[PATCH 2/3] i2c: slave-eeprom: add eeprom simulator driver

Stijn Devriendt highguy at gmail.com
Thu Nov 20 14:39:08 PST 2014


On Tue, Nov 18, 2014 at 5:04 PM, Wolfram Sang <wsa at the-dreams.de> wrote:
> From: Wolfram Sang <wsa+renesas at sang-engineering.com>
>
> The first user of the i2c-slave interface is an eeprom simulator. It is
> a shared memory which can be accessed by the remote master via I2C and
> locally via sysfs.
>
> Signed-off-by: Wolfram Sang <wsa+renesas at sang-engineering.com>
> ---
>
> Changes since RFC:
> * fix build error for modules
> * don't hardcode size
> * add boundary checks for sysfs access
> * use a spinlock
> * s/at24s/eeprom/g
> * add some docs
> * clean up exit paths
> * use size-variable instead of driver_data
>
>  drivers/i2c/Kconfig            |  10 +++
>  drivers/i2c/Makefile           |   1 +
>  drivers/i2c/i2c-slave-eeprom.c | 170 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 181 insertions(+)
>  create mode 100644 drivers/i2c/i2c-slave-eeprom.c
>
> diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
> index b51a402752c4..8c9e619f3026 100644
> --- a/drivers/i2c/Kconfig
> +++ b/drivers/i2c/Kconfig
> @@ -110,6 +110,16 @@ config I2C_STUB
>
>           If you don't know what to do here, definitely say N.
>
> +config I2C_SLAVE
> +       bool "I2C slave support"
> +
> +if I2C_SLAVE
> +
> +config I2C_SLAVE_EEPROM
> +       tristate "I2C eeprom slave driver"
> +
> +endif
> +
>  config I2C_DEBUG_CORE
>         bool "I2C Core debugging messages"
>         help
> diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
> index 1722f50f2473..45095b3d16a9 100644
> --- a/drivers/i2c/Makefile
> +++ b/drivers/i2c/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_I2C_CHARDEV)       += i2c-dev.o
>  obj-$(CONFIG_I2C_MUX)          += i2c-mux.o
>  obj-y                          += algos/ busses/ muxes/
>  obj-$(CONFIG_I2C_STUB)         += i2c-stub.o
> +obj-$(CONFIG_I2C_SLAVE_EEPROM) += i2c-slave-eeprom.o
>
>  ccflags-$(CONFIG_I2C_DEBUG_CORE) := -DDEBUG
>  CFLAGS_i2c-core.o := -Wno-deprecated-declarations
> diff --git a/drivers/i2c/i2c-slave-eeprom.c b/drivers/i2c/i2c-slave-eeprom.c
> new file mode 100644
> index 000000000000..6631400b5f02
> --- /dev/null
> +++ b/drivers/i2c/i2c-slave-eeprom.c
> @@ -0,0 +1,170 @@
> +/*
> + * I2C slave mode EEPROM simulator
> + *
> + * Copyright (C) 2014 by Wolfram Sang, Sang Engineering <wsa at sang-engineering.com>
> + * Copyright (C) 2014 by Renesas Electronics Corporation
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; version 2 of the License.
> + *
> + * Because most IP blocks can only detect one I2C slave address anyhow, this
> + * driver does not support simulating EEPROM types which take more than one
> + * address. It is prepared to simulate bigger EEPROMs with an internal 16 bit
> + * pointer, yet implementation is deferred until the need actually arises.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/sysfs.h>
> +
> +struct eeprom_data {
> +       struct bin_attribute bin;
> +       bool first_write;
> +       spinlock_t buffer_lock;
> +       u8 buffer_idx;
> +       u8 buffer[];
> +};
> +
> +static int i2c_slave_eeprom_slave_cb(struct i2c_client *client,
> +                                    enum i2c_slave_event event, u8 *val)
> +{
> +       struct eeprom_data *eeprom = i2c_get_clientdata(client);
> +
> +       switch (event) {
> +       case I2C_SLAVE_REQ_WRITE_END:
> +               if (eeprom->first_write) {
> +                       eeprom->buffer_idx = *val;
> +                       eeprom->first_write = false;
> +               } else {
> +                       spin_lock(&eeprom->buffer_lock);
> +                       eeprom->buffer[eeprom->buffer_idx++] = *val;
> +                       spin_unlock(&eeprom->buffer_lock);
> +               }
> +               break;
> +
> +       case I2C_SLAVE_REQ_READ_START:
> +               spin_lock(&eeprom->buffer_lock);
> +               *val = eeprom->buffer[eeprom->buffer_idx];
> +               spin_unlock(&eeprom->buffer_lock);
> +               break;
> +
> +       case I2C_SLAVE_REQ_READ_END:
> +               eeprom->buffer_idx++;
> +               break;
> +
> +       case I2C_SLAVE_STOP:
> +               eeprom->first_write = true;
> +               break;
> +
> +       default:
> +               break;
> +       }
> +
> +       return 0;
> +}


Would it make sense to have:
WRITE_START
WRITE_NEXT
WRITE_STOP
WRITE_REPEAT_START
READ_START
READ_NEXT
READ_STOP
READ_REPEAT_START

Some devices may want different behavior for subsequent
reads when they are separate transactions, compared to
a single larger transaction.
e.g. a single transaction may wraparound inside a >8bit
register (e.g. for 16bit: msb, lsb, msb, lsb, ...), but step
to the next register when a separate read/write is issued.
Alternatively, a WRITE/READ_NEXT may be implemented
more efficiently. This may not matter for current systems
compared to the low-frequency bus, but who knows what
IoT devices may bring to the table.

Also, behavior may be different for repeat start versus
stop/start, although a repeat start could be a start
without a previous stop as well...

Of course, if an I2C adapter cannot distinguish these
events, this is probably a futile attempt at adding
semantics that will silently break depending on the
actual hardware/driver used.

Regards,
Stijn

> +
> +static ssize_t i2c_slave_eeprom_bin_read(struct file *filp, struct kobject *kobj,
> +               struct bin_attribute *attr, char *buf, loff_t off, size_t count)
> +{
> +       struct eeprom_data *eeprom;
> +       unsigned long flags;
> +
> +       if (off + count >= attr->size)
> +               return -EFBIG;
> +
> +       eeprom = dev_get_drvdata(container_of(kobj, struct device, kobj));
> +
> +       spin_lock_irqsave(&eeprom->buffer_lock, flags);
> +       memcpy(buf, &eeprom->buffer[off], count);
> +       spin_unlock_irqrestore(&eeprom->buffer_lock, flags);
> +
> +       return count;
> +}
> +
> +static ssize_t i2c_slave_eeprom_bin_write(struct file *filp, struct kobject *kobj,
> +               struct bin_attribute *attr, char *buf, loff_t off, size_t count)
> +{
> +       struct eeprom_data *eeprom;
> +       unsigned long flags;
> +
> +       if (off + count >= attr->size)
> +               return -EFBIG;
> +
> +       eeprom = dev_get_drvdata(container_of(kobj, struct device, kobj));
> +
> +       spin_lock_irqsave(&eeprom->buffer_lock, flags);
> +       memcpy(&eeprom->buffer[off], buf, count);
> +       spin_unlock_irqrestore(&eeprom->buffer_lock, flags);
> +
> +       return count;
> +}
> +
> +static int i2c_slave_eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +       struct eeprom_data *eeprom;
> +       int ret;
> +       unsigned size = id->driver_data;
> +
> +       eeprom = devm_kzalloc(&client->dev, sizeof(struct eeprom_data) + size, GFP_KERNEL);
> +       if (!eeprom)
> +               return -ENOMEM;
> +
> +       eeprom->first_write = true;
> +       spin_lock_init(&eeprom->buffer_lock);
> +       i2c_set_clientdata(client, eeprom);
> +
> +       sysfs_bin_attr_init(&eeprom->bin);
> +       eeprom->bin.attr.name = "slave-eeprom";
> +       eeprom->bin.attr.mode = S_IRUSR | S_IWUSR;
> +       eeprom->bin.read = i2c_slave_eeprom_bin_read;
> +       eeprom->bin.write = i2c_slave_eeprom_bin_write;
> +       eeprom->bin.size = size;
> +
> +       ret = sysfs_create_bin_file(&client->dev.kobj, &eeprom->bin);
> +       if (ret)
> +               return ret;
> +
> +       ret = i2c_slave_register(client, i2c_slave_eeprom_slave_cb);
> +       if (ret) {
> +               sysfs_remove_bin_file(&client->dev.kobj, &eeprom->bin);
> +               return ret;
> +       }
> +
> +       return 0;
> +};
> +
> +static int i2c_slave_eeprom_remove(struct i2c_client *client)
> +{
> +       struct eeprom_data *eeprom = i2c_get_clientdata(client);
> +
> +       i2c_slave_unregister(client);
> +       sysfs_remove_bin_file(&client->dev.kobj, &eeprom->bin);
> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id i2c_slave_eeprom_id[] = {
> +       { "slave-24c02", 2048 / 8 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(i2c, i2c_slave_eeprom_id);
> +
> +static struct i2c_driver i2c_slave_eeprom_driver = {
> +       .driver = {
> +               .name = "i2c-slave-eeprom",
> +               .owner = THIS_MODULE,
> +       },
> +       .probe = i2c_slave_eeprom_probe,
> +       .remove = i2c_slave_eeprom_remove,
> +       .id_table = i2c_slave_eeprom_id,
> +};
> +module_i2c_driver(i2c_slave_eeprom_driver);
> +
> +MODULE_AUTHOR("Wolfram Sang <wsa at sang-engineering.com>");
> +MODULE_DESCRIPTION("I2C slave mode EEPROM simulator");
> +MODULE_LICENSE("GPL v2");
> --
> 2.1.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/



More information about the linux-arm-kernel mailing list