/** ****************************************************************************** * * @file pios_flash_eeprom.c * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2014. * @addtogroup PIOS PIOS Core hardware abstraction layer * @{ * @addtogroup PIOS_FLASH EEPROM Driver API Definition * @{ * @brief EEPROM Driver API Definition * @see The GNU Public License (GPL) Version 3 * *****************************************************************************/ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #ifdef PIOS_INCLUDE_FLASH_EEPROM #ifndef PIOS_EEPROM_EMULATED_SECTOR_SIZE #define PIOS_EEPROM_EMULATED_SECTOR_SIZE 256 #endif #include #include enum pios_eeprom_dev_magic { PIOS_EEPROM_DEV_MAGIC = 0xEE55aa55, }; // ! Device handle structure struct flash_eeprom_dev { enum pios_eeprom_dev_magic magic; uint32_t i2c_adapter; uint32_t i2c_address; const struct pios_flash_eeprom_cfg *cfg; #ifdef PIOS_INCLUDE_FREERTOS xSemaphoreHandle transaction_lock; #endif }; // ! Private functions static int32_t PIOS_Flash_EEPROM_Validate(struct flash_eeprom_dev *flash_dev); static struct flash_eeprom_dev *PIOS_Flash_EEPROM_alloc(void); static int32_t PIOS_Flash_EEPROM_Busy(struct flash_eeprom_dev *flash_dev); /** * @brief Allocate a new device */ static struct flash_eeprom_dev *PIOS_Flash_EEPROM_alloc(void) { struct flash_eeprom_dev *flash_dev; flash_dev = (struct flash_eeprom_dev *)pios_malloc(sizeof(*flash_dev)); if (!flash_dev) { return NULL; } flash_dev->magic = PIOS_EEPROM_DEV_MAGIC; #ifdef PIOS_INCLUDE_FREERTOS flash_dev->transaction_lock = xSemaphoreCreateMutex(); #endif return flash_dev; } /** * @brief Validate the handler to the device */ static int32_t PIOS_Flash_EEPROM_Validate(struct flash_eeprom_dev *flash_dev) { if (flash_dev == NULL) { return -1; } if (flash_dev->magic != PIOS_EEPROM_DEV_MAGIC) { return -2; } if (flash_dev->i2c_adapter == 0 || flash_dev->i2c_address == 0) { return -3; } return 0; } /** * @brief Initialize the flash device and enable write access */ int32_t PIOS_Flash_EEPROM_Init(uintptr_t *flash_id, struct pios_flash_eeprom_cfg *cfg, int32_t i2c_adapter, uint32_t i2c_addr) { struct flash_eeprom_dev *flash_dev = PIOS_Flash_EEPROM_alloc(); if (!flash_dev) { return -1; } flash_dev->i2c_adapter = i2c_adapter; flash_dev->i2c_address = i2c_addr; flash_dev->cfg = cfg; if (!flash_dev->cfg) { return -1; } /* Give back a handle to this flash device */ *flash_id = (uintptr_t)flash_dev; return 0; } /** * Reads one or more bytes into a buffer * \param[in] the command indicating the address to read * \param[out] buffer destination buffer * \param[in] len number of bytes which should be read * \return 0 if operation was successful * \return -1 if error during I2C transfer */ int32_t PIOS_Flash_EEPROM_ReadSinglePage(struct flash_eeprom_dev *flash_dev, uint32_t address, uint8_t *buffer, uint8_t len) { uint8_t i2c_addr = flash_dev->i2c_address; uint32_t bus = flash_dev->i2c_adapter; if (address > 0xFFFF) { return -2; } uint8_t mem_address[] = { (address & 0xFF00) >> 8, // MSB (address & 0xFF) // LSB }; const struct pios_i2c_txn txn_list[] = { { .info = __func__, .addr = i2c_addr, .rw = PIOS_I2C_TXN_WRITE, .len = 2, .buf = mem_address, } , { .info = __func__, .addr = i2c_addr, .rw = PIOS_I2C_TXN_READ, .len = len, .buf = buffer, } }; return PIOS_I2C_Transfer(bus, txn_list, NELEMENTS(txn_list)); } /** * Writes one up to a single page (128 bytes) to the EEPROM * \param[in] address write start address * \param[in] buffer source buffer * \param[in] len Buffer length * \return 0 if operation was successful * \return -1 if error during I2C transfer */ int32_t PIOS_Flash_EEPROM_WriteSinglePage(struct flash_eeprom_dev *flash_dev, const uint32_t address, const uint8_t *buffer, const uint8_t len) { uint8_t i2c_addr = flash_dev->i2c_address; uint32_t bus = flash_dev->i2c_adapter; if (address > 0xFFFF) { return -2; } uint16_t address16 = address & 0xFFFF; uint8_t tmp[len + 2]; tmp[0] = (address16 & 0xFF00) >> 8; // MSB tmp[1] = (address16 & 0xFF); // LSB memcpy(&tmp[2], buffer, len); const struct pios_i2c_txn txn_list[] = { { .info = __func__, .addr = i2c_addr, .rw = PIOS_I2C_TXN_WRITE, .len = len + 2, .buf = tmp, } }; return PIOS_I2C_Transfer(bus, txn_list, NELEMENTS(txn_list)); } /** * Read blocks of data spliting them into several single page operations to the EEPROM * \param[in] address Read start address * \param[in] buffer Dest buffer * \param[in] len Buffer length * \return 0 if operation was successful * \return -1 if error during I2C transfer */ int32_t PIOS_Flash_EEPROM_Read(struct flash_eeprom_dev *flash_dev, const uint32_t address, uint8_t *buffer, const uint8_t len) { uint16_t address16 = address & 0xFFFF; if (address16 < address) { return -2; } // split the operation into several page operations uint8_t bytes_read = 0; const uint16_t page_len = flash_dev->cfg->page_len; while (bytes_read < len) { // all is fine, wait until memory is not busy int32_t status; while ((status = PIOS_Flash_EEPROM_Busy(flash_dev)) == 1) { #ifdef PIOS_INCLUDE_FREERTOS vTaskDelay(0); #endif } // An error occurred while probing the status, return and report if (status < 0) { return status; } uint16_t current_block_len = len - bytes_read; uint16_t index_within_page = (address16 + bytes_read) % page_len; // prevent overflowing the page boundary current_block_len = MIN(page_len - index_within_page, current_block_len); status = PIOS_Flash_EEPROM_ReadSinglePage(flash_dev, address + bytes_read, &buffer[bytes_read], current_block_len); bytes_read += current_block_len; if (status) { // error occurred during the write operation return status; } } return 0; } /** * Writes blocks of data spliting them into several single page writes to the EEPROM * \param[in] address write start address * \param[in] buffer source buffer * \param[in] len Buffer length * \return 0 if operation was successful * \return -1 if error during I2C transfer */ int32_t PIOS_Flash_EEPROM_Write(struct flash_eeprom_dev *flash_dev, const uint32_t address, const uint8_t *buffer, const uint8_t len) { uint16_t address16 = address & 0xFFFF; if (address16 < address) { return -2; } // split the operation into several page writes uint8_t bytes_written = 0; const uint8_t page_len = flash_dev->cfg->page_len; while (bytes_written < len) { // wait until memory is not busy int32_t status; while ((status = PIOS_Flash_EEPROM_Busy(flash_dev)) == 1) { #ifdef PIOS_INCLUDE_FREERTOS vTaskDelay(1); #endif } // An error occurred while probing the status, return and report if (status < 0) { return status; } uint8_t current_block_len = len - bytes_written; uint16_t index_within_page = (address16 + bytes_written) % page_len; // prevent overflowing the page boundary current_block_len = MIN(page_len - index_within_page, current_block_len); status = PIOS_Flash_EEPROM_WriteSinglePage(flash_dev, address + bytes_written, &buffer[bytes_written], current_block_len); bytes_written += current_block_len; if (status) { // error occurred during the write operation return status; } } return 0; } /** * @brief Returns if the flash chip is busy * @returns -1 for failure, 0 for not busy, 1 for busy */ static int32_t PIOS_Flash_EEPROM_Busy(struct flash_eeprom_dev *flash_dev) { uint8_t buf; int32_t status = PIOS_Flash_EEPROM_ReadSinglePage(flash_dev, 0x0, &buf, 1); switch (status) { case -3: // NACK return 1; case 0: return 0; default: return -1; } } /********************************** * * Provide a PIOS flash driver API * *********************************/ #include "pios_flash.h" #if FLASH_USE_FREERTOS_LOCKS /** * @brief Grab the semaphore to perform a transaction * @return 0 for success, -1 for timeout */ static int32_t PIOS_Flash_EEPROM_StartTransaction(uintptr_t flash_id) { struct flash_eeprom_dev *flash_dev = (struct flash_eeprom_dev *)flash_id; if (PIOS_Flash_EEPROM_Validate(flash_dev) != 0) { return -1; } #if defined(PIOS_INCLUDE_FREERTOS) if (xSemaphoreTake(flash_dev->transaction_lock, portMAX_DELAY) != pdTRUE) { return -2; } #endif return 0; } /** * @brief Release the semaphore to perform a transaction * @return 0 for success, -1 for timeout */ static int32_t PIOS_Flash_EEPROM_EndTransaction(uintptr_t flash_id) { struct flash_eeprom_dev *flash_dev = (struct flash_eeprom_dev *)flash_id; if (PIOS_Flash_EEPROM_Validate(flash_dev) != 0) { return -1; } #if defined(PIOS_INCLUDE_FREERTOS) if (xSemaphoreGive(flash_dev->transaction_lock) != pdTRUE) { return -2; } #endif return 0; } #else /* FLASH_USE_FREERTOS_LOCKS */ static int32_t PIOS_Flash_EEPROM_StartTransaction(__attribute__((unused)) uintptr_t flash_id) { return 0; } static int32_t PIOS_Flash_EEPROM_EndTransaction(__attribute__((unused)) uintptr_t flash_id) { return 0; } #endif /* FLASH_USE_FREERTOS_LOCKS */ /** * @brief Erase a sector on the flash chip * @param[in] add Address of flash to erase * @returns 0 if successful * @retval -1 if unable to claim bus * @retval */ static int32_t PIOS_Flash_EEPROM_EraseSector(uintptr_t flash_id, uint32_t addr) { struct flash_eeprom_dev *flash_dev = (struct flash_eeprom_dev *)flash_id; if (PIOS_Flash_EEPROM_Validate(flash_dev) != 0) { return -1; } // Rewrite the whole page const uint8_t buf[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; uint32_t current = addr; while (current - addr < PIOS_EEPROM_EMULATED_SECTOR_SIZE) { uint32_t erase_size = (MIN(sizeof(buf), flash_dev->cfg->page_len)); erase_size = MIN(erase_size, (PIOS_EEPROM_EMULATED_SECTOR_SIZE - current + addr)); int32_t res = PIOS_Flash_EEPROM_Write(flash_dev, current, buf, erase_size); if (!res) { return res; } current += erase_size; } return 0; } /** * @brief Write one page of data (up to 256 bytes) aligned to a page start * @param[in] addr Address in flash to write to * @param[in] data Pointer to data to write to flash * @param[in] len Length of data to write (max 256 bytes) * @return Zero if success or error code * @retval -1 Unable to write to the device */ static int32_t PIOS_Flash_EEPROM_WriteData(uintptr_t flash_id, uint32_t addr, uint8_t *data, uint16_t len) { struct flash_eeprom_dev *flash_dev = (struct flash_eeprom_dev *)flash_id; if (PIOS_Flash_EEPROM_Validate(flash_dev) != 0) { return -1; } if (PIOS_Flash_EEPROM_Write(flash_dev, addr, data, len) < 0) { return -1; } return 0; } /** * @brief Write multiple chunks of data in one transaction * @param[in] addr Address in flash to write to * @param[in] data Pointer to data to write to flash * @param[in] len Length of data to write * @return Zero if success or error code * @retval -1 Unable to write to the device */ static int32_t PIOS_Flash_EEPROM_WriteChunks(uintptr_t flash_id, uint32_t addr, struct pios_flash_chunk chunks[], uint32_t num) { struct flash_eeprom_dev *flash_dev = (struct flash_eeprom_dev *)flash_id; if (PIOS_Flash_EEPROM_Validate(flash_dev) != 0) { return -1; } uint32_t memory_displacement = 0; for (uint32_t i = 0; i < num; i++) { struct pios_flash_chunk *chunk = &chunks[i]; // no need to check for busy flag as it is done before each write operation by _Write function if (PIOS_Flash_EEPROM_Write(flash_dev, addr + memory_displacement, chunk->addr, chunk->len) < 0) { return -1; } memory_displacement += chunk->len; } return 0; } /** * @brief Read data from a location in flash memory * @param[in] addr Address in flash to write to * @param[in] data Pointer to data to write from flash * @param[in] len Length of data to write (max 256 bytes) * @return Zero if success or error code * @retval -1 Unable to read from the device */ static int32_t PIOS_Flash_EEPROM_ReadData(uintptr_t flash_id, uint32_t addr, uint8_t *data, uint16_t len) { struct flash_eeprom_dev *flash_dev = (struct flash_eeprom_dev *)flash_id; if (PIOS_Flash_EEPROM_Validate(flash_dev) != 0) { return -1; } if (PIOS_Flash_EEPROM_Read(flash_dev, addr, data, len) < 0) { return -1; } return 0; } /* Provide a flash driver to external drivers */ const struct pios_flash_driver pios_EEPROM_flash_driver = { .start_transaction = PIOS_Flash_EEPROM_StartTransaction, .end_transaction = PIOS_Flash_EEPROM_EndTransaction, .erase_chip = NULL, .erase_sector = PIOS_Flash_EEPROM_EraseSector, .write_chunks = PIOS_Flash_EEPROM_WriteChunks, .rewrite_chunks = PIOS_Flash_EEPROM_WriteChunks, .write_data = PIOS_Flash_EEPROM_WriteData, .rewrite_data = PIOS_Flash_EEPROM_WriteData, .read_data = PIOS_Flash_EEPROM_ReadData, }; #endif /* PIOS_INCLUDE_FLASH_EEPROM*/