/**********************************************************************
|
* Copyright: (C)2024 LingYun IoT System Studio
|
* Author: GuoWenxue<guowenxue@gmail.com>
|
* Description: I2C EEPROM device driver for ISKBoard (Internal Lock Version)
|
*
|
* ChangeLog:
|
* Version Date Author Description
|
* V1.5.0 2026.07.08 GuoWenxue Use goto cleanup for unified lock releasing
|
***********************************************************************/
|
|
#include "eeprom.h"
|
#include "miscdev.h" /* 提供 mdelay() / udelay() */
|
#include <stdio.h>
|
#include <string.h>
|
|
|
/* EEPROM 型号表:未来若有新芯片,只需在此处追加一行,其余代码不用更改 */
|
static const eeprom_info_t eeprom_ids[] = {
|
{ EEPROM_INFO("AT24C02", 256, 8, 1) },
|
{ EEPROM_INFO("AT24C04", 512, 16, 1) },
|
{ EEPROM_INFO("AT24C08", 1024, 16, 1) },
|
{ EEPROM_INFO("AT24C16", 2048, 16, 1) },
|
{ EEPROM_INFO("AT24C32", 4096, 32, 2) },
|
{ EEPROM_INFO("AT24C64", 8192, 32, 2) },
|
{ EEPROM_INFO("AT24C128", 16384, 64, 2) },
|
{ EEPROM_INFO("AT24C256", 32768, 64, 2) },
|
};
|
#define EEPROM_IDS_NUM (sizeof(eeprom_ids) / sizeof(eeprom_ids[0]))
|
|
/**
|
* @brief 根据芯片型号名称内部查找匹配的型号
|
*/
|
static const eeprom_info_t *eeprom_find_info_by_name(const char *name)
|
{
|
uint32_t i;
|
for (i = 0; i < EEPROM_IDS_NUM; i++)
|
{
|
if (strcmp(eeprom_ids[i].name, name) == 0)
|
{
|
return &eeprom_ids[i];
|
}
|
}
|
return NULL;
|
}
|
|
/**
|
* @brief 初始化 EEPROM 设备参数(通过型号名称自动查表匹配)
|
*/
|
int eeprom_init(eeprom_dev_t *eeprom, i2c_bus_t *bus, uint8_t dev_addr, const char *chip_name)
|
{
|
const eeprom_info_t *info = eeprom_find_info_by_name(chip_name);
|
|
if (!info || !eeprom || !bus)
|
{
|
printf("EEPROM: Named device [%s] not supported!\r\n", chip_name ? chip_name : "NULL");
|
return -1;
|
}
|
|
eeprom->bus = bus;
|
eeprom->dev_addr = dev_addr;
|
eeprom->info = info;
|
|
printf("Initialized EEPROM(%s), Capacity: %ld Bytes, Page Size: %ld\r\n",
|
info->name, info->capacity, info->page_size);
|
return 0;
|
}
|
|
/**
|
* @brief EEPROM 测试函数
|
*/
|
int eeprom_test(i2c_bus_t *bus)
|
{
|
eeprom_dev_t my_at24c256;
|
uint8_t tx_buf[100];
|
uint8_t rx_buf[100];
|
int i;
|
int rv = -1;
|
|
/* 1. 初始化 EEPROM 参数 */
|
if (eeprom_init(&my_at24c256, bus, 0x50, "AT24C256") != 0)
|
{
|
return -1;
|
}
|
|
/* 2. 填充测试数据 */
|
for (i = 0; i < sizeof(tx_buf); i++)
|
{
|
tx_buf[i] = i;
|
}
|
|
/* 3. 测试跨页连续写 */
|
if (eeprom_write(&my_at24c256, 0x00A0, tx_buf, sizeof(tx_buf)) != I2C_OK)
|
{
|
goto cleanup;
|
}
|
|
/* 4. 测试任意地址连续读 */
|
if (eeprom_read(&my_at24c256, 0x00A0, rx_buf, sizeof(rx_buf)) != I2C_OK)
|
{
|
goto cleanup;
|
}
|
|
/* 5. 数据一致性校验 */
|
if (memcmp(tx_buf, rx_buf, sizeof(tx_buf)) == 0)
|
{
|
rv = 0;
|
}
|
|
cleanup:
|
printf("AT24C256 Read/Write Test %s!\r\n", rv == 0 ? "Passed" : "Failed");
|
return rv;
|
}
|
|
/**
|
* @brief 24C16仅支持单字节地址(可寻址256字节),需借用I2C设备地址的低3位充当片内地址的高3位。
|
*/
|
static inline uint8_t eeprom_get_actual_addr(eeprom_dev_t *eeprom, uint16_t reg_addr)
|
{
|
uint8_t actual_dev_addr = eeprom->dev_addr;
|
if (eeprom->info->addr_bytes == 1 && eeprom->info->capacity > 256)
|
{
|
actual_dev_addr |= (uint8_t)((reg_addr >> 8) & 0x07);
|
}
|
return actual_dev_addr;
|
}
|
|
/**
|
* @brief 从 EEPROM 任意地址读取指定长度数据
|
*/
|
i2c_status_t eeprom_read(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len)
|
{
|
uint8_t addr_buf[2];
|
uint8_t addr_len = 0;
|
i2c_msg_t msgs[2];
|
i2c_status_t status;
|
|
if ( len == 0 || !buf || !eeprom || !eeprom->bus || !eeprom->info )
|
{
|
return I2C_OK;
|
}
|
|
/* 1. 准备发送数据 */
|
if (eeprom->info->addr_bytes == 2)
|
{
|
addr_buf[addr_len++] = (uint8_t)(reg_addr >> 8);
|
}
|
addr_buf[addr_len++] = (uint8_t)(reg_addr & 0xFF);
|
|
/* 2. 封装 I2C 消息 */
|
msgs[0].addr = eeprom_get_actual_addr(eeprom, reg_addr);
|
msgs[0].flags = I2C_M_WR;
|
msgs[0].len = addr_len;
|
msgs[0].buf = addr_buf;
|
|
msgs[1].addr = msgs[0].addr;
|
msgs[1].flags = I2C_M_RD;
|
msgs[1].len = len;
|
msgs[1].buf = buf;
|
|
/* 3. 锁住 I2C 总线 */
|
if (i2c_lock(eeprom->bus) != I2C_OK)
|
{
|
return I2C_BUSY;
|
}
|
|
/* 4. 调用 i2c_transfer 发送消息 */
|
status = i2c_transfer(eeprom->bus, msgs, 2);
|
|
/* 5. 释放 I2C 总线 */
|
i2c_unlock(eeprom->bus);
|
|
return status;
|
}
|
|
/**
|
* @brief 轮询 EEPROM 硬件内部擦写就绪状态
|
*/
|
static void eeprom_wait_standby(eeprom_dev_t *eeprom, uint16_t reg_addr)
|
{
|
i2c_msg_t msg;
|
uint32_t timeout = 2000;
|
|
msg.addr = eeprom_get_actual_addr(eeprom, reg_addr);
|
msg.flags = I2C_M_WR;
|
msg.len = 0;
|
msg.buf = NULL;
|
|
while (timeout--)
|
{
|
if (i2c_transfer(eeprom->bus, &msg, 1) == I2C_OK)
|
{
|
break; /* 芯片返回 ACK,擦写完成,退出轮询 */
|
}
|
udelay(50);
|
}
|
}
|
|
/**
|
* @brief 向 EEPROM 写入单页数据
|
*/
|
static i2c_status_t eeprom_write_page(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len)
|
{
|
uint8_t tx_payload[128];
|
uint8_t addr_len = 0;
|
i2c_msg_t msg;
|
i2c_status_t status = I2C_OK;
|
|
/* 1. 准备发送数据 */
|
if (eeprom->info->addr_bytes == 2)
|
{
|
tx_payload[addr_len++] = (uint8_t)(reg_addr >> 8);
|
}
|
tx_payload[addr_len++] = (uint8_t)(reg_addr & 0xFF);
|
memcpy(&tx_payload[addr_len], buf, len);
|
|
msg.addr = eeprom_get_actual_addr(eeprom, reg_addr);
|
msg.flags = I2C_M_WR;
|
msg.len = addr_len + len;
|
msg.buf = tx_payload;
|
|
/* 2. 锁住 I2C 总线 */
|
if (i2c_lock(eeprom->bus) != I2C_OK)
|
{
|
return I2C_BUSY;
|
}
|
|
/* 3. 调用 i2c_transfer 发送消息 */
|
status = i2c_transfer(eeprom->bus, &msg, 1);
|
if (status != I2C_OK)
|
{
|
status = I2C_ERROR;
|
goto cleanup;
|
}
|
|
/* 4. 轮询等待 EEPROM 内部擦写完成 */
|
eeprom_wait_standby(eeprom, reg_addr);
|
|
cleanup:
|
/* 5. 释放 I2C 总线 */
|
i2c_unlock(eeprom->bus);
|
|
return status;
|
}
|
|
/**
|
* @brief 大批量数据动态跨页分包连续写入
|
*/
|
i2c_status_t eeprom_write(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len)
|
{
|
i2c_status_t status = I2C_OK;
|
uint16_t count = 0;
|
uint16_t page_offset;
|
uint16_t bytes_to_write;
|
|
if ( len == 0 || !buf || !eeprom || !eeprom->bus || !eeprom->info )
|
{
|
return I2C_OK;
|
}
|
|
while (len > 0)
|
{
|
page_offset = reg_addr % eeprom->info->page_size;
|
|
bytes_to_write = eeprom->info->page_size - page_offset;
|
if (bytes_to_write > len)
|
{
|
bytes_to_write = len;
|
}
|
|
/* 内部单页级连写带等就绪的一体化锁定 */
|
status = eeprom_write_page(eeprom, reg_addr, &buf[count], bytes_to_write);
|
if (status != I2C_OK)
|
{
|
goto cleanup;
|
}
|
|
reg_addr += bytes_to_write;
|
count += bytes_to_write;
|
len -= bytes_to_write;
|
}
|
|
cleanup:
|
return status;
|
}
|