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