aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxime Ripard <maxime.ripard@free-electrons.com>2014-03-29 22:07:04 +0100
committerSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2015-02-18 09:56:02 +0000
commit04dd8ef7324ad9442cfe2f6719bf4a7671696d92 (patch)
treef2ea13b5db47cbc420b0eab04d983776b2eac9c6
parent7af292ccde5bbc61db672c38eae40b57b94893b2 (diff)
eeprom: Add a simple EEPROM framework
Up until now, EEPROM drivers were stored in drivers/misc, where they all had to duplicate pretty much the same code to register a sysfs file, allow in-kernel users to access the content of the devices they were driving, etc. This was also a problem as far as other in-kernel users were involved, since the solutions used were pretty much different from on driver to another, there was a rather big abstraction leak. This introduction of this framework aims at solving this. It also introduces DT representation for consumer devices to go get the data they require (MAC Addresses, SoC/Revision ID, part numbers, and so on) from the EEPROMs. Having regmap interface to this framework would give much better abstraction. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> [srinivas.kandagatla: Moved to regmap based and cleanedup apis] Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
-rw-r--r--Documentation/devicetree/bindings/eeprom/eeprom.txt48
-rw-r--r--drivers/Kconfig2
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/eeprom/Kconfig18
-rw-r--r--drivers/eeprom/Makefile9
-rw-r--r--drivers/eeprom/core.c311
-rw-r--r--include/linux/eeprom.h110
7 files changed, 499 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/eeprom/eeprom.txt b/Documentation/devicetree/bindings/eeprom/eeprom.txt
new file mode 100644
index 0000000000000..9ec1ec2f2330f
--- /dev/null
+++ b/Documentation/devicetree/bindings/eeprom/eeprom.txt
@@ -0,0 +1,48 @@
+= EEPROM Data Device Tree Bindings =
+
+This binding is intended to represent the location of hardware
+configuration data stored in EEPROMs.
+
+On a significant proportion of boards, the manufacturer has stored
+some data on an EEPROM-like device, for the OS to be able to retrieve
+these information and act upon it. Obviously, the OS has to know
+about where to retrieve these data from, and where they are stored on
+the storage device.
+
+This document is here to document this.
+
+= Data providers =
+
+Required properties:
+#eeprom-cells: Number of cells in an eeprom specifier; The common
+ case is 2.
+
+For example:
+
+ at24: eeprom@42 {
+ #eeprom-cells = <2>;
+ };
+
+= Data consumers =
+
+Required properties:
+
+eeproms: List of phandle and data cell specifier triplet, one triplet
+ for each data cell the device might be interested in. The
+ triplet consists of the phandle to the eeprom provider, then
+ the offset in byte within that storage device, and the length
+ in byte of the data we care about.
+
+Optional properties:
+
+eeprom-names: List of data cell name strings sorted in the same order
+ as the resets property. Consumers drivers will use
+ eeprom-names to differentiate between multiple cells,
+ and hence being able to know what these cells are for.
+
+For example:
+
+ device {
+ eeproms = <&at24 14 42>;
+ eeprom-names = "soc-rev-id";
+ };
diff --git a/drivers/Kconfig b/drivers/Kconfig
index c70d6e45dc102..d7afc828b528f 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -184,4 +184,6 @@ source "drivers/thunderbolt/Kconfig"
source "drivers/android/Kconfig"
+source "drivers/eeprom/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 527a6da8d539a..57eb5b0ab9b81 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -165,3 +165,4 @@ obj-$(CONFIG_RAS) += ras/
obj-$(CONFIG_THUNDERBOLT) += thunderbolt/
obj-$(CONFIG_CORESIGHT) += coresight/
obj-$(CONFIG_ANDROID) += android/
+obj-$(CONFIG_EEPROM) += eeprom/
diff --git a/drivers/eeprom/Kconfig b/drivers/eeprom/Kconfig
new file mode 100644
index 0000000000000..08355a6a548c2
--- /dev/null
+++ b/drivers/eeprom/Kconfig
@@ -0,0 +1,18 @@
+menuconfig EEPROM
+ bool "EEPROM Support"
+ help
+ Support for EEPROM alike devices.
+
+ This framework is designed to provide a generic interface to EEPROM
+ from both the Linux Kernel and the userspace.
+
+ If unsure, say no.
+
+if EEPROM
+
+config EEPROM_DEBUG
+ bool "EEPROM debug support"
+ help
+ Say yes here to enable debugging support.
+
+endif
diff --git a/drivers/eeprom/Makefile b/drivers/eeprom/Makefile
new file mode 100644
index 0000000000000..e130079dd2406
--- /dev/null
+++ b/drivers/eeprom/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for eeprom drivers.
+#
+
+ccflags-$(CONFIG_EEPROM_DEBUG) += -DDEBUG
+
+obj-$(CONFIG_EEPROM) += core.o
+
+# Devices
diff --git a/drivers/eeprom/core.c b/drivers/eeprom/core.c
new file mode 100644
index 0000000000000..8f4e6251bfb1e
--- /dev/null
+++ b/drivers/eeprom/core.c
@@ -0,0 +1,311 @@
+/*
+ * EEPROM framework core.
+ *
+ * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+ * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/device.h>
+#include <linux/eeprom.h>
+#include <linux/export.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+struct eeprom_device {
+ struct device dev;
+ int id;
+ struct regmap *rm;
+ int stride;
+ unsigned long private[];
+};
+
+struct eeprom_cell {
+ struct regmap *rm;
+ loff_t offset;
+ size_t count;
+};
+
+static DEFINE_IDA(eeprom_ida);
+
+static ssize_t bin_attr_eeprom_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr,
+ char *buf, loff_t offset, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct eeprom_device *eeprom = container_of(dev, struct eeprom_device,
+ dev);
+
+ return regmap_bulk_read(eeprom->rm, offset, buf, count/eeprom->stride);
+}
+
+static ssize_t bin_attr_eeprom_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr,
+ char *buf, loff_t offset, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct eeprom_device *eeprom = container_of(dev, struct eeprom_device,
+ dev);
+
+ return regmap_bulk_write(eeprom->rm, offset, buf, count/eeprom->stride);
+}
+
+static struct bin_attribute bin_attr_eeprom = {
+ .attr = {
+ .name = "eeprom",
+ .mode = 0660,
+ },
+ .read = bin_attr_eeprom_read,
+ .write = bin_attr_eeprom_write,
+};
+
+static struct bin_attribute *eeprom_bin_attributes[] = {
+ &bin_attr_eeprom,
+ NULL,
+};
+
+static const struct attribute_group eeprom_bin_group = {
+ .bin_attrs = eeprom_bin_attributes,
+};
+
+static const struct attribute_group *eeprom_dev_groups[] = {
+ &eeprom_bin_group,
+ NULL,
+};
+
+static void eeprom_release(struct device *dev)
+{
+ struct eeprom_device *eeprom = container_of(dev, struct eeprom_device,
+ dev);
+
+ kfree(eeprom);
+}
+
+static struct class eeprom_class = {
+ .name = "eeprom",
+ .dev_groups = eeprom_dev_groups,
+ .dev_release = eeprom_release,
+};
+
+struct eeprom_device *eeprom_alloc(struct device *dev, size_t priv_size)
+{
+ struct eeprom_device *eeprom;
+
+ eeprom = kzalloc(sizeof(struct eeprom_device) + priv_size, GFP_KERNEL);
+ if (!eeprom)
+ return ERR_PTR(-ENOMEM);
+
+ eeprom->id = ida_simple_get(&eeprom_ida, 0, 0, GFP_KERNEL);
+ if (eeprom->id < 0)
+ return ERR_PTR(eeprom->id);
+
+ eeprom->dev.class = &eeprom_class;
+ eeprom->dev.parent = dev;
+ eeprom->dev.of_node = dev ? dev->of_node : NULL;
+ dev_set_name(&eeprom->dev, "eeprom%d", eeprom->id);
+ if (dev) {
+ eeprom->rm = dev_get_regmap(dev, NULL);
+ if (!eeprom->rm)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ eeprom->stride = regmap_get_reg_stride(eeprom->rm);
+
+ }
+
+ device_initialize(&eeprom->dev);
+
+ return eeprom;
+}
+EXPORT_SYMBOL(eeprom_alloc);
+
+struct eeprom_device *eeprom_register(struct device *dev,
+ struct eeprom_config *cfg)
+{
+ struct eeprom_device *eeprom;
+ int rval;
+
+ eeprom = kzalloc(sizeof(*eeprom), GFP_KERNEL);
+ if (!eeprom)
+ return ERR_PTR(-ENOMEM);
+
+ eeprom->id = ida_simple_get(&eeprom_ida, 0, 0, GFP_KERNEL);
+ if (eeprom->id < 0)
+ return ERR_PTR(eeprom->id);
+
+ eeprom->dev.class = &eeprom_class;
+ eeprom->dev.parent = dev;
+ eeprom->dev.of_node = dev ? dev->of_node : NULL;
+ dev_set_name(&eeprom->dev, "eeprom%d", eeprom->id);
+ if (dev) {
+ eeprom->rm = cfg->map;
+ if (!eeprom->rm)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ eeprom->stride = regmap_get_reg_stride(eeprom->rm);
+ }
+
+ device_initialize(&eeprom->dev);
+
+ if (!eeprom)
+ return ERR_PTR(-EINVAL);
+
+ dev_dbg(&eeprom->dev, "Registering eeprom device %s\n",
+ dev_name(&eeprom->dev));
+
+ rval = device_add(&eeprom->dev);
+ if (rval)
+ return ERR_PTR(rval);
+
+ return eeprom;
+}
+EXPORT_SYMBOL(eeprom_register);
+
+int eeprom_unregister(struct eeprom_device *eeprom)
+{
+ device_del(&eeprom->dev);
+ return 0;
+}
+EXPORT_SYMBOL(eeprom_unregister);
+
+static struct eeprom_cell *__eeprom_cell_get(struct device_node *node,
+ int index)
+{
+ struct of_phandle_args args;
+ struct eeprom_cell *cell;
+ int ret;
+ struct regmap *rm;
+
+ ret = of_parse_phandle_with_args(node, "eeproms",
+ "#eeprom-cells", index, &args);
+ if (ret)
+ return ERR_PTR(ret);
+
+ rm = of_get_regmap(args.np);
+
+ if (!rm)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (args.args_count != 2)
+ return ERR_PTR(-EINVAL);
+
+ cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+ if (!cell)
+ return ERR_PTR(-ENOMEM);
+
+ cell->rm = rm;
+ cell->offset = args.args[0];
+ cell->count = args.args[1];
+
+ return cell;
+}
+
+static struct eeprom_cell *__eeprom_cell_get_byname(struct device_node *node,
+ const char *id)
+{
+ int index = 0;
+
+ if (id)
+ index = of_property_match_string(node,
+ "eeprom-names",
+ id);
+ return __eeprom_cell_get(node, index);
+
+}
+
+struct eeprom_cell *eeprom_cell_get(struct device *dev, int index)
+{
+ if (!dev)
+ return ERR_PTR(-EINVAL);
+
+ /* First, attempt to retrieve the cell through the DT */
+ if (dev->of_node)
+ return __eeprom_cell_get(dev->of_node, index);
+
+ /* We don't support anything else yet */
+ return ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL(eeprom_cell_get);
+
+struct eeprom_cell *eeprom_cell_get_byname(struct device *dev, const char *id)
+{
+ if (!dev)
+ return ERR_PTR(-EINVAL);
+
+ if (id && dev->of_node)
+ return __eeprom_cell_get_byname(dev->of_node, id);
+
+ /* We don't support anything else yet */
+ return ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL(eeprom_cell_get_byname);
+
+void eeprom_cell_put(struct eeprom_cell *cell)
+{
+ kfree(cell);
+}
+EXPORT_SYMBOL(eeprom_cell_put);
+
+char *eeprom_cell_read(struct eeprom_cell *cell, ssize_t *len)
+{
+ char *buf;
+ int rc, stride = 1;
+
+ if (!cell || !cell->rm)
+ return ERR_PTR(-EINVAL);
+
+ buf = kzalloc(cell->count, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ stride = regmap_get_reg_stride(cell->rm);
+
+ rc = regmap_bulk_read(cell->rm, cell->offset, buf, cell->count/stride);
+ if (IS_ERR_VALUE(rc)) {
+ kfree(buf);
+ return ERR_PTR(rc);
+ }
+
+ *len = cell->count;
+
+ return buf;
+}
+EXPORT_SYMBOL(eeprom_cell_read);
+
+int eeprom_cell_write(struct eeprom_cell *cell, const char *buf, ssize_t len)
+{
+ int stride;
+
+ if (!cell || !cell->rm)
+ return -EINVAL;
+
+ stride = regmap_get_reg_stride(cell->rm);
+
+ return regmap_bulk_write(cell->rm, cell->offset,
+ buf, cell->count/stride);
+}
+EXPORT_SYMBOL(eeprom_cell_write);
+
+static int eeprom_init(void)
+{
+ return class_register(&eeprom_class);
+}
+
+static void eeprom_exit(void)
+{
+ class_unregister(&eeprom_class);
+}
+
+subsys_initcall(eeprom_init);
+module_exit(eeprom_exit);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com");
+MODULE_DESCRIPTION("EEPROM Driver Core");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/eeprom.h b/include/linux/eeprom.h
new file mode 100644
index 0000000000000..06677b46eecd0
--- /dev/null
+++ b/include/linux/eeprom.h
@@ -0,0 +1,110 @@
+/*
+ * EEPROM framework core.
+ *
+ * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+ * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef _LINUX_EEPROM_H
+#define _LINUX_EEPROM_H
+
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+struct eeprom_device;
+struct eeprom_cell;
+
+/**
+ * A eeprom_config, used for eeprom configuration details.
+ *
+ * @map: Pointer to regmap of the eeprom device.
+ *
+ * This struct can be used to extend non-DT capabilities to the driver.
+ */
+struct eeprom_config {
+ struct regmap *map;
+};
+
+/**
+ * eeprom_register(): Register a eeprom device for given eeprom config.
+ *
+ * @dev: Device that will be interacted with
+ * @config: Configuration for eeprom device.
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct eeprom_device. The eeprom_device will be freed by the
+ * eeprom_unregister().
+ */
+
+struct eeprom_device *eeprom_register(struct device *dev,
+ struct eeprom_config *config);
+/**
+ * eeprom_unregister(): Unregister previously registered eeprom device
+ *
+ * @eeprom: Pointer to previously registered eeprom device.
+ *
+ * The return value will be an non zero on error or a zero on success.
+ */
+int eeprom_unregister(struct eeprom_device *eeprom);
+
+/**
+ * eeprom_cell_get(): Get eeprom cell of device form a given index.
+ *
+ * @dev: Device that will be interacted with
+ * @index: Index of the eeprom cell.
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct eeprom_cell. The eeprom_cell will be freed by the
+ * eeprom_cell_put().
+ */
+struct eeprom_cell *eeprom_cell_get(struct device *dev, int index);
+
+/**
+ * eeprom_cell_get(): Get eeprom cell of device form a given name.
+ *
+ * @dev: Device that will be interacted with
+ * @name: Name of the eeprom cell.
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct eeprom_cell. The eeprom_cell will be freed by the
+ * eeprom_cell_put().
+ */
+struct eeprom_cell *eeprom_cell_get_byname(struct device *dev,
+ const char *name);
+
+/**
+ * eeprom_cell_put(): Release previously allocated eeprom cell.
+ *
+ * @cell: Previously allocated eeprom cell by eeprom_cell_get()
+ * or eeprom_cell_get_byname().
+ */
+void eeprom_cell_put(struct eeprom_cell *cell);
+
+/**
+ * eeprom_cell_read(): Read a given eeprom cell
+ *
+ * @cell: eeprom cell to be read.
+ * @len: pointer to length of cell which will be populated on successful read.
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a char * bufffer. The buffer should be freed by the consumer with a
+ * kfree().
+ */
+char *eeprom_cell_read(struct eeprom_cell *cell, ssize_t *len);
+
+/**
+ * eeprom_cell_write(): Write to a given eeprom cell
+ *
+ * @cell: eeprom cell to be written.
+ * @buf: Buffer to be written.
+ * @len: lenght of buffer to be written to eeprom cell.
+ *
+ * The return value will be an non zero on error or a zero on successful write.
+ */
+int eeprom_cell_write(struct eeprom_cell *cell, const char *buf, ssize_t len);
+
+#endif /* ifndef _LINUX_EEPROM_H */