Added driver to support the MEN Board Information EEPROM.
The driver exports the production information as read only sysfs
entries, as well as a user section which is read/write accessible.

Tested on PPC QorIQ and Intel Atom E680.

Tested-by: Johannes Thumshirn <[email protected]>
Signed-off-by: Andreas Werner <[email protected]>
---
 MAINTAINERS                      |   6 +
 drivers/misc/eeprom/Kconfig      |  10 +
 drivers/misc/eeprom/Makefile     |   1 +
 drivers/misc/eeprom/men_eeprod.c | 560 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 577 insertions(+)
 create mode 100644 drivers/misc/eeprom/men_eeprod.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 503da28..88ede76 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6029,6 +6029,12 @@ F:       drivers/leds/leds-menf21bmc.c
 F:     drivers/hwmon/menf21bmc_hwmon.c
 F:     Documentation/hwmon/menf21bmc
 
+MEN EEPROD (Board information EEPROM)
+M:     Andreas Werner <[email protected]>
+S:     Supported
+F:     drivers/misc/eeprom/men_eeprod.c
+F:     Documentation/ABI/testing/sysfs-bus-i2c-devices-men_eeprod
+
 METAG ARCHITECTURE
 M:     James Hogan <[email protected]>
 L:     [email protected]
diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index 9536852f..e087d08 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -62,6 +62,16 @@ config EEPROM_MAX6875
          This driver can also be built as a module.  If so, the module
          will be called max6875.
 
+config EEPROM_MEN_EEPROD
+       tristate "MEN Board Information EEPROM"
+       depends on I2C && SYSFS
+       help
+         If you say yes here you get support for the MEN Board Information
+         EEPROM. This driver supports read-only access to the production
+         data section, and read-write access to the user section.
+
+         This driver can also be built as a module. If so, the module
+         will be called men_eeprod.
 
 config EEPROM_93CX6
        tristate "EEPROM 93CX6 support"
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index 9507aec..8c70a81 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -2,6 +2,7 @@ obj-$(CONFIG_EEPROM_AT24)       += at24.o
 obj-$(CONFIG_EEPROM_AT25)      += at25.o
 obj-$(CONFIG_EEPROM_LEGACY)    += eeprom.o
 obj-$(CONFIG_EEPROM_MAX6875)   += max6875.o
+obj-$(CONFIG_EEPROM_MEN_EEPROD) += men_eeprod.o
 obj-$(CONFIG_EEPROM_93CX6)     += eeprom_93cx6.o
 obj-$(CONFIG_EEPROM_93XX46)    += eeprom_93xx46.o
 obj-$(CONFIG_EEPROM_SUNXI_SID) += sunxi_sid.o
diff --git a/drivers/misc/eeprom/men_eeprod.c b/drivers/misc/eeprom/men_eeprod.c
new file mode 100644
index 0000000..28264df
--- /dev/null
+++ b/drivers/misc/eeprom/men_eeprod.c
@@ -0,0 +1,560 @@
+/*
+ *  men_eeprod - MEN board information EEPROM driver.
+ *
+ *  This is the driver for the Board Information EEPROM on almost
+ *  all of the MEN boards.
+ *  The driver exports each of the predefined eeprom sections as sysfs entries
+ *  including an entry for user data.
+ *
+ *  The EEPROM can be normally found at I2C address 0x57.
+ *
+ *  Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH
+ *
+ *  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.
+ */
+
+/*
+ * Supports the following EEPROM layouts:
+ *
+ * Name                eeprod_id       user_section size
+ * ----------------------------------------------
+ * EEPROD2     0x0E            232 byte
+ *
+ * See Documentation/ABI/testing/sysfs-bus-i2c-devices-men_eeprod
+ * for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/jiffies.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+
+#define EEPROM_ID_ADDR 0x00
+#define EEPROM_SIZE    256
+#define EEPROD2_ID     0x0E
+
+#define DATE_YEAR_BIAS 1990
+
+/*
+ * Internal structure of the Board Information EEPROM
+ * production data section
+ */
+struct eeprom_data {
+       uint8_t eeprod_id;
+
+       uint8_t revision[3];
+       uint32_t serialnr;
+       uint8_t board_model;
+       char hw_name[6];
+
+       uint8_t reserved;
+
+       __be16 prod_date;
+       __be16 rep_date;
+
+       uint8_t reserved2[4];
+};
+
+struct men_eeprod_data {
+       struct bin_attribute user_section;
+       struct i2c_client *client;
+       struct eeprom_data eeprom;
+       struct mutex lock;
+       int smb_access;
+};
+
+static unsigned int i2c_timeout = 25;
+module_param(i2c_timeout, uint, 0);
+MODULE_PARM_DESC(i2c_timeout, "Time (in ms) to try reads and writes (default 
25)");
+
+#define OFFSET_TO_USR_SECTION(off) (off + sizeof(struct eeprom_data))
+
+static inline int eeprod_get_day(__be16 eeprod_date)
+{
+       return be16_to_cpu(eeprod_date) & 0x001f;
+}
+
+static inline int eeprod_get_month(__be16 eeprod_date)
+{
+       return (be16_to_cpu(eeprod_date) >> 5) & 0x000f;
+}
+
+static inline int eeprod_get_year(__be16 eeprod_date)
+{
+       return ((be16_to_cpu(eeprod_date) >> 9) & 0x007f) + DATE_YEAR_BIAS;
+}
+
+static ssize_t men_eeprom_read(struct men_eeprod_data *drv_data,
+                              char *buf, loff_t offset, size_t count)
+{
+       struct i2c_client *i2c_client = drv_data->client;
+       unsigned long timeout, read_time;
+       int ret_val;
+
+       /*
+        * Read fail if the previous write did not copmlete yet.
+        * Therefore we try to read a few times until this succeed.
+        */
+       timeout = jiffies + msecs_to_jiffies(i2c_timeout);
+       do {
+               read_time = jiffies;
+
+               /*
+                * if there is just one byte requested, we use read byte data.
+                * This will also protect us against a rollover if there is
+                * just one byte left to read.
+                */
+               if (count == 1) {
+                       ret_val = i2c_smbus_read_byte_data(i2c_client, offset);
+                       if (ret_val >= 0) {
+                               buf[0] = ret_val;
+                               ret_val = 1;
+                       }
+                       goto err_byte;
+               }
+
+               switch (drv_data->smb_access) {
+               case I2C_SMBUS_I2C_BLOCK_DATA:
+                       if (count > I2C_SMBUS_BLOCK_MAX)
+                               count = I2C_SMBUS_BLOCK_MAX;
+
+                       ret_val = i2c_smbus_read_i2c_block_data(i2c_client,
+                                                               offset, count,
+                                                               buf);
+                       if (ret_val >= 0)
+                               return count;
+                       break;
+               case I2C_SMBUS_WORD_DATA:
+                       ret_val = i2c_smbus_read_word_data(i2c_client, offset);
+                       if (ret_val >= 0) {
+                               buf[0] = ret_val & 0xff;
+                               buf[1] = ret_val >> 8;
+                               return 2;
+                       }
+                       break;
+               default:
+                       ret_val = i2c_smbus_read_byte_data(i2c_client, offset);
+                       if (ret_val >= 0) {
+                               buf[0] = ret_val;
+                               return 1;
+                       }
+                       break;
+               }
+
+err_byte:
+               usleep_range(600, 1000);
+       } while (time_before(read_time, timeout));
+
+       return -ETIMEDOUT;
+
+}
+
+static ssize_t men_user_section_read(struct file *filp, struct kobject *kobj,
+                                    struct bin_attribute *attr,
+                                    char *buf, loff_t off, size_t count)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       int user_section_size = attr->size;
+       ssize_t retval = 0;
+       int bytes_read;
+
+       if (off > user_section_size)
+               return 0;
+
+       if (off + count > user_section_size)
+               count = user_section_size - off;
+
+       off = OFFSET_TO_USR_SECTION(off);
+
+       mutex_lock(&drv_data->lock);
+       while (count) {
+               bytes_read = men_eeprom_read(drv_data, buf, off, count);
+
+               if (bytes_read <= 0) {
+                       if (retval == 0)
+                               retval = bytes_read;
+                       break;
+               }
+
+               buf += bytes_read;
+               off += bytes_read;
+               count -= bytes_read;
+               retval += bytes_read;
+       }
+       mutex_unlock(&drv_data->lock);
+
+       return retval;
+}
+
+static ssize_t men_eeprom_write(struct men_eeprod_data *drv_data,
+                               char *buf, loff_t offset, size_t count)
+{
+       struct i2c_client *i2c_client = drv_data->client;
+       unsigned long timeout, write_time;
+       uint16_t word_data;
+       int ret_val;
+
+       /*
+        * Write fail if the previous write did not copmlete yet.
+        * Therefore we try to write a few times until this succeed.
+        */
+       timeout = jiffies + msecs_to_jiffies(i2c_timeout);
+       do {
+               write_time = jiffies;
+
+               /*
+                * if there is just one byte to write, we use write byte data.
+                * This will also protect us against a rollover if there is
+                * just one byte left to write.
+                */
+               if (count == 1) {
+                       ret_val =  i2c_smbus_write_byte_data(i2c_client, offset,
+                                                            buf[0]);
+                       if (!ret_val)
+                               return 1;
+                       goto err_byte;
+               }
+
+               switch (drv_data->smb_access) {
+               case I2C_SMBUS_I2C_BLOCK_DATA:
+                       if (count > I2C_SMBUS_BLOCK_MAX)
+                               count = I2C_SMBUS_BLOCK_MAX;
+
+                       ret_val = i2c_smbus_write_i2c_block_data(i2c_client,
+                                                                offset, count,
+                                                                buf);
+                       if (!ret_val)
+                               return count;
+                       break;
+               case I2C_SMBUS_WORD_DATA:
+                       word_data = buf[0] | (buf[1] << 8);
+
+                       ret_val = i2c_smbus_write_word_data(i2c_client, offset,
+                                                           word_data);
+                       if (!ret_val)
+                               return 2;
+                       break;
+               default:
+                       ret_val = i2c_smbus_write_byte_data(i2c_client, offset,
+                                                           buf[0]);
+                       if (!ret_val)
+                               return 1;
+                       break;
+               }
+err_byte:
+               usleep_range(600, 1000);
+       } while (time_before(write_time, timeout));
+
+       return -ETIMEDOUT;
+}
+
+static ssize_t men_user_section_write(struct file *filp, struct kobject *kobj,
+                                     struct bin_attribute *attr, char *buf,
+                                     loff_t off, size_t count)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       int user_section_size = attr->size;
+       int bytes_written;
+       ssize_t retval = 0;
+
+       if (off > user_section_size)
+               return 0;
+
+       if (off + count > user_section_size)
+               count = user_section_size - off;
+
+       off = OFFSET_TO_USR_SECTION(off);
+
+       mutex_lock(&drv_data->lock);
+       while (count) {
+               bytes_written = men_eeprom_write(drv_data, buf, off, count);
+
+               if (bytes_written <= 0) {
+                       if (retval == 0)
+                               retval = bytes_written;
+                       break;
+               }
+
+               buf += bytes_written;
+               off += bytes_written;
+               count -= bytes_written;
+               retval += bytes_written;
+       }
+       mutex_unlock(&drv_data->lock);
+
+       return retval;
+}
+
+static ssize_t
+show_eeprod_id(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       struct eeprom_data *eeprom = &drv_data->eeprom;
+
+       return sprintf(buf, "0x%02x\n", eeprom->eeprod_id);
+}
+
+static ssize_t
+show_revision(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       struct eeprom_data *eeprom = &drv_data->eeprom;
+
+       return sprintf(buf, "%02d.%02d.%02d\n", eeprom->revision[0],
+                      eeprom->revision[1], eeprom->revision[2]);
+}
+
+static ssize_t
+show_serialnr(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       struct eeprom_data *eeprom = &drv_data->eeprom;
+
+       return sprintf(buf, "%d\n", cpu_to_be32(eeprom->serialnr));
+}
+
+static ssize_t
+show_hw_name(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       struct eeprom_data *eeprom = &drv_data->eeprom;
+
+       return sprintf(buf, "%s%02d\n", eeprom->hw_name, eeprom->board_model);
+}
+
+static ssize_t
+show_prod_date(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       __be16 eeprod_date = drv_data->eeprom.prod_date;
+
+       return sprintf(buf, "%04d-%02d-%02d\n",
+                      eeprod_get_year(eeprod_date),
+                      eeprod_get_month(eeprod_date),
+                      eeprod_get_day(eeprod_date));
+}
+
+static ssize_t
+show_rep_date(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct men_eeprod_data *drv_data = dev_get_drvdata(dev);
+       __be16 eeprod_date = drv_data->eeprom.rep_date;
+
+       /*
+        * could be empty if the board was never send back
+        * to the repair department.
+        */
+       if (eeprod_date == cpu_to_be16(0xffff))
+               return -EINVAL;
+
+       return sprintf(buf, "%04d-%02d-%02d\n",
+                      eeprod_get_year(eeprod_date),
+                      eeprod_get_month(eeprod_date),
+                      eeprod_get_day(eeprod_date));
+}
+
+static DEVICE_ATTR(eeprod_id, S_IRUGO, show_eeprod_id, NULL);
+static DEVICE_ATTR(revision, S_IRUGO, show_revision, NULL);
+static DEVICE_ATTR(serial, S_IRUGO, show_serialnr, NULL);
+static DEVICE_ATTR(hw_name, S_IRUGO, show_hw_name, NULL);
+static DEVICE_ATTR(prod_date, S_IRUGO, show_prod_date, NULL);
+static DEVICE_ATTR(rep_date, S_IRUGO, show_rep_date, NULL);
+
+static struct attribute *eeprod_attrs[] = {
+       &dev_attr_eeprod_id.attr,
+       &dev_attr_revision.attr,
+       &dev_attr_serial.attr,
+       &dev_attr_hw_name.attr,
+       &dev_attr_prod_date.attr,
+       &dev_attr_rep_date.attr,
+       NULL,
+};
+
+static struct attribute_group eeprod_attr_group = {
+       .attrs = eeprod_attrs,
+};
+
+static struct bin_attribute eeprom_user_attr = {
+       .attr = {
+               .name = "user_section",
+               .mode = S_IRUGO | S_IWUSR,
+       },
+       .size = EEPROM_SIZE - sizeof(struct eeprom_data),
+       .read = men_user_section_read,
+       .write = men_user_section_write,
+};
+
+static int men_eeprod_read_prod_data(struct men_eeprod_data *drv_data)
+{
+       struct eeprom_data *eeprom = &drv_data->eeprom;
+       uint8_t *eeprom_byte;
+       int i, ret;
+
+       eeprom_byte = (uint8_t *)eeprom + 1;
+
+       for (i = 1; i < sizeof(*eeprom); i++) {
+               ret = i2c_smbus_read_byte_data(drv_data->client, i);
+               if (ret < 0)
+                       return ret;
+
+               *(eeprom_byte++) = ret;
+       }
+       return 0;
+}
+
+static int men_eeprod_calc_parity(struct eeprom_data *eeprom)
+{
+       uint8_t *eeprom_byte;
+       int parity, len;
+
+       eeprom_byte = (uint8_t *)eeprom + 1;
+       len = sizeof(*eeprom) - 1;
+       parity = 0x0f;
+
+       while (len--) {
+               parity ^= (*eeprom_byte >> 4);
+               parity ^= (*eeprom_byte) & 0x0f;
+               eeprom_byte++;
+       }
+
+       return parity;
+}
+
+static int men_eeprod_i2c_functionality(struct men_eeprod_data *drv_data)
+{
+       struct i2c_adapter *i2c_adapter = drv_data->client->adapter;
+       int ret;
+
+       /*
+        * As the minimum we need read/write byte data
+        * which every adapter should support
+        */
+       ret = i2c_check_functionality(i2c_adapter,
+                                     I2C_FUNC_SMBUS_BYTE_DATA);
+       if (!ret)
+               return -ENODEV;
+
+       if (i2c_check_functionality(i2c_adapter,
+                                   I2C_FUNC_SMBUS_I2C_BLOCK)) {
+               drv_data->smb_access = I2C_SMBUS_I2C_BLOCK_DATA;
+       } else if (i2c_check_functionality(i2c_adapter,
+                                          I2C_FUNC_SMBUS_WORD_DATA)) {
+               drv_data->smb_access = I2C_SMBUS_WORD_DATA;
+       } else {
+               drv_data->smb_access = I2C_SMBUS_BYTE_DATA;
+       }
+
+       return 0;
+}
+
+static int
+men_eeprod_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct men_eeprod_data *drv_data;
+       int eeprod_id, eeprod_chksum;
+       int parity, ret;
+
+       drv_data = devm_kzalloc(&client->dev, sizeof(*drv_data), GFP_KERNEL);
+       if (!drv_data)
+               return -ENOMEM;
+
+       drv_data->client = client;
+       mutex_init(&drv_data->lock);
+
+       i2c_set_clientdata(client, drv_data);
+
+       ret = men_eeprod_i2c_functionality(drv_data);
+       if (ret)
+               return ret;
+
+       eeprod_id = i2c_smbus_read_byte_data(client, EEPROM_ID_ADDR);
+       if (eeprod_id < 0)
+               return eeprod_id;
+
+       eeprod_chksum = eeprod_id & 0x0f;
+       eeprod_id >>= 4;
+       drv_data->eeprom.eeprod_id = eeprod_id;
+
+       if (eeprod_id == EEPROD2_ID) {
+               dev_info(&client->dev,
+                        "found MEN Board EEPROM. ID: 0x%02x\n", eeprod_id);
+       } else {
+               dev_err(&client->dev,
+                       "board eeprom not supported. ID: 0x%02x\n", eeprod_id);
+               return -ENXIO;
+       }
+
+       ret = men_eeprod_read_prod_data(drv_data);
+       if (ret < 0) {
+               dev_err(&client->dev, "failed to read EEPROM board data\n");
+               return ret;
+       }
+
+       parity = men_eeprod_calc_parity(&drv_data->eeprom);
+       if (parity != eeprod_chksum) {
+               dev_err(&client->dev, "checksum error. eeprom in invalid 
state\n");
+               return -EINVAL;
+       }
+
+       drv_data->user_section = eeprom_user_attr;
+       ret = sysfs_create_bin_file(&client->dev.kobj,
+                                   &drv_data->user_section);
+       if (ret) {
+               dev_err(&client->dev,
+                       "failed to create user_section sysfs entry\n");
+               return ret;
+       }
+
+       ret = sysfs_create_group(&client->dev.kobj, &eeprod_attr_group);
+       if (ret < 0) {
+               dev_err(&client->dev, "failed to create sysfs entries\n");
+               goto err_sysfs;
+       }
+
+       dev_info(&client->dev, "MEN Board Information EEPROM registered\n");
+
+       return 0;
+
+err_sysfs:
+       sysfs_remove_bin_file(&client->dev.kobj, &drv_data->user_section);
+       return ret;
+}
+
+static int men_eeprod_remove(struct i2c_client *client)
+{
+       struct men_eeprod_data *drv_data = i2c_get_clientdata(client);
+
+       sysfs_remove_group(&client->dev.kobj, &eeprod_attr_group);
+       sysfs_remove_bin_file(&client->dev.kobj, &drv_data->user_section);
+       return 0;
+}
+
+static const struct i2c_device_id men_eeprod_ids[] = {
+       { "men_eeprod" },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, men_eeprod_ids);
+
+static struct i2c_driver men_eeprod_driver = {
+       .driver = {
+               .name = "men_eeprod",
+               .owner = THIS_MODULE,
+       },
+       .probe = men_eeprod_probe,
+       .remove = men_eeprod_remove,
+       .id_table = men_eeprod_ids,
+};
+
+module_i2c_driver(men_eeprod_driver);
+
+MODULE_DESCRIPTION("MEN Board Information EEPROM driver");
+MODULE_AUTHOR("Andreas Werner <[email protected]>");
+MODULE_LICENSE("GPL v2");
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to