Files
CCU621M/BSP/eeprom/fm24cl16.c
T

632 lines
19 KiB
C

/*!
\file fm24cl16.c
\brief the read and write function file
\version 2025-01-24, V1.4.0, firmware for GD32H7xx
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
3. Neither the name of the copyright holder 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 IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "fm24cl16.h"
#include "i2c1.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#define EEPROM_BLOCK0_ADDRESS 0xA0
#define MAX_RELOAD_SIZE 255
#define EEPROM_WRITE_DELAY_MS 5U
#define EEPROM_STATE_LOOP_MAX 64U
/* Stable mode on this board: fixed slave address + 16-bit internal address */
#define EEPROM_MEM_ADDR_16BIT 1U
#define EEPROM_DEVADDR_FIXED 1U
static uint8_t eeprom_address;
static volatile uint32_t g_eeprom_last_error = 0U;
static SemaphoreHandle_t g_eeprom_mutex = NULL;
/* forward declarations for internal helpers */
static void eeprom_i2c_clear_error_flags(void);
uint8_t eeprom_probe(uint16_t mem_address);
static void eeprom_lock(void)
{
if (g_eeprom_mutex != NULL) {
(void)xSemaphoreTake(g_eeprom_mutex, portMAX_DELAY);
}
}
static void eeprom_unlock(void)
{
if (g_eeprom_mutex != NULL) {
(void)xSemaphoreGive(g_eeprom_mutex);
}
}
enum {
EEPROM_ERR_NONE = 0U,
EEPROM_ERR_WR_BUSY_TIMEOUT = 1U,
EEPROM_ERR_WR_ADDR_TBE_TIMEOUT = 2U,
EEPROM_ERR_WR_DATA_TBE_TIMEOUT = 3U,
EEPROM_ERR_WR_STOP_TIMEOUT = 4U,
EEPROM_ERR_RD_BUSY_TIMEOUT = 5U,
EEPROM_ERR_RD_ADDR_TBE_TIMEOUT = 6U,
EEPROM_ERR_RD_RESTART_TC_TIMEOUT = 7U,
EEPROM_ERR_RD_RELOAD_TCR_TIMEOUT = 8U,
EEPROM_ERR_RD_DATA_RBNE_TIMEOUT = 9U,
EEPROM_ERR_RD_STOP_TIMEOUT = 10U,
EEPROM_ERR_LOOP_GUARD = 11U,
EEPROM_ERR_I2C_NACK = 12U,
EEPROM_ERR_I2C_BUS = 13U
};
static uint8_t eeprom_i2c_error_pending(void)
{
if(i2c_flag_get(I2CX, I2C_FLAG_NACK)) {
g_eeprom_last_error = EEPROM_ERR_I2C_NACK;
i2c_flag_clear(I2CX, I2C_FLAG_NACK);
return 1U;
}
if(i2c_flag_get(I2CX, I2C_FLAG_BERR) || i2c_flag_get(I2CX, I2C_FLAG_LOSTARB) ||
i2c_flag_get(I2CX, I2C_FLAG_OUERR)) {
g_eeprom_last_error = EEPROM_ERR_I2C_BUS;
i2c_flag_clear(I2CX, I2C_FLAG_BERR);
i2c_flag_clear(I2CX, I2C_FLAG_LOSTARB);
i2c_flag_clear(I2CX, I2C_FLAG_OUERR);
return 1U;
}
return 0U;
}
static void eeprom_i2c_clear_error_flags(void)
{
i2c_flag_clear(I2CX, I2C_FLAG_NACK);
i2c_flag_clear(I2CX, I2C_FLAG_BERR);
i2c_flag_clear(I2CX, I2C_FLAG_LOSTARB);
i2c_flag_clear(I2CX, I2C_FLAG_OUERR);
i2c_flag_clear(I2CX, I2C_FLAG_TIMEOUT);
}
static uint8_t eeprom_make_dev_addr(uint16_t mem_addr)
{
#if (EEPROM_DEVADDR_FIXED == 1U)
(void)mem_addr;
return (uint8_t)EEPROM_BLOCK0_ADDRESS;
#else
return (uint8_t)(EEPROM_BLOCK0_ADDRESS | ((mem_addr >> 7U) & 0x0EU));
#endif
}
static void eeprom_page_write(uint8_t *p_buffer, uint16_t write_address, uint8_t number_of_byte);
static uint8_t eeprom_read_chunk(uint8_t *p_buffer, uint16_t read_address, uint8_t number_of_byte);
uint32_t eeprom_get_last_error(void)
{
return g_eeprom_last_error;
}
uint8_t eeprom_probe(uint16_t mem_address)
{
uint32_t timeout = 0U;
uint8_t dev_addr = eeprom_make_dev_addr(mem_address);
eeprom_lock();
eeprom_i2c_clear_error_flags();
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_master_addressing(I2CX, dev_addr, I2C_MASTER_TRANSMIT);
i2c_transfer_byte_number_config(I2CX,
#if (EEPROM_MEM_ADDR_16BIT == 1U)
2U
#else
1U
#endif
);
i2c_automatic_end_disable(I2CX);
while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_BUSY_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_start_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_stop_on_bus(I2CX);
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
/* send memory address bytes to validate addressing scheme */
#if (EEPROM_MEM_ADDR_16BIT == 1U)
i2c_data_transmit(I2CX, (uint8_t)((mem_address >> 8U) & 0xFFU));
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_stop_on_bus(I2CX);
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
#endif
i2c_data_transmit(I2CX, (uint8_t)(mem_address & 0xFFU));
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TC)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_stop_on_bus(I2CX);
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_DATA_TBE_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_stop_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_STPDET)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_STOP_TIMEOUT;
i2c_bus_reset();
eeprom_unlock();
return I2C_FAIL;
}
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
g_eeprom_last_error = EEPROM_ERR_NONE;
eeprom_unlock();
return I2C_OK;
}
/*!
\brief unified init entry for EEPROM driver
\param[in] none
\param[out] none
\retval none
*/
void eeprom_driver_init(void)
{
gpio_config();
i2c_config();
i2c_eeprom_init();
if (g_eeprom_mutex == NULL) {
g_eeprom_mutex = xSemaphoreCreateMutex();
}
}
/*!
\brief initialize peripherals used by the I2C EEPROM driver
\param[in] none
\param[out] none
\retval none
*/
void i2c_eeprom_init(void)
{
eeprom_address = EEPROM_BLOCK0_ADDRESS;
}
/*!
\brief write buffer of data to the I2C EEPROM
\param[in] p_buffer: pointer to the buffer containing the data to be written to the EEPROM
\param[in] write_address: EEPROM's internal address to write to
\param[in] number_of_byte: number of bytes to write to the EEPROM
\param[out] none
\retval none
*/
void eeprom_buffer_write(uint8_t *p_buffer, uint16_t write_address, uint16_t number_of_byte)
{
uint16_t remain;
uint16_t off;
uint8_t chunk;
uint8_t page_left;
uint8_t retry;
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
g_eeprom_last_error = EEPROM_ERR_NONE;
return;
}
eeprom_lock();
g_eeprom_last_error = EEPROM_ERR_NONE;
remain = number_of_byte;
off = 0U;
while (remain > 0U) {
/* Do not cross page boundary (AT24/FRAM compatible safe strategy). */
page_left = (uint8_t)(I2C_PAGE_SIZE - (((uint16_t)(write_address + off)) % I2C_PAGE_SIZE));
if (page_left == 0U) {
page_left = I2C_PAGE_SIZE;
}
chunk = (remain > (uint16_t)page_left) ? page_left : (uint8_t)remain;
if (chunk > I2C_PAGE_SIZE) {
chunk = I2C_PAGE_SIZE;
}
retry = 0U;
while (retry < 3U) {
eeprom_page_write(&p_buffer[off], (uint16_t)(write_address + off), chunk);
if (g_eeprom_last_error == EEPROM_ERR_NONE) {
break;
}
/* retry after simple bus recovery */
i2c_bus_reset();
vTaskDelay(pdMS_TO_TICKS(1U));
retry++;
}
if (g_eeprom_last_error != EEPROM_ERR_NONE) {
eeprom_unlock();
return;
}
off = (uint16_t)(off + (uint16_t)chunk);
remain = (uint16_t)(remain - (uint16_t)chunk);
vTaskDelay(pdMS_TO_TICKS(EEPROM_WRITE_DELAY_MS));
}
eeprom_unlock();
}
/*!
\brief write more than one byte to the EEPROM with a single write cycle
\param[in] p_buffer: pointer to the buffer containing the data to be written to the EEPROM
\param[in] write_address: EEPROM's internal address to write to
\param[in] number_of_byte: number of bytes to write to the EEPROM
\param[out] none
\retval none
*/
static void eeprom_page_write(uint8_t *p_buffer, uint16_t write_address, uint8_t number_of_byte)
{
uint32_t timeout = 0U;
uint8_t bytes_sent = 0;
uint8_t mem_high_addr = (uint8_t)((write_address >> 8U) & 0xFFU);
uint8_t mem_low_addr = (uint8_t)(write_address & 0xFFU);
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
return;
}
eeprom_address = eeprom_make_dev_addr(write_address);
eeprom_i2c_clear_error_flags();
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_BUSY_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_master_addressing(I2CX, eeprom_address, I2C_MASTER_TRANSMIT);
i2c_transfer_byte_number_config(I2CX,
#if (EEPROM_MEM_ADDR_16BIT == 1U)
(uint32_t)number_of_byte + 2U
#else
(uint32_t)number_of_byte + 1U
#endif
);
/* Use explicit TC->STOP sequence to avoid partial writes */
i2c_automatic_end_disable(I2CX);
i2c_reload_disable(I2CX);
i2c_start_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
#if (EEPROM_MEM_ADDR_16BIT == 1U)
i2c_data_transmit(I2CX, mem_high_addr);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
#endif
i2c_data_transmit(I2CX, mem_low_addr);
/* wait address byte(s) accepted before data */
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
while(bytes_sent < number_of_byte) {
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_DATA_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_data_transmit(I2CX, *p_buffer);
p_buffer++;
bytes_sent++;
}
/* wait transfer complete then send stop */
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TC)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_DATA_TBE_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_stop_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_STPDET)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_WR_STOP_TIMEOUT;
i2c_bus_reset();
return;
}
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
}
static uint8_t eeprom_read_chunk(uint8_t *p_buffer, uint16_t read_address, uint8_t number_of_byte)
{
uint32_t timeout = 0U;
uint8_t mem_high_addr = (uint8_t)((read_address >> 8U) & 0xFFU);
uint8_t mem_low_addr = (uint8_t)(read_address & 0xFFU);
uint8_t dev_addr = eeprom_make_dev_addr(read_address);
uint8_t i = 0U;
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
return I2C_FAIL;
}
eeprom_i2c_clear_error_flags();
while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_BUSY_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
/* phase 1: set memory low address */
i2c_master_addressing(I2CX, dev_addr, I2C_MASTER_TRANSMIT);
i2c_transfer_byte_number_config(I2CX,
#if (EEPROM_MEM_ADDR_16BIT == 1U)
2U
#else
1U
#endif
);
i2c_automatic_end_disable(I2CX);
i2c_start_on_bus(I2CX);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
#if (EEPROM_MEM_ADDR_16BIT == 1U)
i2c_data_transmit(I2CX, mem_high_addr);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_ADDR_TBE_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
#endif
i2c_data_transmit(I2CX, mem_low_addr);
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_TC)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_RESTART_TC_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
/* phase 2: repeated start + read bytes */
i2c_master_addressing(I2CX, dev_addr, I2C_MASTER_RECEIVE);
i2c_transfer_byte_number_config(I2CX, number_of_byte);
i2c_automatic_end_enable(I2CX);
i2c_start_on_bus(I2CX);
for(i = 0U; i < number_of_byte; i++) {
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_RBNE)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_DATA_RBNE_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
p_buffer[i] = (uint8_t)i2c_data_receive(I2CX);
}
timeout = 0U;
while((!i2c_flag_get(I2CX, I2C_FLAG_STPDET)) && (timeout < I2C_TIME_OUT)) {
if(eeprom_i2c_error_pending()) {
i2c_bus_reset();
return I2C_FAIL;
}
timeout++;
}
if(timeout >= I2C_TIME_OUT) {
g_eeprom_last_error = EEPROM_ERR_RD_STOP_TIMEOUT;
i2c_bus_reset();
return I2C_FAIL;
}
i2c_flag_clear(I2CX, I2C_FLAG_STPDET);
return I2C_OK;
}
/*!
\brief read data from the EEPROM
\param[in] p_buffer: pointer to the buffer that receives the data read from the EEPROM
\param[in] read_address: EEPROM's internal address to start reading from
\param[in] number_of_byte: number of bytes to reads from the EEPROM
\param[out] none
\retval none
*/
void eeprom_buffer_read(uint8_t *p_buffer, uint16_t read_address, uint16_t number_of_byte)
{
uint16_t remain;
uint16_t off;
uint8_t chunk;
uint8_t retry;
if ((p_buffer == NULL) || (number_of_byte == 0U)) {
g_eeprom_last_error = EEPROM_ERR_NONE;
return;
}
eeprom_lock();
g_eeprom_last_error = EEPROM_ERR_NONE;
remain = number_of_byte;
off = 0U;
while (remain > 0U) {
/* For read, allow larger chunk but keep it reasonable */
chunk = (remain > 128U) ? 128U : (uint8_t)remain;
retry = 0U;
while (retry < 3U) {
if (eeprom_read_chunk(&p_buffer[off], (uint16_t)(read_address + off), chunk) == I2C_OK) {
break;
}
i2c_bus_reset();
vTaskDelay(pdMS_TO_TICKS(1U));
retry++;
}
if (g_eeprom_last_error != EEPROM_ERR_NONE) {
eeprom_unlock();
return;
}
off = (uint16_t)(off + (uint16_t)chunk);
remain = (uint16_t)(remain - (uint16_t)chunk);
vTaskDelay(pdMS_TO_TICKS(1U));
}
eeprom_unlock();
}