Skip to content

Commit

Permalink
i2c: nvmem: at24: Provide an EEPROM framework interface
Browse files Browse the repository at this point in the history
For DT and in-kernel users there is no interface to the
at24 EEPROMs so provide an NVMEM framework interface.

This allows us to use AT24 based EEPROMs and reference them
from within the DT tree.

Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
  • Loading branch information
pantoniou committed Aug 12, 2015
1 parent bf81603 commit e02880f
Showing 1 changed file with 174 additions and 45 deletions.
219 changes: 174 additions & 45 deletions drivers/misc/eeprom/at24.c
Expand Up @@ -3,6 +3,7 @@
*
* Copyright (C) 2005-2007 David Brownell
* Copyright (C) 2008 Wolfram Sang, Pengutronix
* Copyright (C) 2015 Pantelis Antoniou, Konsulko Group
*
* 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
Expand All @@ -23,6 +24,9 @@
#include <linux/of.h>
#include <linux/i2c.h>
#include <linux/platform_data/at24.h>
#include <linux/regmap.h>
#include <linux/nvmem-provider.h>
#include <linux/io.h>

/*
* I2C EEPROMs from most vendors are inexpensive and mostly interchangeable.
Expand Down Expand Up @@ -63,12 +67,16 @@ struct at24_data {
* but not from changes by other I2C masters.
*/
struct mutex lock;
struct bin_attribute bin;

u8 *writebuf;
unsigned write_max;
unsigned num_addresses;

struct regmap_config *regmap_config;
struct regmap *regmap;
struct nvmem_config *nvmem_config;
struct nvmem_device *nvmem_dev;

/*
* Some chips tie up multiple I2C addresses; dummy devices reserve
* them for us, and we'll use them with SMBus calls.
Expand Down Expand Up @@ -131,6 +139,8 @@ static const struct i2c_device_id at24_ids[] = {
};
MODULE_DEVICE_TABLE(i2c, at24_ids);

static DEFINE_IDA(at24_ida);

/*-------------------------------------------------------------------------*/

/*
Expand Down Expand Up @@ -301,17 +311,6 @@ static ssize_t at24_read(struct at24_data *at24,
return retval;
}

static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct at24_data *at24;

at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
return at24_read(at24, buf, off, count);
}


/*
* Note that if the hardware write-protect pin is pulled high, the whole
* chip is normally write protected. But there are plenty of product
Expand Down Expand Up @@ -432,18 +431,10 @@ static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
return retval;
}

static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct at24_data *at24;

at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
return at24_write(at24, buf, off, count);
}

/*-------------------------------------------------------------------------*/

/* panto: using the EEPROM framework macc is superfluous */

/*
* This lets other kernel code access the eeprom data. For example, it
* might hold a board's Ethernet address, or board-specific calibration
Expand Down Expand Up @@ -489,6 +480,91 @@ static void at24_get_ofdata(struct i2c_client *client,
{ }
#endif /* CONFIG_OF */

static int regmap_at24_read(void *context,
const void *reg, size_t reg_size,
void *val, size_t val_size)
{
struct i2c_client *client = context;
struct at24_data *at24;
unsigned int offset;
int ret;

/* reg bits is hardcoded to 32 bits */
BUG_ON(reg_size != 4);
offset = __raw_readl(reg);

at24 = i2c_get_clientdata(client);
if (at24 == NULL)
return -ENODEV;

/* val bytes is always 1 */
BUG_ON(at24->regmap_config->val_bits != 8);

ret = at24_read(at24, val, offset, val_size);
if (ret < 0)
return ret;
if (ret != val_size)
return -EINVAL;

return 0;
}

static int regmap_at24_gather_write(void *context,
const void *reg, size_t reg_size,
const void *val, size_t val_size)
{
struct i2c_client *client = context;
struct at24_data *at24;
unsigned int offset;
int ret;

at24 = i2c_get_clientdata(client);
if (at24 == NULL)
return -ENODEV;

BUG_ON(reg_size != 4);
offset = __raw_readl(reg);

/* val bytes is always 1 */
BUG_ON(at24->regmap_config->val_bits != 8);

ret = at24_write(at24, val, offset, val_size);
if (ret < 0)
return ret;
if (ret != val_size)
return -EINVAL;

return 0;
}

static int regmap_at24_write(void *context, const void *data, size_t count)
{
struct i2c_client *client = context;
struct at24_data *at24;
unsigned int reg_bytes, offset;

at24 = i2c_get_clientdata(client);
if (at24 == NULL)
return -ENODEV;

reg_bytes = at24->regmap_config->reg_bits / 8;
offset = reg_bytes;

BUG_ON(reg_bytes != 4);
BUG_ON(count <= offset);

return regmap_at24_gather_write(context, data, reg_bytes,
data + offset, count - offset);
}

static struct regmap_bus regmap_at24_bus = {
.read = regmap_at24_read,
.write = regmap_at24_write,
.gather_write = regmap_at24_gather_write,
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
};

static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct at24_platform_data chip;
Expand All @@ -499,6 +575,10 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
int err;
unsigned i, num_addresses;
kernel_ulong_t magic;
struct regmap_config *regmap_config;
struct regmap *regmap;
struct nvmem_config *nvmem_config;
struct nvmem_device *nvmem_dev;

if (client->dev.platform_data) {
chip = *(struct at24_platform_data *)client->dev.platform_data;
Expand Down Expand Up @@ -566,33 +646,82 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
}
}

regmap_config = devm_kzalloc(&client->dev, sizeof(*regmap_config),
GFP_KERNEL);
if (IS_ERR(regmap_config)) {
err = PTR_ERR(regmap_config);
dev_err(&client->dev, "%s: regmap_config allocation failed (%d)\n",
__func__, err);
return err;
}

/* use 32 bits as registers, they don't appear on the wire anyway */
regmap_config->reg_bits = 32;
regmap_config->val_bits = 8;
regmap_config->cache_type = REGCACHE_NONE;
regmap_config->max_register = chip.byte_len;

if (chip.flags & AT24_FLAG_TAKE8ADDR)
num_addresses = 8;
else
num_addresses = DIV_ROUND_UP(chip.byte_len,
(chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);

/* we can't use devm_regmap_init_i2c due to the many i2c clients */
regmap = devm_regmap_init(&client->dev, &regmap_at24_bus,
client, regmap_config);
if (IS_ERR(regmap)) {
err = PTR_ERR(regmap);
dev_err(&client->dev, "%s: regmap allocation failed (%d)\n",
__func__, err);
return err;
}

nvmem_config = devm_kzalloc(&client->dev, sizeof(*nvmem_config),
GFP_KERNEL);
if (IS_ERR(nvmem_config)) {
err = PTR_ERR(nvmem_config);
dev_err(&client->dev, "%s: nvmem_config allocation failed (%d)\n",
__func__, err);
return err;
}
nvmem_config->dev = &client->dev;
nvmem_config->name = "at24-";
nvmem_config->id = ida_simple_get(&at24_ida, 0, 0, GFP_KERNEL);
nvmem_config->owner = THIS_MODULE;
if (nvmem_config->id < 0) {
err = nvmem_config->id;
dev_err(&client->dev, "%s: eeprom id allocation failed (%d)\n",
__func__, err);
return err;
}

nvmem_dev = nvmem_register(nvmem_config);
if (IS_ERR(nvmem_dev)) {
err = PTR_ERR(nvmem_dev);
dev_err(&client->dev, "%s: nvmem_register failed (%d)\n",
__func__, err);
goto err_out;
}

at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) +
num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);
if (!at24)
return -ENOMEM;
if (!at24) {
err = -ENOMEM;
goto err_out;
}

at24->regmap = regmap;
at24->regmap_config = regmap_config;
at24->nvmem_config = nvmem_config;
at24->nvmem_dev = nvmem_dev;

mutex_init(&at24->lock);
at24->use_smbus = use_smbus;
at24->use_smbus_write = use_smbus_write;
at24->chip = chip;
at24->num_addresses = num_addresses;

/*
* Export the EEPROM bytes through sysfs, since that's convenient.
* By default, only root should see the data (maybe passwords etc)
*/
sysfs_bin_attr_init(&at24->bin);
at24->bin.attr.name = "eeprom";
at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
at24->bin.read = at24_bin_read;
at24->bin.size = chip.byte_len;

at24->macc.read = at24_macc_read;

writable = !(chip.flags & AT24_FLAG_READONLY);
Expand All @@ -603,9 +732,6 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)

at24->macc.write = at24_macc_write;

at24->bin.write = at24_bin_write;
at24->bin.attr.mode |= S_IWUSR;

if (write_max > io_limit)
write_max = io_limit;
if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
Expand All @@ -615,8 +741,10 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
/* buffer (data + address at the beginning) */
at24->writebuf = devm_kzalloc(&client->dev,
write_max + 2, GFP_KERNEL);
if (!at24->writebuf)
return -ENOMEM;
if (!at24->writebuf) {
err = -ENOMEM;
goto err_out;
}
} else {
dev_warn(&client->dev,
"cannot write due to controller restrictions.");
Expand All @@ -637,14 +765,10 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
}
}

err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
if (err)
goto err_clients;

i2c_set_clientdata(client, at24);

dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n",
at24->bin.size, client->name,
at24->chip.byte_len, client->name,
writable ? "writable" : "read-only", at24->write_max);
if (use_smbus == I2C_SMBUS_WORD_DATA ||
use_smbus == I2C_SMBUS_BYTE_DATA) {
Expand All @@ -660,10 +784,14 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
return 0;

err_clients:

for (i = 1; i < num_addresses; i++)
if (at24->client[i])
i2c_unregister_device(at24->client[i]);

err_out:
ida_simple_remove(&at24_ida, nvmem_config->id);

return err;
}

Expand All @@ -673,11 +801,12 @@ static int at24_remove(struct i2c_client *client)
int i;

at24 = i2c_get_clientdata(client);
sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);

for (i = 1; i < at24->num_addresses; i++)
i2c_unregister_device(at24->client[i]);

nvmem_unregister(at24->nvmem_dev);

return 0;
}

Expand Down

0 comments on commit e02880f

Please sign in to comment.