You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
672 lines
15 KiB
672 lines
15 KiB
/* |
|
* Copyright (c) 2019 Vestas Wind Systems A/S |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file |
|
* @brief Driver for Atmel AT24 I2C and Atmel AT25 SPI EEPROMs. |
|
*/ |
|
|
|
#include <drivers/eeprom.h> |
|
#include <drivers/gpio.h> |
|
#include <drivers/i2c.h> |
|
#include <drivers/spi.h> |
|
#include <sys/byteorder.h> |
|
#include <zephyr.h> |
|
|
|
#define LOG_LEVEL CONFIG_EEPROM_LOG_LEVEL |
|
#include <logging/log.h> |
|
LOG_MODULE_REGISTER(eeprom_at2x); |
|
|
|
/* AT25 instruction set */ |
|
#define EEPROM_AT25_WRSR 0x01U /* Write STATUS register */ |
|
#define EEPROM_AT25_WRITE 0x02U /* Write data to memory array */ |
|
#define EEPROM_AT25_READ 0x03U /* Read data from memory array */ |
|
#define EEPROM_AT25_WRDI 0x04U /* Reset the write enable latch */ |
|
#define EEPROM_AT25_RDSR 0x05U /* Read STATUS register */ |
|
#define EEPROM_AT25_WREN 0x06U /* Set the write enable latch */ |
|
|
|
/* AT25 status register bits */ |
|
#define EEPROM_AT25_STATUS_WIP BIT(0) /* Write-In-Process (RO) */ |
|
#define EEPROM_AT25_STATUS_WEL BIT(1) /* Write Enable Latch (RO) */ |
|
#define EEPROM_AT25_STATUS_BP0 BIT(2) /* Block Protection 0 (RW) */ |
|
#define EEPROM_AT25_STATUS_BP1 BIT(3) /* Block Protection 1 (RW) */ |
|
|
|
#define HAS_WP_OR(id) DT_NODE_HAS_PROP(id, wp_gpios) || |
|
#define ANY_INST_HAS_WP_GPIOS (DT_FOREACH_STATUS_OKAY(atmel_at24, HAS_WP_OR) \ |
|
DT_FOREACH_STATUS_OKAY(atmel_at25, HAS_WP_OR) 0) |
|
|
|
struct eeprom_at2x_config { |
|
union { |
|
#ifdef CONFIG_EEPROM_AT24 |
|
struct i2c_dt_spec i2c; |
|
#endif /* CONFIG_EEPROM_AT24 */ |
|
#ifdef CONFIG_EEPROM_AT25 |
|
struct spi_dt_spec spi; |
|
#endif /* CONFIG_EEPROM_AT25 */ |
|
} bus; |
|
#if ANY_INST_HAS_WP_GPIOS |
|
struct gpio_dt_spec wp_gpio; |
|
#endif /* ANY_INST_HAS_WP_GPIOS */ |
|
size_t size; |
|
size_t pagesize; |
|
uint8_t addr_width; |
|
bool readonly; |
|
uint16_t timeout; |
|
bool (*bus_is_ready)(const struct device *dev); |
|
eeprom_api_read read_fn; |
|
eeprom_api_write write_fn; |
|
}; |
|
|
|
struct eeprom_at2x_data { |
|
struct k_mutex lock; |
|
}; |
|
|
|
#if ANY_INST_HAS_WP_GPIOS |
|
static inline int eeprom_at2x_write_protect(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
|
|
if (!config->wp_gpio.port) { |
|
return 0; |
|
} |
|
|
|
return gpio_pin_set_dt(&config->wp_gpio, 1); |
|
} |
|
|
|
static inline int eeprom_at2x_write_enable(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
|
|
if (!config->wp_gpio.port) { |
|
return 0; |
|
} |
|
|
|
return gpio_pin_set_dt(&config->wp_gpio, 0); |
|
} |
|
#endif /* ANY_INST_HAS_WP_GPIOS */ |
|
|
|
static int eeprom_at2x_read(const struct device *dev, off_t offset, void *buf, |
|
size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
struct eeprom_at2x_data *data = dev->data; |
|
uint8_t *pbuf = buf; |
|
int ret; |
|
|
|
if (!len) { |
|
return 0; |
|
} |
|
|
|
if ((offset + len) > config->size) { |
|
LOG_WRN("attempt to read past device boundary"); |
|
return -EINVAL; |
|
} |
|
|
|
k_mutex_lock(&data->lock, K_FOREVER); |
|
while (len) { |
|
ret = config->read_fn(dev, offset, pbuf, len); |
|
if (ret < 0) { |
|
LOG_ERR("failed to read EEPROM (err %d)", ret); |
|
k_mutex_unlock(&data->lock); |
|
return ret; |
|
} |
|
|
|
pbuf += ret; |
|
offset += ret; |
|
len -= ret; |
|
} |
|
|
|
k_mutex_unlock(&data->lock); |
|
|
|
return 0; |
|
} |
|
|
|
static size_t eeprom_at2x_limit_write_count(const struct device *dev, |
|
off_t offset, |
|
size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
size_t count = len; |
|
off_t page_boundary; |
|
|
|
/* We can at most write one page at a time */ |
|
if (count > config->pagesize) { |
|
count = config->pagesize; |
|
} |
|
|
|
/* Writes can not cross a page boundary */ |
|
page_boundary = ROUND_UP(offset + 1, config->pagesize); |
|
if (offset + count > page_boundary) { |
|
count = page_boundary - offset; |
|
} |
|
|
|
return count; |
|
} |
|
|
|
static int eeprom_at2x_write(const struct device *dev, off_t offset, |
|
const void *buf, |
|
size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
struct eeprom_at2x_data *data = dev->data; |
|
const uint8_t *pbuf = buf; |
|
int ret; |
|
|
|
if (config->readonly) { |
|
LOG_WRN("attempt to write to read-only device"); |
|
return -EACCES; |
|
} |
|
|
|
if (!len) { |
|
return 0; |
|
} |
|
|
|
if ((offset + len) > config->size) { |
|
LOG_WRN("attempt to write past device boundary"); |
|
return -EINVAL; |
|
} |
|
|
|
k_mutex_lock(&data->lock, K_FOREVER); |
|
|
|
#if ANY_INST_HAS_WP_GPIOS |
|
ret = eeprom_at2x_write_enable(dev); |
|
if (ret) { |
|
LOG_ERR("failed to write-enable EEPROM (err %d)", ret); |
|
k_mutex_unlock(&data->lock); |
|
return ret; |
|
} |
|
#endif /* ANY_INST_HAS_WP_GPIOS */ |
|
|
|
while (len) { |
|
ret = config->write_fn(dev, offset, pbuf, len); |
|
if (ret < 0) { |
|
LOG_ERR("failed to write to EEPROM (err %d)", ret); |
|
#if ANY_INST_HAS_WP_GPIOS |
|
eeprom_at2x_write_protect(dev); |
|
#endif /* ANY_INST_HAS_WP_GPIOS */ |
|
k_mutex_unlock(&data->lock); |
|
return ret; |
|
} |
|
|
|
pbuf += ret; |
|
offset += ret; |
|
len -= ret; |
|
} |
|
|
|
#if ANY_INST_HAS_WP_GPIOS |
|
ret = eeprom_at2x_write_protect(dev); |
|
if (ret) { |
|
LOG_ERR("failed to write-protect EEPROM (err %d)", ret); |
|
} |
|
#else |
|
ret = 0; |
|
#endif /* ANY_INST_HAS_WP_GPIOS */ |
|
|
|
k_mutex_unlock(&data->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static size_t eeprom_at2x_size(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
|
|
return config->size; |
|
} |
|
|
|
#ifdef CONFIG_EEPROM_AT24 |
|
|
|
static bool eeprom_at24_bus_is_ready(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
|
|
return device_is_ready(config->bus.i2c.bus); |
|
} |
|
|
|
/** |
|
* @brief translate an offset to a device address / offset pair |
|
* |
|
* It allows to address several devices as a continuous memory region |
|
* but also to address higher part of eeprom for chips |
|
* with more than 2^(addr_width) adressable word. |
|
*/ |
|
static uint16_t eeprom_at24_translate_offset(const struct device *dev, |
|
off_t *offset) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
|
|
const uint16_t addr_incr = *offset >> config->addr_width; |
|
*offset &= BIT_MASK(config->addr_width); |
|
|
|
return config->bus.i2c.addr + addr_incr; |
|
} |
|
|
|
static size_t eeprom_at24_adjust_read_count(const struct device *dev, |
|
off_t offset, size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
const size_t remainder = BIT(config->addr_width) - offset; |
|
|
|
if (len > remainder) { |
|
len = remainder; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
static int eeprom_at24_read(const struct device *dev, off_t offset, void *buf, |
|
size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
int64_t timeout; |
|
uint8_t addr[2]; |
|
uint16_t bus_addr; |
|
int err; |
|
|
|
bus_addr = eeprom_at24_translate_offset(dev, &offset); |
|
|
|
if (config->addr_width == 16) { |
|
sys_put_be16(offset, addr); |
|
} else { |
|
addr[0] = offset & BIT_MASK(8); |
|
} |
|
|
|
len = eeprom_at24_adjust_read_count(dev, offset, len); |
|
|
|
/* |
|
* A write cycle may be in progress so reads must be attempted |
|
* until the current write cycle should be completed. |
|
*/ |
|
timeout = k_uptime_get() + config->timeout; |
|
while (1) { |
|
int64_t now = k_uptime_get(); |
|
err = i2c_write_read(config->bus.i2c.bus, bus_addr, |
|
addr, config->addr_width / 8, |
|
buf, len); |
|
if (!err || now > timeout) { |
|
break; |
|
} |
|
k_sleep(K_MSEC(1)); |
|
} |
|
|
|
if (err < 0) { |
|
return err; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
static int eeprom_at24_write(const struct device *dev, off_t offset, |
|
const void *buf, size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
int count = eeprom_at2x_limit_write_count(dev, offset, len); |
|
uint8_t block[config->addr_width / 8 + count]; |
|
int64_t timeout; |
|
uint16_t bus_addr; |
|
int i = 0; |
|
int err; |
|
|
|
bus_addr = eeprom_at24_translate_offset(dev, &offset); |
|
|
|
/* |
|
* Not all I2C EEPROMs support repeated start so the the |
|
* address (offset) and data (buf) must be provided in one |
|
* write transaction (block). |
|
*/ |
|
if (config->addr_width == 16) { |
|
block[i++] = offset >> 8; |
|
} |
|
block[i++] = offset; |
|
memcpy(&block[i], buf, count); |
|
|
|
/* |
|
* A write cycle may already be in progress so writes must be |
|
* attempted until the previous write cycle should be |
|
* completed. |
|
*/ |
|
timeout = k_uptime_get() + config->timeout; |
|
while (1) { |
|
int64_t now = k_uptime_get(); |
|
err = i2c_write(config->bus.i2c.bus, block, sizeof(block), |
|
bus_addr); |
|
if (!err || now > timeout) { |
|
break; |
|
} |
|
k_sleep(K_MSEC(1)); |
|
} |
|
|
|
if (err < 0) { |
|
return err; |
|
} |
|
|
|
return count; |
|
} |
|
#endif /* CONFIG_EEPROM_AT24 */ |
|
|
|
#ifdef CONFIG_EEPROM_AT25 |
|
|
|
static bool eeprom_at25_bus_is_ready(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
|
|
return spi_is_ready(&config->bus.spi); |
|
} |
|
|
|
static int eeprom_at25_rdsr(const struct device *dev, uint8_t *status) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
uint8_t rdsr[2] = { EEPROM_AT25_RDSR, 0 }; |
|
uint8_t sr[2]; |
|
int err; |
|
const struct spi_buf tx_buf = { |
|
.buf = rdsr, |
|
.len = sizeof(rdsr), |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1, |
|
}; |
|
const struct spi_buf rx_buf = { |
|
.buf = sr, |
|
.len = sizeof(sr), |
|
}; |
|
const struct spi_buf_set rx = { |
|
.buffers = &rx_buf, |
|
.count = 1, |
|
}; |
|
|
|
err = spi_transceive_dt(&config->bus.spi, &tx, &rx); |
|
if (!err) { |
|
*status = sr[1]; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int eeprom_at25_wait_for_idle(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
int64_t timeout; |
|
uint8_t status; |
|
int err; |
|
|
|
timeout = k_uptime_get() + config->timeout; |
|
while (1) { |
|
int64_t now = k_uptime_get(); |
|
err = eeprom_at25_rdsr(dev, &status); |
|
if (err) { |
|
LOG_ERR("Could not read status register (err %d)", err); |
|
return err; |
|
} |
|
|
|
if (!(status & EEPROM_AT25_STATUS_WIP)) { |
|
return 0; |
|
} |
|
if (now > timeout) { |
|
break; |
|
} |
|
k_sleep(K_MSEC(1)); |
|
} |
|
|
|
return -EBUSY; |
|
} |
|
|
|
static int eeprom_at25_read(const struct device *dev, off_t offset, void *buf, |
|
size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
struct eeprom_at2x_data *data = dev->data; |
|
size_t cmd_len = 1 + config->addr_width / 8; |
|
uint8_t cmd[4] = { EEPROM_AT25_READ, 0, 0, 0 }; |
|
uint8_t *paddr; |
|
int err; |
|
const struct spi_buf tx_buf = { |
|
.buf = cmd, |
|
.len = cmd_len, |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1, |
|
}; |
|
const struct spi_buf rx_bufs[2] = { |
|
{ |
|
.buf = NULL, |
|
.len = cmd_len, |
|
}, |
|
{ |
|
.buf = buf, |
|
.len = len, |
|
}, |
|
}; |
|
const struct spi_buf_set rx = { |
|
.buffers = rx_bufs, |
|
.count = ARRAY_SIZE(rx_bufs), |
|
}; |
|
|
|
if (!len) { |
|
return 0; |
|
} |
|
|
|
if ((offset + len) > config->size) { |
|
LOG_WRN("attempt to read past device boundary"); |
|
return -EINVAL; |
|
} |
|
|
|
paddr = &cmd[1]; |
|
switch (config->addr_width) { |
|
case 24: |
|
*paddr++ = offset >> 16; |
|
__fallthrough; |
|
case 16: |
|
*paddr++ = offset >> 8; |
|
__fallthrough; |
|
case 8: |
|
*paddr++ = offset; |
|
break; |
|
default: |
|
__ASSERT(0, "invalid address width"); |
|
} |
|
|
|
err = eeprom_at25_wait_for_idle(dev); |
|
if (err) { |
|
LOG_ERR("EEPROM idle wait failed (err %d)", err); |
|
k_mutex_unlock(&data->lock); |
|
return err; |
|
} |
|
|
|
err = spi_transceive_dt(&config->bus.spi, &tx, &rx); |
|
if (err < 0) { |
|
return err; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
static int eeprom_at25_wren(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
uint8_t cmd = EEPROM_AT25_WREN; |
|
const struct spi_buf tx_buf = { |
|
.buf = &cmd, |
|
.len = 1, |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1, |
|
}; |
|
|
|
return spi_write_dt(&config->bus.spi, &tx); |
|
} |
|
|
|
static int eeprom_at25_write(const struct device *dev, off_t offset, |
|
const void *buf, size_t len) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
int count = eeprom_at2x_limit_write_count(dev, offset, len); |
|
uint8_t cmd[4] = { EEPROM_AT25_WRITE, 0, 0, 0 }; |
|
size_t cmd_len = 1 + config->addr_width / 8; |
|
uint8_t *paddr; |
|
int err; |
|
const struct spi_buf tx_bufs[2] = { |
|
{ |
|
.buf = cmd, |
|
.len = cmd_len, |
|
}, |
|
{ |
|
.buf = (void *)buf, |
|
.len = count, |
|
}, |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = tx_bufs, |
|
.count = ARRAY_SIZE(tx_bufs), |
|
}; |
|
|
|
paddr = &cmd[1]; |
|
switch (config->addr_width) { |
|
case 24: |
|
*paddr++ = offset >> 16; |
|
__fallthrough; |
|
case 16: |
|
*paddr++ = offset >> 8; |
|
__fallthrough; |
|
case 8: |
|
*paddr++ = offset; |
|
break; |
|
default: |
|
__ASSERT(0, "invalid address width"); |
|
} |
|
|
|
err = eeprom_at25_wait_for_idle(dev); |
|
if (err) { |
|
LOG_ERR("EEPROM idle wait failed (err %d)", err); |
|
return err; |
|
} |
|
|
|
err = eeprom_at25_wren(dev); |
|
if (err) { |
|
LOG_ERR("failed to disable write protection (err %d)", err); |
|
return err; |
|
} |
|
|
|
err = spi_transceive_dt(&config->bus.spi, &tx, NULL); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
return count; |
|
} |
|
#endif /* CONFIG_EEPROM_AT25 */ |
|
|
|
static int eeprom_at2x_init(const struct device *dev) |
|
{ |
|
const struct eeprom_at2x_config *config = dev->config; |
|
struct eeprom_at2x_data *data = dev->data; |
|
|
|
k_mutex_init(&data->lock); |
|
|
|
if (!config->bus_is_ready(dev)) { |
|
LOG_ERR("parent bus device not ready"); |
|
return -EINVAL; |
|
} |
|
|
|
#if ANY_INST_HAS_WP_GPIOS |
|
if (config->wp_gpio.port) { |
|
int err; |
|
if (!device_is_ready(config->wp_gpio.port)) { |
|
LOG_ERR("wp gpio device not ready"); |
|
return -EINVAL; |
|
} |
|
|
|
err = gpio_pin_configure_dt(&config->wp_gpio, GPIO_OUTPUT_ACTIVE); |
|
if (err) { |
|
LOG_ERR("failed to configure WP GPIO pin (err %d)", err); |
|
return err; |
|
} |
|
} |
|
#endif /* ANY_INST_HAS_WP_GPIOS */ |
|
|
|
return 0; |
|
} |
|
|
|
static const struct eeprom_driver_api eeprom_at2x_api = { |
|
.read = eeprom_at2x_read, |
|
.write = eeprom_at2x_write, |
|
.size = eeprom_at2x_size, |
|
}; |
|
|
|
#define ASSERT_AT24_ADDR_W_VALID(w) \ |
|
BUILD_ASSERT(w == 8U || w == 16U, \ |
|
"Unsupported address width") |
|
|
|
#define ASSERT_AT25_ADDR_W_VALID(w) \ |
|
BUILD_ASSERT(w == 8U || w == 16U || w == 24U, \ |
|
"Unsupported address width") |
|
|
|
#define ASSERT_PAGESIZE_IS_POWER_OF_2(page) \ |
|
BUILD_ASSERT((page != 0U) && ((page & (page - 1)) == 0U), \ |
|
"Page size is not a power of two") |
|
|
|
#define ASSERT_SIZE_PAGESIZE_VALID(size, page) \ |
|
BUILD_ASSERT(size % page == 0U, \ |
|
"Size is not an integer multiple of page size") |
|
|
|
#define INST_DT_AT2X(inst, t) DT_INST(inst, atmel_at##t) |
|
|
|
#define EEPROM_AT24_BUS(n, t) \ |
|
{ .i2c = I2C_DT_SPEC_GET(INST_DT_AT2X(n, t)) } |
|
|
|
#define EEPROM_AT25_BUS(n, t) \ |
|
{ .spi = SPI_DT_SPEC_GET(INST_DT_AT2X(n, t), \ |
|
SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \ |
|
SPI_WORD_SET(8), 0) } |
|
|
|
#define EEPROM_AT2X_WP_GPIOS(id) \ |
|
IF_ENABLED(DT_NODE_HAS_PROP(id, wp_gpios), \ |
|
(.wp_gpio = GPIO_DT_SPEC_GET(id, wp_gpios),)) |
|
|
|
#define EEPROM_AT2X_DEVICE(n, t) \ |
|
ASSERT_PAGESIZE_IS_POWER_OF_2(DT_PROP(INST_DT_AT2X(n, t), pagesize)); \ |
|
ASSERT_SIZE_PAGESIZE_VALID(DT_PROP(INST_DT_AT2X(n, t), size), \ |
|
DT_PROP(INST_DT_AT2X(n, t), pagesize)); \ |
|
ASSERT_AT##t##_ADDR_W_VALID(DT_PROP(INST_DT_AT2X(n, t), \ |
|
address_width)); \ |
|
static const struct eeprom_at2x_config eeprom_at##t##_config_##n = { \ |
|
.bus = EEPROM_AT##t##_BUS(n, t), \ |
|
EEPROM_AT2X_WP_GPIOS(INST_DT_AT2X(n, t)) \ |
|
.size = DT_PROP(INST_DT_AT2X(n, t), size), \ |
|
.pagesize = DT_PROP(INST_DT_AT2X(n, t), pagesize), \ |
|
.addr_width = DT_PROP(INST_DT_AT2X(n, t), address_width), \ |
|
.readonly = DT_PROP(INST_DT_AT2X(n, t), read_only), \ |
|
.timeout = DT_PROP(INST_DT_AT2X(n, t), timeout), \ |
|
.bus_is_ready = eeprom_at##t##_bus_is_ready, \ |
|
.read_fn = eeprom_at##t##_read, \ |
|
.write_fn = eeprom_at##t##_write, \ |
|
}; \ |
|
static struct eeprom_at2x_data eeprom_at##t##_data_##n; \ |
|
DEVICE_DT_DEFINE(INST_DT_AT2X(n, t), &eeprom_at2x_init, \ |
|
NULL, &eeprom_at##t##_data_##n, \ |
|
&eeprom_at##t##_config_##n, POST_KERNEL, \ |
|
CONFIG_EEPROM_INIT_PRIORITY, \ |
|
&eeprom_at2x_api) |
|
|
|
#define EEPROM_AT24_DEVICE(n) EEPROM_AT2X_DEVICE(n, 24) |
|
#define EEPROM_AT25_DEVICE(n) EEPROM_AT2X_DEVICE(n, 25) |
|
|
|
#define CALL_WITH_ARG(arg, expr) expr(arg); |
|
|
|
#define INST_DT_AT2X_FOREACH(t, inst_expr) \ |
|
LISTIFY(DT_NUM_INST_STATUS_OKAY(atmel_at##t), \ |
|
CALL_WITH_ARG, (), inst_expr) |
|
|
|
#ifdef CONFIG_EEPROM_AT24 |
|
INST_DT_AT2X_FOREACH(24, EEPROM_AT24_DEVICE); |
|
#endif |
|
|
|
#ifdef CONFIG_EEPROM_AT25 |
|
INST_DT_AT2X_FOREACH(25, EEPROM_AT25_DEVICE); |
|
#endif
|
|
|