[PATCH 1/2] drivers/misc/eeprom/men_eeprod: Introduce MEN Board Information EEPROM driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 <johannes.thumshirn@xxxxxx>
Signed-off-by: Andreas Werner <andreas.werner@xxxxxx>
---
 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 <andreas.werner@xxxxxx>
+S:	Supported
+F:	drivers/misc/eeprom/men_eeprod.c
+F:	Documentation/ABI/testing/sysfs-bus-i2c-devices-men_eeprod
+
 METAG ARCHITECTURE
 M:	James Hogan <james.hogan@xxxxxxxxxx>
 L:	linux-metag@xxxxxxxxxxxxxxx
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 <andreas.werner@xxxxxx>");
+MODULE_LICENSE("GPL v2");
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux