| /* |
| * 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 <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(atmel_at24); |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/emul.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/i2c_emul.h> |
| |
| /** 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) |