/* * Copyright (c) 2023 Antmicro * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT fujitsu_mb85rcxx #include #include #include #include #include #include #include LOG_MODULE_REGISTER(EEPROM_MB85RCXX, CONFIG_EEPROM_LOG_LEVEL); struct mb85rcxx_config { struct i2c_dt_spec i2c; struct gpio_dt_spec wp_gpio; size_t size; size_t pagesize; uint8_t addr_width; bool readonly; }; struct mb85rcxx_data { struct k_mutex lock; }; static int mb85rcxx_write_protect_set(const struct device *dev, int value) { const struct mb85rcxx_config *cfg = dev->config; if (!cfg->wp_gpio.port) { return 0; } return gpio_pin_set_dt(&cfg->wp_gpio, value); } static uint16_t mb85rcxx_translate_address(const struct device *dev, off_t offset, uint8_t *addr) { const struct mb85rcxx_config *cfg = dev->config; off_t page_offset = offset % cfg->pagesize; if (cfg->addr_width > 8) { sys_put_be16(page_offset, addr); addr[0] &= BIT_MASK(cfg->addr_width - 8); } else { addr[0] = page_offset & BIT_MASK(cfg->addr_width); } return cfg->i2c.addr + (offset >> cfg->addr_width); } static size_t mb85rcxx_remaining_len_in_page(const struct device *dev, off_t offset, size_t len) { const struct mb85rcxx_config *cfg = dev->config; off_t page_offset = offset % cfg->pagesize; size_t rem = cfg->pagesize - page_offset; if (rem > len) { rem = len; } return rem; } static int mb85rcxx_init(const struct device *dev) { const struct mb85rcxx_config *cfg = dev->config; struct mb85rcxx_data *data = dev->data; k_mutex_init(&data->lock); if (!i2c_is_ready_dt(&cfg->i2c)) { LOG_ERR("i2c bus device not ready"); return -EINVAL; } if (cfg->wp_gpio.port) { if (!gpio_is_ready_dt(&cfg->wp_gpio)) { LOG_ERR("wp gpio device not ready"); return -EINVAL; } int err = gpio_pin_configure_dt(&cfg->wp_gpio, GPIO_OUTPUT_ACTIVE); if (err) { LOG_ERR("failed to configure WP GPIO pin (err %d)", err); return err; } } return 0; } static int mb85rcxx_read(const struct device *dev, off_t offset, void *buf, size_t len) { const struct mb85rcxx_config *cfg = dev->config; struct mb85rcxx_data *data = dev->data; uint8_t addr[2]; uint16_t i2c_addr; size_t len_in_page; int ret; if (offset + len > cfg->size) { LOG_ERR("attempt to read past device boundary"); return -EINVAL; } k_mutex_lock(&data->lock, K_FOREVER); while (len) { i2c_addr = mb85rcxx_translate_address(dev, offset, addr); len_in_page = mb85rcxx_remaining_len_in_page(dev, offset, len); ret = i2c_write_read(cfg->i2c.bus, i2c_addr, addr, DIV_ROUND_UP(cfg->addr_width, 8), buf, len_in_page); if (ret < 0) { LOG_ERR("failed to read FRAM (err %d)", ret); k_mutex_unlock(&data->lock); return ret; } len -= len_in_page; *(char *)&buf += len_in_page; offset += len_in_page; } k_mutex_unlock(&data->lock); return 0; } static int mb85rcxx_i2c_write(const struct device *dev, uint16_t i2c_addr, uint8_t *addr, const void *buf, size_t len) { const struct mb85rcxx_config *cfg = dev->config; struct i2c_msg msgs[2]; msgs[0].buf = addr; msgs[0].len = DIV_ROUND_UP(cfg->addr_width, 8); msgs[0].flags = I2C_MSG_WRITE; msgs[1].buf = (void *)buf; msgs[1].len = len; msgs[1].flags = I2C_MSG_WRITE | I2C_MSG_STOP; return i2c_transfer(cfg->i2c.bus, &msgs[0], 2, i2c_addr); } static int mb85rcxx_write(const struct device *dev, off_t offset, const void *buf, size_t len) { const struct mb85rcxx_config *cfg = dev->config; struct mb85rcxx_data *data = dev->data; uint8_t addr[2]; uint16_t i2c_addr; size_t len_in_page; int ret; if (cfg->readonly) { LOG_ERR("attempt to write to read-only device"); return -EACCES; } if (offset + len > cfg->size) { LOG_ERR("attempt to write past device boundary"); return -EINVAL; } ret = mb85rcxx_write_protect_set(dev, 0); if (ret) { LOG_ERR("failed to write-enable FRAM (err %d)", ret); return ret; } k_mutex_lock(&data->lock, K_FOREVER); while (len) { i2c_addr = mb85rcxx_translate_address(dev, offset, addr); len_in_page = mb85rcxx_remaining_len_in_page(dev, offset, len); ret = mb85rcxx_i2c_write(dev, i2c_addr, addr, buf, len); if (ret < 0) { LOG_ERR("failed to write to FRAM (err %d)", ret); k_mutex_unlock(&data->lock); return ret; } len -= len_in_page; *(char *)&buf += len_in_page; offset += len_in_page; } k_mutex_unlock(&data->lock); mb85rcxx_write_protect_set(dev, 1); return ret; } static size_t mb85rcxx_get_size(const struct device *dev) { const struct mb85rcxx_config *cfg = dev->config; return cfg->size; } static const struct eeprom_driver_api mb85rcxx_driver_api = { .read = &mb85rcxx_read, .write = &mb85rcxx_write, .size = &mb85rcxx_get_size, }; #define MB85RCXX_DEFINE(inst) \ static struct mb85rcxx_data mb85rcxx_data_##inst; \ \ static const struct mb85rcxx_config mb85rcxx_config_##inst = { \ .i2c = I2C_DT_SPEC_INST_GET(inst), \ IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, wp_gpios), \ (.wp_gpio = GPIO_DT_SPEC_INST_GET(inst, wp_gpios),)) \ .size = DT_INST_PROP(inst, size), \ .pagesize = \ COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, pagesize), \ (DT_INST_PROP(inst, pagesize)), (DT_INST_PROP(inst, size))), \ .addr_width = DT_INST_PROP(inst, address_width), \ .readonly = DT_INST_PROP(inst, read_only)}; \ \ DEVICE_DT_INST_DEFINE(inst, mb85rcxx_init, NULL, &mb85rcxx_data_##inst, \ &mb85rcxx_config_##inst, POST_KERNEL, CONFIG_EEPROM_INIT_PRIORITY, \ &mb85rcxx_driver_api); DT_INST_FOREACH_STATUS_OKAY(MB85RCXX_DEFINE)