summaryrefslogtreecommitdiff
path: root/drivers/misc/eeprom
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/eeprom')
-rw-r--r--drivers/misc/eeprom/Kconfig11
-rw-r--r--drivers/misc/eeprom/Makefile1
-rw-r--r--drivers/misc/eeprom/at24.c43
-rw-r--r--drivers/misc/eeprom/at25.c13
-rw-r--r--drivers/misc/eeprom/ee1004.c281
-rw-r--r--drivers/misc/eeprom/eeprom_93xx46.c19
6 files changed, 321 insertions, 47 deletions
diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index 68a1ac929917..fe7a1d27a017 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -111,4 +111,15 @@ config EEPROM_IDT_89HPESX
This driver can also be built as a module. If so, the module
will be called idt_89hpesx.
+config EEPROM_EE1004
+ tristate "SPD EEPROMs on DDR4 memory modules"
+ depends on I2C && SYSFS
+ help
+ Enable this driver to get read support to SPD EEPROMs following
+ the JEDEC EE1004 standard. These are typically found on DDR4
+ SDRAM memory modules.
+
+ This driver can also be built as a module. If so, the module
+ will be called ee1004.
+
endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index 2aab60ef3e3e..a9b4b6579b75 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o
obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o
obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
+obj-$(CONFIG_EEPROM_EE1004) += ee1004.o
diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c
index 7e50e1d6f58c..636ed7149793 100644
--- a/drivers/misc/eeprom/at24.c
+++ b/drivers/misc/eeprom/at24.c
@@ -106,23 +106,6 @@ static unsigned int at24_write_timeout = 25;
module_param_named(write_timeout, at24_write_timeout, uint, 0);
MODULE_PARM_DESC(at24_write_timeout, "Time (in ms) to try writes (default 25)");
-/*
- * Both reads and writes fail if the previous write didn't complete yet. This
- * macro loops a few times waiting at least long enough for one entire page
- * write to work while making sure that at least one iteration is run before
- * checking the break condition.
- *
- * It takes two parameters: a variable in which the future timeout in jiffies
- * will be stored and a temporary variable holding the time of the last
- * iteration of processing the request. Both should be unsigned integers
- * holding at least 32 bits.
- */
-#define at24_loop_until_timeout(tout, op_time) \
- for (tout = jiffies + msecs_to_jiffies(at24_write_timeout), \
- op_time = 0; \
- op_time ? time_before(op_time, tout) : true; \
- usleep_range(1000, 1500), op_time = jiffies)
-
struct at24_chip_data {
/*
* these fields mirror their equivalents in
@@ -308,13 +291,22 @@ static ssize_t at24_regmap_read(struct at24_data *at24, char *buf,
/* adjust offset for mac and serial read ops */
offset += at24->offset_adj;
- at24_loop_until_timeout(timeout, read_time) {
+ timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
+ do {
+ /*
+ * The timestamp shall be taken before the actual operation
+ * to avoid a premature timeout in case of high CPU load.
+ */
+ read_time = jiffies;
+
ret = regmap_bulk_read(regmap, offset, buf, count);
dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
count, offset, ret, jiffies);
if (!ret)
return count;
- }
+
+ usleep_range(1000, 1500);
+ } while (time_before(read_time, timeout));
return -ETIMEDOUT;
}
@@ -358,14 +350,23 @@ static ssize_t at24_regmap_write(struct at24_data *at24, const char *buf,
regmap = at24_client->regmap;
client = at24_client->client;
count = at24_adjust_write_count(at24, offset, count);
+ timeout = jiffies + msecs_to_jiffies(at24_write_timeout);
+
+ do {
+ /*
+ * The timestamp shall be taken before the actual operation
+ * to avoid a premature timeout in case of high CPU load.
+ */
+ write_time = jiffies;
- at24_loop_until_timeout(timeout, write_time) {
ret = regmap_bulk_write(regmap, offset, buf, count);
dev_dbg(&client->dev, "write %zu@%d --> %d (%ld)\n",
count, offset, ret, jiffies);
if (!ret)
return count;
- }
+
+ usleep_range(1000, 1500);
+ } while (time_before(write_time, timeout));
return -ETIMEDOUT;
}
diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c
index 840afb398f9e..99de6939cd5a 100644
--- a/drivers/misc/eeprom/at25.c
+++ b/drivers/misc/eeprom/at25.c
@@ -366,7 +366,7 @@ static int at25_probe(struct spi_device *spi)
at25->nvmem_config.word_size = 1;
at25->nvmem_config.size = chip.byte_len;
- at25->nvmem = nvmem_register(&at25->nvmem_config);
+ at25->nvmem = devm_nvmem_register(&spi->dev, &at25->nvmem_config);
if (IS_ERR(at25->nvmem))
return PTR_ERR(at25->nvmem);
@@ -379,16 +379,6 @@ static int at25_probe(struct spi_device *spi)
return 0;
}
-static int at25_remove(struct spi_device *spi)
-{
- struct at25_data *at25;
-
- at25 = spi_get_drvdata(spi);
- nvmem_unregister(at25->nvmem);
-
- return 0;
-}
-
/*-------------------------------------------------------------------------*/
static const struct of_device_id at25_of_match[] = {
@@ -403,7 +393,6 @@ static struct spi_driver at25_driver = {
.of_match_table = at25_of_match,
},
.probe = at25_probe,
- .remove = at25_remove,
};
module_spi_driver(at25_driver);
diff --git a/drivers/misc/eeprom/ee1004.c b/drivers/misc/eeprom/ee1004.c
new file mode 100644
index 000000000000..276c1690ea1b
--- /dev/null
+++ b/drivers/misc/eeprom/ee1004.c
@@ -0,0 +1,281 @@
+/*
+ * ee1004 - driver for DDR4 SPD EEPROMs
+ *
+ * Copyright (C) 2017 Jean Delvare
+ *
+ * Based on the at24 driver:
+ * Copyright (C) 2005-2007 David Brownell
+ * Copyright (C) 2008 Wolfram Sang, Pengutronix
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+/*
+ * DDR4 memory modules use special EEPROMs following the Jedec EE1004
+ * specification. These are 512-byte EEPROMs using a single I2C address
+ * in the 0x50-0x57 range for data. One of two 256-byte page is selected
+ * by writing a command to I2C address 0x36 or 0x37 on the same I2C bus.
+ *
+ * Therefore we need to request these 2 additional addresses, and serialize
+ * access to all such EEPROMs with a single mutex.
+ *
+ * We assume it is safe to read up to 32 bytes at once from these EEPROMs.
+ * We use SMBus access even if I2C is available, these EEPROMs are small
+ * enough, and reading from them infrequent enough, that we favor simplicity
+ * over performance.
+ */
+
+#define EE1004_ADDR_SET_PAGE 0x36
+#define EE1004_EEPROM_SIZE 512
+#define EE1004_PAGE_SIZE 256
+#define EE1004_PAGE_SHIFT 8
+
+/*
+ * Mutex protects ee1004_set_page and ee1004_dev_count, and must be held
+ * from page selection to end of read.
+ */
+static DEFINE_MUTEX(ee1004_bus_lock);
+static struct i2c_client *ee1004_set_page[2];
+static unsigned int ee1004_dev_count;
+static int ee1004_current_page;
+
+static const struct i2c_device_id ee1004_ids[] = {
+ { "ee1004", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ee1004_ids);
+
+/*-------------------------------------------------------------------------*/
+
+static ssize_t ee1004_eeprom_read(struct i2c_client *client, char *buf,
+ unsigned int offset, size_t count)
+{
+ int status;
+
+ if (count > I2C_SMBUS_BLOCK_MAX)
+ count = I2C_SMBUS_BLOCK_MAX;
+ /* Can't cross page boundaries */
+ if (unlikely(offset + count > EE1004_PAGE_SIZE))
+ count = EE1004_PAGE_SIZE - offset;
+
+ status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset,
+ count, buf);
+ dev_dbg(&client->dev, "read %zu@%d --> %d\n", count, offset, status);
+
+ return status;
+}
+
+static ssize_t ee1004_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct i2c_client *client = to_i2c_client(dev);
+ size_t requested = count;
+ int page;
+
+ if (unlikely(!count))
+ return count;
+
+ page = off >> EE1004_PAGE_SHIFT;
+ if (unlikely(page > 1))
+ return 0;
+ off &= (1 << EE1004_PAGE_SHIFT) - 1;
+
+ /*
+ * Read data from chip, protecting against concurrent access to
+ * other EE1004 SPD EEPROMs on the same adapter.
+ */
+ mutex_lock(&ee1004_bus_lock);
+
+ while (count) {
+ int status;
+
+ /* Select page */
+ if (page != ee1004_current_page) {
+ /* Data is ignored */
+ status = i2c_smbus_write_byte(ee1004_set_page[page],
+ 0x00);
+ if (status < 0) {
+ dev_err(dev, "Failed to select page %d (%d)\n",
+ page, status);
+ mutex_unlock(&ee1004_bus_lock);
+ return status;
+ }
+ dev_dbg(dev, "Selected page %d\n", page);
+ ee1004_current_page = page;
+ }
+
+ status = ee1004_eeprom_read(client, buf, off, count);
+ if (status < 0) {
+ mutex_unlock(&ee1004_bus_lock);
+ return status;
+ }
+ buf += status;
+ off += status;
+ count -= status;
+
+ if (off == EE1004_PAGE_SIZE) {
+ page++;
+ off = 0;
+ }
+ }
+
+ mutex_unlock(&ee1004_bus_lock);
+
+ return requested;
+}
+
+static const struct bin_attribute eeprom_attr = {
+ .attr = {
+ .name = "eeprom",
+ .mode = 0444,
+ },
+ .size = EE1004_EEPROM_SIZE,
+ .read = ee1004_read,
+};
+
+static int ee1004_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err, cnr = 0;
+ const char *slow = NULL;
+
+ /* Make sure we can operate on this adapter */
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_BYTE |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ if (i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_BYTE |
+ I2C_FUNC_SMBUS_READ_WORD_DATA))
+ slow = "word";
+ else if (i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_BYTE |
+ I2C_FUNC_SMBUS_READ_BYTE_DATA))
+ slow = "byte";
+ else
+ return -EPFNOSUPPORT;
+ }
+
+ /* Use 2 dummy devices for page select command */
+ mutex_lock(&ee1004_bus_lock);
+ if (++ee1004_dev_count == 1) {
+ for (cnr = 0; cnr < 2; cnr++) {
+ ee1004_set_page[cnr] = i2c_new_dummy(client->adapter,
+ EE1004_ADDR_SET_PAGE + cnr);
+ if (!ee1004_set_page[cnr]) {
+ dev_err(&client->dev,
+ "address 0x%02x unavailable\n",
+ EE1004_ADDR_SET_PAGE + cnr);
+ err = -EADDRINUSE;
+ goto err_clients;
+ }
+ }
+ } else if (i2c_adapter_id(client->adapter) !=
+ i2c_adapter_id(ee1004_set_page[0]->adapter)) {
+ dev_err(&client->dev,
+ "Driver only supports devices on a single I2C bus\n");
+ err = -EOPNOTSUPP;
+ goto err_clients;
+ }
+
+ /* Remember current page to avoid unneeded page select */
+ err = i2c_smbus_read_byte(ee1004_set_page[0]);
+ if (err == -ENXIO) {
+ /* Nack means page 1 is selected */
+ ee1004_current_page = 1;
+ } else if (err < 0) {
+ /* Anything else is a real error, bail out */
+ goto err_clients;
+ } else {
+ /* Ack means page 0 is selected, returned value meaningless */
+ ee1004_current_page = 0;
+ }
+ dev_dbg(&client->dev, "Currently selected page: %d\n",
+ ee1004_current_page);
+ mutex_unlock(&ee1004_bus_lock);
+
+ /* Create the sysfs eeprom file */
+ err = sysfs_create_bin_file(&client->dev.kobj, &eeprom_attr);
+ if (err)
+ goto err_clients_lock;
+
+ dev_info(&client->dev,
+ "%u byte EE1004-compliant SPD EEPROM, read-only\n",
+ EE1004_EEPROM_SIZE);
+ if (slow)
+ dev_notice(&client->dev,
+ "Falling back to %s reads, performance will suffer\n",
+ slow);
+
+ return 0;
+
+ err_clients_lock:
+ mutex_lock(&ee1004_bus_lock);
+ err_clients:
+ if (--ee1004_dev_count == 0) {
+ for (cnr--; cnr >= 0; cnr--) {
+ i2c_unregister_device(ee1004_set_page[cnr]);
+ ee1004_set_page[cnr] = NULL;
+ }
+ }
+ mutex_unlock(&ee1004_bus_lock);
+
+ return err;
+}
+
+static int ee1004_remove(struct i2c_client *client)
+{
+ int i;
+
+ sysfs_remove_bin_file(&client->dev.kobj, &eeprom_attr);
+
+ /* Remove page select clients if this is the last device */
+ mutex_lock(&ee1004_bus_lock);
+ if (--ee1004_dev_count == 0) {
+ for (i = 0; i < 2; i++) {
+ i2c_unregister_device(ee1004_set_page[i]);
+ ee1004_set_page[i] = NULL;
+ }
+ }
+ mutex_unlock(&ee1004_bus_lock);
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct i2c_driver ee1004_driver = {
+ .driver = {
+ .name = "ee1004",
+ },
+ .probe = ee1004_probe,
+ .remove = ee1004_remove,
+ .id_table = ee1004_ids,
+};
+
+static int __init ee1004_init(void)
+{
+ return i2c_add_driver(&ee1004_driver);
+}
+module_init(ee1004_init);
+
+static void __exit ee1004_exit(void)
+{
+ i2c_del_driver(&ee1004_driver);
+}
+module_exit(ee1004_exit);
+
+MODULE_DESCRIPTION("Driver for EE1004-compliant DDR4 SPD EEPROMs");
+MODULE_AUTHOR("Jean Delvare");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c
index 38766968bfa2..c6dd9ad9bf7b 100644
--- a/drivers/misc/eeprom/eeprom_93xx46.c
+++ b/drivers/misc/eeprom/eeprom_93xx46.c
@@ -439,7 +439,7 @@ static int eeprom_93xx46_probe(struct spi_device *spi)
return -ENODEV;
}
- edev = kzalloc(sizeof(*edev), GFP_KERNEL);
+ edev = devm_kzalloc(&spi->dev, sizeof(*edev), GFP_KERNEL);
if (!edev)
return -ENOMEM;
@@ -449,8 +449,7 @@ static int eeprom_93xx46_probe(struct spi_device *spi)
edev->addrlen = 6;
else {
dev_err(&spi->dev, "unspecified address type\n");
- err = -EINVAL;
- goto fail;
+ return -EINVAL;
}
mutex_init(&edev->lock);
@@ -473,11 +472,9 @@ static int eeprom_93xx46_probe(struct spi_device *spi)
edev->nvmem_config.word_size = 1;
edev->nvmem_config.size = edev->size;
- edev->nvmem = nvmem_register(&edev->nvmem_config);
- if (IS_ERR(edev->nvmem)) {
- err = PTR_ERR(edev->nvmem);
- goto fail;
- }
+ edev->nvmem = devm_nvmem_register(&spi->dev, &edev->nvmem_config);
+ if (IS_ERR(edev->nvmem))
+ return PTR_ERR(edev->nvmem);
dev_info(&spi->dev, "%d-bit eeprom %s\n",
(pd->flags & EE_ADDR8) ? 8 : 16,
@@ -490,21 +487,15 @@ static int eeprom_93xx46_probe(struct spi_device *spi)
spi_set_drvdata(spi, edev);
return 0;
-fail:
- kfree(edev);
- return err;
}
static int eeprom_93xx46_remove(struct spi_device *spi)
{
struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi);
- nvmem_unregister(edev->nvmem);
-
if (!(edev->pdata->flags & EE_READONLY))
device_remove_file(&spi->dev, &dev_attr_erase);
- kfree(edev);
return 0;
}