linux/drivers/i2c/i2c-slave-eeprom.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * I2C slave mode EEPROM simulator
   4 *
   5 * Copyright (C) 2014 by Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
   6 * Copyright (C) 2014 by Renesas Electronics Corporation
   7 *
   8 * Because most IP blocks can only detect one I2C slave address anyhow, this
   9 * driver does not support simulating EEPROM types which take more than one
  10 * address. It is prepared to simulate bigger EEPROMs with an internal 16 bit
  11 * pointer, yet implementation is deferred until the need actually arises.
  12 */
  13
  14#include <linux/i2c.h>
  15#include <linux/init.h>
  16#include <linux/module.h>
  17#include <linux/of.h>
  18#include <linux/slab.h>
  19#include <linux/spinlock.h>
  20#include <linux/sysfs.h>
  21
  22struct eeprom_data {
  23        struct bin_attribute bin;
  24        bool first_write;
  25        spinlock_t buffer_lock;
  26        u8 buffer_idx;
  27        u8 buffer[];
  28};
  29
  30static int i2c_slave_eeprom_slave_cb(struct i2c_client *client,
  31                                     enum i2c_slave_event event, u8 *val)
  32{
  33        struct eeprom_data *eeprom = i2c_get_clientdata(client);
  34
  35        switch (event) {
  36        case I2C_SLAVE_WRITE_RECEIVED:
  37                if (eeprom->first_write) {
  38                        eeprom->buffer_idx = *val;
  39                        eeprom->first_write = false;
  40                } else {
  41                        spin_lock(&eeprom->buffer_lock);
  42                        eeprom->buffer[eeprom->buffer_idx++] = *val;
  43                        spin_unlock(&eeprom->buffer_lock);
  44                }
  45                break;
  46
  47        case I2C_SLAVE_READ_PROCESSED:
  48                /* The previous byte made it to the bus, get next one */
  49                eeprom->buffer_idx++;
  50                /* fallthrough */
  51        case I2C_SLAVE_READ_REQUESTED:
  52                spin_lock(&eeprom->buffer_lock);
  53                *val = eeprom->buffer[eeprom->buffer_idx];
  54                spin_unlock(&eeprom->buffer_lock);
  55                /*
  56                 * Do not increment buffer_idx here, because we don't know if
  57                 * this byte will be actually used. Read Linux I2C slave docs
  58                 * for details.
  59                 */
  60                break;
  61
  62        case I2C_SLAVE_STOP:
  63        case I2C_SLAVE_WRITE_REQUESTED:
  64                eeprom->first_write = true;
  65                break;
  66
  67        default:
  68                break;
  69        }
  70
  71        return 0;
  72}
  73
  74static ssize_t i2c_slave_eeprom_bin_read(struct file *filp, struct kobject *kobj,
  75                struct bin_attribute *attr, char *buf, loff_t off, size_t count)
  76{
  77        struct eeprom_data *eeprom;
  78        unsigned long flags;
  79
  80        eeprom = dev_get_drvdata(container_of(kobj, struct device, kobj));
  81
  82        spin_lock_irqsave(&eeprom->buffer_lock, flags);
  83        memcpy(buf, &eeprom->buffer[off], count);
  84        spin_unlock_irqrestore(&eeprom->buffer_lock, flags);
  85
  86        return count;
  87}
  88
  89static ssize_t i2c_slave_eeprom_bin_write(struct file *filp, struct kobject *kobj,
  90                struct bin_attribute *attr, char *buf, loff_t off, size_t count)
  91{
  92        struct eeprom_data *eeprom;
  93        unsigned long flags;
  94
  95        eeprom = dev_get_drvdata(container_of(kobj, struct device, kobj));
  96
  97        spin_lock_irqsave(&eeprom->buffer_lock, flags);
  98        memcpy(&eeprom->buffer[off], buf, count);
  99        spin_unlock_irqrestore(&eeprom->buffer_lock, flags);
 100
 101        return count;
 102}
 103
 104static int i2c_slave_eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id)
 105{
 106        struct eeprom_data *eeprom;
 107        int ret;
 108        unsigned size = id->driver_data;
 109
 110        eeprom = devm_kzalloc(&client->dev, sizeof(struct eeprom_data) + size, GFP_KERNEL);
 111        if (!eeprom)
 112                return -ENOMEM;
 113
 114        eeprom->first_write = true;
 115        spin_lock_init(&eeprom->buffer_lock);
 116        i2c_set_clientdata(client, eeprom);
 117
 118        sysfs_bin_attr_init(&eeprom->bin);
 119        eeprom->bin.attr.name = "slave-eeprom";
 120        eeprom->bin.attr.mode = S_IRUSR | S_IWUSR;
 121        eeprom->bin.read = i2c_slave_eeprom_bin_read;
 122        eeprom->bin.write = i2c_slave_eeprom_bin_write;
 123        eeprom->bin.size = size;
 124
 125        ret = sysfs_create_bin_file(&client->dev.kobj, &eeprom->bin);
 126        if (ret)
 127                return ret;
 128
 129        ret = i2c_slave_register(client, i2c_slave_eeprom_slave_cb);
 130        if (ret) {
 131                sysfs_remove_bin_file(&client->dev.kobj, &eeprom->bin);
 132                return ret;
 133        }
 134
 135        return 0;
 136};
 137
 138static int i2c_slave_eeprom_remove(struct i2c_client *client)
 139{
 140        struct eeprom_data *eeprom = i2c_get_clientdata(client);
 141
 142        i2c_slave_unregister(client);
 143        sysfs_remove_bin_file(&client->dev.kobj, &eeprom->bin);
 144
 145        return 0;
 146}
 147
 148static const struct i2c_device_id i2c_slave_eeprom_id[] = {
 149        { "slave-24c02", 2048 / 8 },
 150        { }
 151};
 152MODULE_DEVICE_TABLE(i2c, i2c_slave_eeprom_id);
 153
 154static struct i2c_driver i2c_slave_eeprom_driver = {
 155        .driver = {
 156                .name = "i2c-slave-eeprom",
 157        },
 158        .probe = i2c_slave_eeprom_probe,
 159        .remove = i2c_slave_eeprom_remove,
 160        .id_table = i2c_slave_eeprom_id,
 161};
 162module_i2c_driver(i2c_slave_eeprom_driver);
 163
 164MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>");
 165MODULE_DESCRIPTION("I2C slave mode EEPROM simulator");
 166MODULE_LICENSE("GPL v2");
 167