/* * Copyright 2020 Google LLC * Copyright (c) 2020 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_at24 #define LOG_LEVEL CONFIG_I2C_LOG_LEVEL #include LOG_MODULE_REGISTER(atmel_at24); #include #include #include #include /** Run-time data used by the emulator */ struct at24_emul_data { /** I2C emulator detail */ struct i2c_emul emul; /** AT24 device being emulated */ const struct device *i2c; /** Current register to read (address) */ uint32_t cur_reg; }; /** Static configuration for the emulator */ struct at24_emul_cfg { /** EEPROM data contents */ uint8_t *buf; /** Size of EEPROM in bytes */ uint32_t size; /** Address of EEPROM on i2c bus */ uint16_t addr; /** Address width for EEPROM in bits (only 8 is supported at present) */ uint8_t addr_width; }; /** * Emulator an I2C transfer to an AT24 chip * * This handles simple reads and writes * * @param emul I2C emulation information * @param msgs List of messages to process. For 'read' messages, this function * updates the 'buf' member with the data that was read * @param num_msgs Number of messages to process * @param addr Address of the I2C target device. This is assumed to be correct, * due to the * @retval 0 If successful * @retval -EIO General input / output error */ static int at24_emul_transfer(const struct emul *target, struct i2c_msg *msgs, int num_msgs, int addr) { struct at24_emul_data *data; const struct at24_emul_cfg *cfg; unsigned int len; bool too_fast; uint32_t i2c_cfg; data = target->data; cfg = target->cfg; if (cfg->addr != addr) { LOG_ERR("Address mismatch, expected %02x, got %02x", cfg->addr, addr); return -EIO; } if (i2c_get_config(data->i2c, &i2c_cfg)) { LOG_ERR("i2c_get_config failed"); return -EIO; } /* For testing purposes, fail if the bus speed is above standard */ too_fast = (I2C_SPEED_GET(i2c_cfg) > I2C_SPEED_STANDARD); if (too_fast) { LOG_ERR("Speed too high"); return -EIO; } i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false); switch (num_msgs) { case 1: if (msgs->flags & I2C_MSG_READ) { /* handle read */ break; } data->cur_reg = msgs->buf[0]; len = MIN(msgs->len - 1, cfg->size - data->cur_reg); memcpy(&cfg->buf[data->cur_reg], &msgs->buf[1], len); return 0; case 2: if (msgs->flags & I2C_MSG_READ) { LOG_ERR("Unexpected read"); return -EIO; } data->cur_reg = msgs->buf[0]; /* Now process the 'read' part of the message */ msgs++; if (!(msgs->flags & I2C_MSG_READ)) { LOG_ERR("Unexpected write"); return -EIO; } break; default: LOG_ERR("Invalid number of messages"); return -EIO; } /* Read data from the EEPROM into the buffer */ len = MIN(msgs->len, cfg->size - data->cur_reg); memcpy(msgs->buf, &cfg->buf[data->cur_reg], len); data->cur_reg += len; return 0; } /* Device instantiation */ static struct i2c_emul_api bus_api = { .transfer = at24_emul_transfer, }; /** * Set up a new AT24 emulator * * This should be called for each AT24 device that needs to be emulated. It * registers it with the I2C emulation controller. * * @param target Emulation information * @param parent Device to emulate (must use AT24 driver) * @return 0 indicating success (always) */ static int emul_atmel_at24_init(const struct emul *target, const struct device *parent) { const struct at24_emul_cfg *cfg = target->cfg; struct at24_emul_data *data = target->data; data->emul.api = &bus_api; data->emul.addr = cfg->addr; data->emul.target = target; data->i2c = parent; data->cur_reg = 0; /* Start with an erased EEPROM, assuming all 0xff */ memset(cfg->buf, 0xff, cfg->size); return 0; } #define EEPROM_AT24_EMUL(n) \ static uint8_t at24_emul_buf_##n[DT_INST_PROP(n, size)]; \ static struct at24_emul_data at24_emul_data_##n; \ static const struct at24_emul_cfg at24_emul_cfg_##n = { \ .buf = at24_emul_buf_##n, \ .size = DT_INST_PROP(n, size), \ .addr = DT_INST_REG_ADDR(n), \ .addr_width = 8, \ }; \ EMUL_DT_INST_DEFINE(n, emul_atmel_at24_init, &at24_emul_data_##n, &at24_emul_cfg_##n, \ &bus_api, NULL) DT_INST_FOREACH_STATUS_OKAY(EEPROM_AT24_EMUL)