/* 
 | 
 * Simulate an I2C eeprom 
 | 
 * 
 | 
 * Copyright (c) 2014 Google, Inc 
 | 
 * 
 | 
 * SPDX-License-Identifier:    GPL-2.0+ 
 | 
 */ 
 | 
  
 | 
#include <common.h> 
 | 
#include <dm.h> 
 | 
#include <errno.h> 
 | 
#include <i2c.h> 
 | 
#include <malloc.h> 
 | 
#include <asm/test.h> 
 | 
  
 | 
#ifdef DEBUG 
 | 
#define debug_buffer print_buffer 
 | 
#else 
 | 
#define debug_buffer(x, ...) 
 | 
#endif 
 | 
  
 | 
DECLARE_GLOBAL_DATA_PTR; 
 | 
  
 | 
struct sandbox_i2c_flash_plat_data { 
 | 
    enum sandbox_i2c_eeprom_test_mode test_mode; 
 | 
    const char *filename; 
 | 
    int offset_len;        /* Length of an offset in bytes */ 
 | 
    int size;        /* Size of data buffer */ 
 | 
}; 
 | 
  
 | 
struct sandbox_i2c_flash { 
 | 
    uint8_t *data; 
 | 
}; 
 | 
  
 | 
void sandbox_i2c_eeprom_set_test_mode(struct udevice *dev, 
 | 
                      enum sandbox_i2c_eeprom_test_mode mode) 
 | 
{ 
 | 
    struct sandbox_i2c_flash_plat_data *plat = dev_get_platdata(dev); 
 | 
  
 | 
    plat->test_mode = mode; 
 | 
} 
 | 
  
 | 
void sandbox_i2c_eeprom_set_offset_len(struct udevice *dev, int offset_len) 
 | 
{ 
 | 
    struct sandbox_i2c_flash_plat_data *plat = dev_get_platdata(dev); 
 | 
  
 | 
    plat->offset_len = offset_len; 
 | 
} 
 | 
  
 | 
static int sandbox_i2c_eeprom_xfer(struct udevice *emul, struct i2c_msg *msg, 
 | 
                  int nmsgs) 
 | 
{ 
 | 
    struct sandbox_i2c_flash *priv = dev_get_priv(emul); 
 | 
    uint offset = 0; 
 | 
  
 | 
    debug("\n%s\n", __func__); 
 | 
    debug_buffer(0, priv->data, 1, 16, 0); 
 | 
    for (; nmsgs > 0; nmsgs--, msg++) { 
 | 
        struct sandbox_i2c_flash_plat_data *plat = 
 | 
                dev_get_platdata(emul); 
 | 
        int len; 
 | 
        u8 *ptr; 
 | 
  
 | 
        if (!plat->size) 
 | 
            return -ENODEV; 
 | 
        if (msg->addr + msg->len > plat->size) { 
 | 
            debug("%s: Address %x, len %x is outside range 0..%x\n", 
 | 
                  __func__, msg->addr, msg->len, plat->size); 
 | 
            return -EINVAL; 
 | 
        } 
 | 
        len = msg->len; 
 | 
        debug("   %s: msg->len=%d", 
 | 
              msg->flags & I2C_M_RD ? "read" : "write", 
 | 
              msg->len); 
 | 
        if (msg->flags & I2C_M_RD) { 
 | 
            if (plat->test_mode == SIE_TEST_MODE_SINGLE_BYTE) 
 | 
                len = 1; 
 | 
            debug(", offset %x, len %x: ", offset, len); 
 | 
            memcpy(msg->buf, priv->data + offset, len); 
 | 
            memset(msg->buf + len, '\xff', msg->len - len); 
 | 
            debug_buffer(0, msg->buf, 1, msg->len, 0); 
 | 
        } else if (len >= plat->offset_len) { 
 | 
            int i; 
 | 
  
 | 
            ptr = msg->buf; 
 | 
            for (i = 0; i < plat->offset_len; i++, len--) 
 | 
                offset = (offset << 8) | *ptr++; 
 | 
            debug(", set offset %x: ", offset); 
 | 
            debug_buffer(0, msg->buf, 1, msg->len, 0); 
 | 
            if (plat->test_mode == SIE_TEST_MODE_SINGLE_BYTE) 
 | 
                len = min(len, 1); 
 | 
  
 | 
            /* For testing, map offsets into our limited buffer */ 
 | 
            for (i = 24; i > 0; i -= 8) { 
 | 
                if (offset > (1 << i)) { 
 | 
                    offset = (offset >> i) | 
 | 
                        (offset & ((1 << i) - 1)); 
 | 
                    offset += i; 
 | 
                } 
 | 
            } 
 | 
            memcpy(priv->data + offset, ptr, len); 
 | 
        } 
 | 
    } 
 | 
    debug_buffer(0, priv->data, 1, 16, 0); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
struct dm_i2c_ops sandbox_i2c_emul_ops = { 
 | 
    .xfer = sandbox_i2c_eeprom_xfer, 
 | 
}; 
 | 
  
 | 
static int sandbox_i2c_eeprom_ofdata_to_platdata(struct udevice *dev) 
 | 
{ 
 | 
    struct sandbox_i2c_flash_plat_data *plat = dev_get_platdata(dev); 
 | 
  
 | 
    plat->size = dev_read_u32_default(dev, "sandbox,size", 32); 
 | 
    plat->filename = dev_read_string(dev, "sandbox,filename"); 
 | 
    if (!plat->filename) { 
 | 
        debug("%s: No filename for device '%s'\n", __func__, 
 | 
              dev->name); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
    plat->test_mode = SIE_TEST_MODE_NONE; 
 | 
    plat->offset_len = 1; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sandbox_i2c_eeprom_probe(struct udevice *dev) 
 | 
{ 
 | 
    struct sandbox_i2c_flash_plat_data *plat = dev_get_platdata(dev); 
 | 
    struct sandbox_i2c_flash *priv = dev_get_priv(dev); 
 | 
  
 | 
    priv->data = calloc(1, plat->size); 
 | 
    if (!priv->data) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sandbox_i2c_eeprom_remove(struct udevice *dev) 
 | 
{ 
 | 
    struct sandbox_i2c_flash *priv = dev_get_priv(dev); 
 | 
  
 | 
    free(priv->data); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct udevice_id sandbox_i2c_ids[] = { 
 | 
    { .compatible = "sandbox,i2c-eeprom" }, 
 | 
    { } 
 | 
}; 
 | 
  
 | 
U_BOOT_DRIVER(sandbox_i2c_emul) = { 
 | 
    .name        = "sandbox_i2c_eeprom_emul", 
 | 
    .id        = UCLASS_I2C_EMUL, 
 | 
    .of_match    = sandbox_i2c_ids, 
 | 
    .ofdata_to_platdata = sandbox_i2c_eeprom_ofdata_to_platdata, 
 | 
    .probe        = sandbox_i2c_eeprom_probe, 
 | 
    .remove        = sandbox_i2c_eeprom_remove, 
 | 
    .priv_auto_alloc_size = sizeof(struct sandbox_i2c_flash), 
 | 
    .platdata_auto_alloc_size = sizeof(struct sandbox_i2c_flash_plat_data), 
 | 
    .ops        = &sandbox_i2c_emul_ops, 
 | 
}; 
 |