lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Tue, 29 Mar 2011 13:07:58 +0100
From:	Jamie Iles <jamie@...ieiles.com>
To:	linux-kernel@...r.kernel.org
Cc:	gregkh@...e.de, vapier@...too.org, arnd@...db.de,
	Jamie Iles <jamie@...ieiles.com>
Subject: [RFC PATCHv4 1/4] drivers/otp: add initial support for OTP memory

OTP memory is typically found in some embedded devices and can be
used for storing boot code, cryptographic keys and other persistent
information onchip.  This patch adds a generic layer that devices can
register OTP with and allows access through a set of character
device nodes.

Changes since v3:
	- Allow regions to have labels.
	- Add a word_size attribute to allow users to query the OTP word
	  size.
	- Support the locking of words in OTP through the OTP_LOCK_AREA
	  ioctl() and the lock_word() device operation.
	- Move the read_word/write_word operations into the device
	  operations from region operations.
	- Take a backend module reference in sysfs show/store function.

Changes since v2:
	- Updated ABI documentation.
	- Don't put the OTP regions directly in /sys/bus/otp/devices.
	- Squash the ioctl addition patch into this one.
	- Add CONFIG_OTP_WRITE_ENABLE to allow OTP support to be build
	  without allowing write accesses to OTP.
	- Add the "ecc" redundancy format.
	- Permit registration of multiple OTP devices.

Signed-off-by: Jamie Iles <jamie@...ieiles.com>
---
 Documentation/ABI/testing/sysfs-bus-otp |   93 +++
 Documentation/ioctl/ioctl-number.txt    |    1 +
 drivers/Kconfig                         |    2 +
 drivers/Makefile                        |    1 +
 drivers/otp/Kconfig                     |   28 +
 drivers/otp/Makefile                    |    1 +
 drivers/otp/otp.c                       | 1051 +++++++++++++++++++++++++++++++
 include/linux/otp.h                     |  305 +++++++++
 8 files changed, 1482 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-otp
 create mode 100644 drivers/otp/Kconfig
 create mode 100644 drivers/otp/Makefile
 create mode 100644 drivers/otp/otp.c
 create mode 100644 include/linux/otp.h

diff --git a/Documentation/ABI/testing/sysfs-bus-otp b/Documentation/ABI/testing/sysfs-bus-otp
new file mode 100644
index 0000000..02ff12a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-otp
@@ -0,0 +1,93 @@
+What:           /sys/bus/otp/
+Date:           March 2011
+KernelVersion:  2.6.40+
+Contact:        Jamie Iles <jamie@...ieiles.com>
+Description:
+		The otp (One Time Programmable memory) bus presents a number
+		of devices where each device represents and OTP device in the
+		system.  An OTP device may have a number of regions (which can
+		be thought of as partitions) and the regions are child
+		devices.  Each region will create a device node which allows
+		the region to be written with read()/write() calls and the
+		device on the bus has attributes for controlling the
+		redundancy format and getting the region size.
+
+What:		/sys/bus/devices/otp#/
+Date:		March 2011
+KernelVersion:	2.6.40+
+Contact:	Jamie Iles <jamie@...ieiles.com>
+Description:
+		Each otp# directory represents an OTP device in the system.
+		The device has attributes that affect all of the regions in
+		the device such as write_enable and the number of regions.
+
+What:		/sys/bus/devices/otp#/otp#N
+Date:		March 2011
+KernelVersion:	2.6.40+
+Contact:	Jamie Iles <jamie@...ieiles.com>
+Description:
+		Each otp#N directory represents a region in the otp# device.
+		A region may be read from/written to through the character
+		device node named after the otp#N name.
+
+What:		/sys/bus/devices/otp#/write_enable
+Date:		March 2011
+KernelVersion:	2.6.40+
+Contact:	Jamie Iles <jamie@...ieiles.com>
+Description:
+		This file controls whether the OTP device can be written to.
+		If set to "enabled" then the regions may be written, the
+		number of regions may be changed and the format of any region
+		may be changed.
+
+What:		/sys/bus/devices/otp#/num_regions
+Date:		March 2011
+KernelVersion:	2.6.40+
+Contact:	Jamie Iles <jamie@...ieiles.com>
+Description:
+		This file controls the number of regions in the OTP device.
+		Valid values are 1, 2, 4 and 8. The number of regions may be
+		increased but never decreased. Increasing the number of
+		regions will create new devices on the otp bus.
+
+What:		/sys/bus/devices/otp#/strict_programming
+Date:		March 2011
+KernelVersion:	2.6.40+
+Contact:	Jamie Iles <jamie@...ieiles.com>
+Description:
+		This file indicates whether all words in a redundant format
+		must be programmed correctly to indicate success.  For
+		example, using the "redundant format", the same word is
+		written to two locations and wire-OR'd when reading back.
+		Disabling this will mean that programming will be considered a
+		success if the word can be read back correctly in its
+		redundant format even if there are bit errors when programming
+		the extra redundancy words.
+
+What:           /sys/bus/otp/devices/otp#/otp#N/format
+Date:           March 2011
+KernelVersion:  2.6.40+
+Contact:        Jamie Iles <jamie@...ieiles.com>
+Description:
+                The redundancy format of the region. Valid values are:
+			- single-ended (1 bit of storage per data bit).
+			- redundant (2 bits of storage, wire-OR'd per data
+			  bit).
+			- differential (2 bits of storage, differential
+			  voltage per data bit).
+			- differential-redundant (4 bits of storage, combining
+			  redundant and differential).
+			- ecc (transparent to the user)
+		Depending on the device type, it may be possible to increase
+		redundancy of a region but care will be needed if there is
+		data already in the region.
+
+What:           /sys/bus/otp/devices/otp#/otp#N/size
+Date:           March 2011
+KernelVersion:  2.6.40+
+Contact:        Jamie Iles <jamie@...ieiles.com>
+Description:
+                The effective storage size of the region. This is the amount
+		of data that a user can store in the region taking into
+		account the number of regions and the redundancy format of the
+		region itself.
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index a0a5d82..d2953d2 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -150,6 +150,7 @@ Code  Seq#(hex)	Include File		Comments
 'M'	00-0F	drivers/video/fsl-diu-fb.h	conflict!
 'N'	00-1F	drivers/usb/scanner.h
 'O'     00-06   mtd/ubi-user.h		UBI
+'O'	10-1F	include/linux/otp.h	OTP
 'P'	all	linux/soundcard.h	conflict!
 'P'	60-6F	sound/sscape_ioctl.h	conflict!
 'P'	00-0F	drivers/usb/class/usblp.c	conflict!
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 177c7d1..77da156 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -119,4 +119,6 @@ source "drivers/platform/Kconfig"
 source "drivers/clk/Kconfig"
 
 source "drivers/hwspinlock/Kconfig"
+
+source "drivers/otp/Kconfig"
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 3f135b6..6ae2f815 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -119,3 +119,4 @@ obj-y				+= ieee802154/
 obj-y				+= clk/
 
 obj-$(CONFIG_HWSPINLOCK)	+= hwspinlock/
+obj-$(CONFIG_OTP)		+= otp/
diff --git a/drivers/otp/Kconfig b/drivers/otp/Kconfig
new file mode 100644
index 0000000..a1ce310
--- /dev/null
+++ b/drivers/otp/Kconfig
@@ -0,0 +1,28 @@
+#
+# OTP memory configuration
+#
+
+menuconfig OTP
+	bool "OTP memory support"
+	help
+	  Say Y here to support OTP (One Time Programmable) memory.  This
+	  memory can commonly be used to store boot code, cryptographic keys
+	  and other persistent data.  This memory is typically a few KBytes in
+	  size and different devices may have different characteristics.  This
+	  provides a character device interface to these devices to allow the
+	  OTP to be programmed and read.
+
+if OTP
+
+config OTP_WRITE_ENABLE
+	bool "Enable writing support of OTP pages"
+	default n
+	help
+	  If you say Y here, you will enable support for writing of the
+	  OTP pages.  This is dangerous by nature as you can only program
+	  the pages once, so only enable this option when you actually
+	  need it so as to not inadvertently clobber data.
+
+	  If unsure, say N.
+
+endif
diff --git a/drivers/otp/Makefile b/drivers/otp/Makefile
new file mode 100644
index 0000000..84fd03e
--- /dev/null
+++ b/drivers/otp/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_OTP)	+= otp.o
diff --git a/drivers/otp/otp.c b/drivers/otp/otp.c
new file mode 100644
index 0000000..5176fbc
--- /dev/null
+++ b/drivers/otp/otp.c
@@ -0,0 +1,1051 @@
+/*
+ * Copyright (c) 2011 Picochip Ltd., Jamie Iles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * All enquiries to support@...ochip.com
+ */
+#define pr_fmt(fmt) "otp: " fmt
+
+#include <linux/cdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/otp.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/uaccess.h>
+
+/* We'll allow OTP devices to be named otpa-otpz. */
+#define MAX_OTP_DEVICES		26
+
+static unsigned long registered_otp_map[BITS_TO_LONGS(MAX_OTP_DEVICES)];
+static DEFINE_MUTEX(otp_register_mutex);
+
+/*
+ * The otp currently works in 64 bit words. When we are programming or
+ * reading, everything is done with 64 bit word addresses.
+ */
+#define OTP_WORD_SIZE			8
+
+bool otp_strict_programming_enabled(struct otp_device *otp_dev)
+{
+	return otp_dev->strict_programming;
+}
+EXPORT_SYMBOL_GPL(otp_strict_programming_enabled);
+
+bool otp_write_enabled(struct otp_device *otp_dev)
+{
+#ifdef CONFIG_OTP_WRITE_ENABLE
+	return otp_dev->write_enable;
+#else /* CONFIG_OTP_WRITE_ENABLE */
+	return false;
+#endif /* CONFIG_OTP_WRITE_ENABLE */
+}
+EXPORT_SYMBOL_GPL(otp_write_enabled);
+
+static const char *otp_format_names[OTP_REDUNDANCY_NR_FMTS] = {
+	[OTP_REDUNDANCY_FMT_SINGLE_ENDED]	= "single-ended",
+	[OTP_REDUNDANCY_FMT_REDUNDANT]		= "redundant",
+	[OTP_REDUNDANCY_FMT_DIFFERENTIAL]	= "differential",
+	[OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT] = "differential-redundant",
+	[OTP_REDUNDANCY_FMT_ECC]		= "ecc",
+};
+
+static const char *otp_fmt_to_string(enum otp_redundancy_fmt fmt)
+{
+	if (fmt < 0 || fmt >= OTP_REDUNDANCY_NR_FMTS)
+		return NULL;
+
+	return otp_format_names[fmt];
+}
+
+static int otp_string_to_fmt(const char *name)
+{
+	int i;
+
+	for (i = 0; i < OTP_REDUNDANCY_NR_FMTS; ++i)
+		if (sysfs_streq(name, otp_format_names[i]))
+			return i;
+
+	return -1;
+}
+
+static ssize_t otp_dev_get_and_lock(struct otp_device *otp_dev)
+{
+	if (mutex_lock_interruptible(&otp_dev->lock))
+		return -ERESTARTSYS;
+
+	if (!try_module_get(otp_dev->ops->owner)) {
+		mutex_unlock(&otp_dev->lock);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void otp_dev_put_and_unlock(struct otp_device *otp_dev)
+{
+	module_put(otp_dev->ops->owner);
+	mutex_unlock(&otp_dev->lock);
+}
+
+static ssize_t otp_format_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct otp_region *region = to_otp_region(dev);
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	enum otp_redundancy_fmt fmt;
+	const char *fmt_string;
+	ssize_t err;
+
+	err = otp_dev_get_and_lock(otp_dev);
+	if (err)
+		return err;
+
+	if (region->ops->get_fmt(region))
+		fmt = region->ops->get_fmt(region);
+	else
+		fmt = OTP_REDUNDANCY_FMT_SINGLE_ENDED;
+
+	otp_dev_put_and_unlock(otp_dev);
+
+	fmt_string = otp_fmt_to_string(fmt);
+	if (!fmt_string)
+		return -EINVAL;
+
+	return sprintf(buf, "%s\n", fmt_string);
+}
+
+static ssize_t otp_format_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t len)
+{
+	ssize_t err = 0;
+	struct otp_region *region = to_otp_region(dev);
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	enum otp_redundancy_fmt new_fmt;
+
+	if (!region->ops->set_fmt)
+		return -EOPNOTSUPP;
+
+	err = otp_dev_get_and_lock(otp_dev);
+	if (err)
+		return err;
+
+	/* This is irreversible so don't make it too easy to break it! */
+	if (!otp_write_enabled(otp_dev)) {
+		err = -EPERM;
+		goto out;
+	}
+
+	new_fmt = otp_string_to_fmt(buf);
+	if (new_fmt < 0) {
+		err = -EINVAL;
+		goto out;
+	}
+	region->ops->set_fmt(region, new_fmt);
+
+out:
+	otp_dev_put_and_unlock(otp_dev);
+
+	return err ?: len;
+}
+static DEVICE_ATTR(format, S_IRUSR | S_IWUSR, otp_format_show,
+		   otp_format_store);
+
+static ssize_t otp_size_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct otp_region *region = to_otp_region(dev);
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	size_t region_sz;
+	ssize_t err;
+
+	err = otp_dev_get_and_lock(otp_dev);
+	if (err)
+		return err;
+
+	region_sz = region->ops->get_size(region);
+
+	otp_dev_put_and_unlock(otp_dev);
+
+	return sprintf(buf, "%zu\n", region_sz);
+}
+static DEVICE_ATTR(size, S_IRUSR, otp_size_show, NULL);
+
+static ssize_t otp_label_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	struct otp_region *region = to_otp_region(dev);
+
+	return sprintf(buf, "%s\n", region->label);
+}
+static DEVICE_ATTR(label, S_IRUSR, otp_label_show, NULL);
+
+static struct bus_type otp_bus_type = {
+	.name		= "otp",
+};
+
+static struct attribute *region_attrs[] = {
+	&dev_attr_format.attr,
+	&dev_attr_size.attr,
+	&dev_attr_label.attr,
+	NULL,
+};
+
+static const struct attribute_group region_attr_group = {
+	.attrs		= region_attrs,
+};
+
+const struct attribute_group *region_attr_groups[] = {
+	&region_attr_group,
+	NULL,
+};
+
+static struct device_type region_type = {
+	.name		= "region",
+	.groups		= region_attr_groups,
+};
+
+static ssize_t otp_attr_store_enabled(struct device *dev, const char *buf,
+				      size_t len, int *param)
+{
+	ssize_t err = 0;
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	err = otp_dev_get_and_lock(otp_dev);
+	if (err)
+		return err;
+
+	if (sysfs_streq(buf, "enabled"))
+		*param = 1;
+	else if (sysfs_streq(buf, "disabled"))
+		*param = 0;
+	else
+		err = -EINVAL;
+
+	otp_dev_put_and_unlock(otp_dev);
+
+	return err ?: len;
+}
+
+static ssize_t otp_attr_show_enabled(struct device *dev, char *buf, int param)
+{
+	ssize_t ret;
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	ret = otp_dev_get_and_lock(otp_dev);
+	if (ret)
+		return ret;
+
+	ret = sprintf(buf, "%s\n", param ? "enabled" : "disabled");
+
+	otp_dev_put_and_unlock(otp_dev);
+
+	return ret;
+}
+
+/*
+ * Show the current write enable state of the otp. Users can only program the
+ * otp when this shows 'enabled'.
+ */
+static ssize_t otp_we_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	return otp_attr_show_enabled(dev, buf, otp_dev->write_enable);
+}
+
+/*
+ * Set the write enable state of the otp. 'enabled' will enable programming
+ * and 'disabled' will prevent further programming from occurring. On loading
+ * the module, this will default to 'disabled'.
+ */
+#ifdef CONFIG_OTP_WRITE_ENABLE
+static ssize_t otp_we_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t len)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	return otp_attr_store_enabled(dev, buf, len, &otp_dev->write_enable);
+}
+#else
+static ssize_t otp_we_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t len)
+{
+	if (!sysfs_streq(buf, "disabled"))
+		return -EACCES;
+	return len;
+}
+#endif
+static DEVICE_ATTR(write_enable, S_IRUSR | S_IWUSR, otp_we_show, otp_we_store);
+
+/*
+ * Show the current strict programming state of the otp. If set to "enabled",
+ * then when programming, all raw words must program correctly to succeed. If
+ * disabled, then as long as the word reads back correctly in the redundant
+ * mode, then some bits may be allowed to be incorrect in the raw words.
+ */
+static ssize_t otp_strict_programming_show(struct device *dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	return otp_attr_show_enabled(dev, buf, otp_dev->strict_programming);
+}
+
+/*
+ * Set the current strict programming state of the otp. If set to "enabled",
+ * then when programming, all raw words must program correctly to succeed. If
+ * disabled, then as long as the word reads back correctly in the redundant
+ * mode, then some bits may be allowed to be incorrect in the raw words.
+ */
+static ssize_t otp_strict_programming_store(struct device *dev,
+					    struct device_attribute *attr,
+					    const char *buf, size_t len)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	return otp_attr_store_enabled(dev, buf, len,
+				      &otp_dev->strict_programming);
+}
+static DEVICE_ATTR(strict_programming, S_IRUSR | S_IWUSR,
+		   otp_strict_programming_show, otp_strict_programming_store);
+
+static ssize_t otp_num_regions_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+	ssize_t nr_regions, err;
+
+	err = otp_dev_get_and_lock(otp_dev);
+	if (err)
+		return err;
+	nr_regions = otp_dev->ops->get_nr_regions(otp_dev);
+	otp_dev_put_and_unlock(otp_dev);
+
+	if (nr_regions < 0)
+		return nr_regions;
+
+	return sprintf(buf, "%zd\n", nr_regions);
+}
+
+static ssize_t otp_num_regions_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t len)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+	unsigned long nr_regions;
+	ssize_t err = 0;
+
+	if (!otp_dev->ops->set_nr_regions)
+		return -EOPNOTSUPP;
+
+	err = strict_strtoul(buf, 0, &nr_regions);
+	if (err)
+		return err;
+
+	err = otp_dev_get_and_lock(otp_dev);
+	if (err)
+		return err;
+
+	if (!otp_write_enabled(otp_dev)) {
+		err = -EPERM;
+		goto out;
+	}
+
+	err = otp_dev->ops->set_nr_regions(otp_dev, nr_regions);
+
+out:
+	otp_dev_put_and_unlock(otp_dev);
+
+	return err ?: len;
+}
+static DEVICE_ATTR(num_regions, S_IRUSR | S_IWUSR, otp_num_regions_show,
+		   otp_num_regions_store);
+
+static ssize_t otp_word_size_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	return sprintf(buf, "%zu\n", otp_dev->word_sz);
+}
+static DEVICE_ATTR(word_size, S_IRUSR, otp_word_size_show, NULL);
+
+static struct attribute *otp_attrs[] = {
+	&dev_attr_strict_programming.attr,
+	&dev_attr_num_regions.attr,
+	&dev_attr_write_enable.attr,
+	&dev_attr_word_size.attr,
+	NULL,
+};
+
+static const struct attribute_group otp_attr_group = {
+	.attrs		= otp_attrs,
+};
+
+static const struct attribute_group *otp_attr_groups[] = {
+	&otp_attr_group,
+	NULL,
+};
+
+static struct device_type otp_type = {
+	.name		= "otp",
+	.groups		= otp_attr_groups,
+};
+
+static void otp_dev_release(struct device *dev)
+{
+	struct otp_device *otp_dev = to_otp_device(dev);
+
+	mutex_lock(&otp_register_mutex);
+	clear_bit(otp_dev->dev_nr, registered_otp_map);
+	mutex_unlock(&otp_register_mutex);
+	unregister_chrdev_region(otp_dev->devno, otp_dev->max_regions);
+	kfree(otp_dev);
+}
+
+struct otp_device *otp_device_alloc(struct device *dev,
+				    const struct otp_device_ops *ops,
+				    size_t size, size_t word_sz,
+				    unsigned max_regions,
+				    unsigned long flags)
+{
+	struct otp_device *otp_dev;
+	int err = -EBUSY, otp_nr;
+
+	mutex_lock(&otp_register_mutex);
+	otp_nr = find_first_zero_bit(registered_otp_map, MAX_OTP_DEVICES);
+	if (otp_nr < MAX_OTP_DEVICES)
+		set_bit(otp_nr, registered_otp_map);
+	mutex_unlock(&otp_register_mutex);
+
+	if (otp_nr == MAX_OTP_DEVICES)
+		goto out;
+
+	if (word_sz != OTP_WORD_SIZE) {
+		dev_err(dev, "otp word size of %zu is not supported\n",
+			word_sz);
+		err = -EINVAL;
+		goto out_clear;
+	}
+
+	if (!dev || !get_device(dev)) {
+		err = -ENODEV;
+		goto out_clear;
+	}
+
+	otp_dev = kzalloc(sizeof(*otp_dev), GFP_KERNEL);
+	if (!otp_dev) {
+		err = -ENOMEM;
+		goto out_put;
+	}
+
+	err = alloc_chrdev_region(&otp_dev->devno, 0, max_regions, "otp");
+	if (err)
+		goto out_put;
+
+	INIT_LIST_HEAD(&otp_dev->regions);
+	mutex_init(&otp_dev->lock);
+	otp_dev->ops		= ops;
+	otp_dev->dev.release	= otp_dev_release;
+	otp_dev->dev.bus	= &otp_bus_type;
+	otp_dev->dev.type	= &otp_type;
+	otp_dev->dev.parent	= dev;
+	otp_dev->size		= size;
+	otp_dev->max_regions	= max_regions;
+	otp_dev->dev_nr		= otp_nr;
+	otp_dev->flags		= flags;
+	otp_dev->word_sz	= word_sz;
+	dev_set_name(&otp_dev->dev, "otp%c", 'a' + otp_dev->dev_nr);
+
+	otp_dev = otp_dev;
+	err = device_register(&otp_dev->dev);
+	if (err) {
+		dev_err(&otp_dev->dev, "couldn't add device\n");
+		goto out_unalloc_chrdev;
+	}
+	pr_info("device %s of %zu bytes registered\n", ops->name, size);
+	return otp_dev;
+
+out_unalloc_chrdev:
+	unregister_chrdev_region(otp_dev->devno, otp_dev->max_regions);
+out_put:
+	if (dev)
+		put_device(dev);
+out_clear:
+	clear_bit(otp_nr, registered_otp_map);
+out:
+	return err ? ERR_PTR(err) : otp_dev;
+}
+EXPORT_SYMBOL_GPL(otp_device_alloc);
+
+void otp_device_unregister(struct otp_device *dev)
+{
+	struct otp_region *region, *tmp;
+
+	list_for_each_entry_safe(region, tmp, &dev->regions, head)
+		otp_region_unregister(region);
+	device_unregister(&dev->dev);
+}
+EXPORT_SYMBOL_GPL(otp_device_unregister);
+
+static void otp_region_release(struct device *dev)
+{
+	struct otp_region *region = to_otp_region(dev);
+
+	kfree(region->label);
+	cdev_del(&region->cdev);
+	list_del(&region->head);
+	kfree(region);
+}
+
+static int otp_open(struct inode *inode, struct file *filp)
+{
+	struct otp_region *region;
+	struct otp_device *otp_dev;
+	int ret = 0;
+
+	region = container_of(inode->i_cdev, struct otp_region, cdev);
+	otp_dev = to_otp_device(region->dev.parent);
+
+	if (!try_module_get(otp_dev->ops->owner)) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (!get_device(&region->dev)) {
+		ret = -ENODEV;
+		goto out_put_module;
+	}
+	filp->private_data = region;
+
+	goto out;
+
+out_put_module:
+	module_put(otp_dev->ops->owner);
+out:
+	return ret;
+}
+
+static int otp_release(struct inode *inode, struct file *filp)
+{
+	struct otp_region *region;
+	struct otp_device *otp_dev;
+
+	region = container_of(inode->i_cdev, struct otp_region, cdev);
+	otp_dev = to_otp_device(region->dev.parent);
+
+	region = filp->private_data;
+	put_device(&region->dev);
+	module_put(otp_dev->ops->owner);
+
+	return 0;
+}
+
+#ifdef CONFIG_OTP_WRITE_ENABLE
+/*
+ * Write to the otp memory from a userspace buffer. This requires that the
+ * write_enable attribute is set to "enabled" in
+ * /sys/bus/otp/devices/otp#/write_enable
+ *
+ * If writing is not enabled, this should return -EPERM.
+ *
+ * The write method takes a buffer from userspace and writes it into the
+ * corresponding bits of the otp. The current file offset refers to the byte
+ * address of the words in the otp region that should be written to.
+ * Therefore:
+ *
+ *	- we may have to do a read-modify-write to get up to an aligned
+ *	boundary, then
+ *	- do a series of word writes, followed by,
+ *	- an optional final read-modify-write if there are less than
+ *	OTP_WORD_SIZE bytes left to write.
+ *
+ * After writing, the file offset will be updated to the next byte address. If
+ * one word fails to write then the writing is aborted at that point and no
+ * further data is written. If the user can carry on then they may call
+ * write(2) again with an updated offset.
+ */
+static ssize_t otp_write(struct file *filp, const char __user *buf, size_t len,
+			 loff_t *offs)
+{
+	ssize_t ret = 0;
+	u64 word;
+	ssize_t written = 0;
+	struct otp_region *region = filp->private_data;
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	unsigned pos = (unsigned)*offs;
+	enum otp_redundancy_fmt fmt;
+
+	if (mutex_lock_interruptible(&otp_dev->lock))
+		return -ERESTARTSYS;
+
+	if (region->ops->get_fmt)
+		fmt = region->ops->get_fmt(region);
+	else
+		fmt = OTP_REDUNDANCY_FMT_SINGLE_ENDED;
+
+	if (*offs >= region->ops->get_size(region)) {
+		ret = -ENOSPC;
+		goto out;
+	}
+
+	if (!otp_write_enabled(otp_dev)) {
+		ret = -EPERM;
+		goto out;
+	}
+
+	len = min_t(size_t, len, region->ops->get_size(region) - *offs);
+	if (!len) {
+		ret = 0;
+		goto out;
+	}
+
+	if ((otp_dev->flags & OTP_CAPS_NO_SUBWORD_WRITE) &&
+	    ((len & otp_dev->word_sz) || (pos & otp_dev->word_sz))) {
+		dev_info(&otp_dev->dev, "unable to perform partial word writes\n");
+		ret = -EMSGSIZE;
+		goto out;
+	}
+
+	if (otp_dev->ops->set_fmt)
+		otp_dev->ops->set_fmt(otp_dev, fmt);
+
+	if (pos & (OTP_WORD_SIZE - 1)) {
+		/*
+		 * We're not currently on a otp word aligned boundary so we
+		 * need to do a read-modify-write.
+		 */
+		unsigned word_addr = pos / OTP_WORD_SIZE;
+		unsigned offset = pos % OTP_WORD_SIZE;
+		size_t bytes = min_t(size_t, OTP_WORD_SIZE - offset, len);
+
+		ret = otp_dev->ops->read_word(otp_dev, region, word_addr,
+					      &word);
+		if (ret)
+			goto out;
+
+		if (copy_from_user((void *)(&word) + offset, buf, bytes)) {
+			ret = -EFAULT;
+			goto out;
+		}
+
+		ret = otp_dev->ops->write_word(otp_dev, region, word_addr,
+					       word);
+		if (ret)
+			goto out;
+
+		written += bytes;
+		len -= bytes;
+		buf += bytes;
+		pos += bytes;
+	}
+
+	/*
+	 * We're now aligned to OTP word byte boundary so we can simply copy
+	 * words from userspace and write them into the otp.
+	 */
+	while (len >= OTP_WORD_SIZE) {
+		if (copy_from_user(&word, buf, OTP_WORD_SIZE)) {
+			ret = -EFAULT;
+			goto out;
+		}
+
+		ret = otp_dev->ops->write_word(otp_dev, region,
+					       pos / OTP_WORD_SIZE, word);
+		if (ret)
+			goto out;
+
+		written += OTP_WORD_SIZE;
+		len -= OTP_WORD_SIZE;
+		buf += OTP_WORD_SIZE;
+		pos += OTP_WORD_SIZE;
+	}
+
+	/*
+	 * We might have less than a full OTP word left so we'll need to do
+	 * another read-modify-write.
+	 */
+	if (len) {
+		ret = otp_dev->ops->read_word(otp_dev, region,
+					      pos / OTP_WORD_SIZE, &word);
+		if (ret)
+			goto out;
+
+		if (copy_from_user(&word, buf, len)) {
+			ret = -EFAULT;
+			goto out;
+		}
+
+		ret = otp_dev->ops->write_word(otp_dev, region,
+					       pos / OTP_WORD_SIZE, word);
+		if (ret)
+			goto out;
+
+		written += len;
+		buf += len;
+		pos += len;
+		len = 0;
+	}
+
+	*offs += written;
+
+out:
+	mutex_unlock(&otp_dev->lock);
+	return ret ?: written;
+}
+#else /* CONFIG_OTP_WRITE_ENABLE */
+static ssize_t otp_write(struct file *filp, const char __user *buf, size_t len,
+			 loff_t *offs)
+{
+	return -EACCES;
+}
+#endif /* CONFIG_OTP_WRITE_ENABLE */
+
+/*
+ * Lock an area of an OTP region down.  We can lock multiple words in a
+ * request and we do this one word at a time.  It is not possible to lock a
+ * sub-word area.
+ */
+static long otp_lock_area(struct otp_region *region, unsigned long arg)
+{
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	struct otp_lock_area_info info;
+	size_t n, words_locked = 0;
+	unsigned long word_addr;
+	long ret;
+	ssize_t region_sz = region->ops->get_size(region);
+
+	if (!otp_write_enabled(otp_dev))
+		return -EPERM;
+
+	if (!otp_dev->ops->lock_word)
+		return -EOPNOTSUPP;
+
+	if (copy_from_user(&info, (void __user *)arg, sizeof(info)))
+		return -EFAULT;
+
+	if ((info.byte_addr & otp_dev->word_sz) ||
+	    (info.byte_count & otp_dev->word_sz) ||
+	    (info.byte_addr + info.byte_count > (size_t)region_sz))
+		return -EMSGSIZE;
+
+	for (n = 0, word_addr = info.byte_addr / otp_dev->word_sz;
+	     n < info.byte_count / otp_dev->word_sz; ++n, ++word_addr) {
+		ret = otp_dev->ops->lock_word(otp_dev, region, word_addr);
+		if (ret) {
+			dev_warn(&region->dev, "failed to lock word %lu\n",
+				 word_addr);
+			break;
+		}
+		++words_locked;
+	}
+
+	info.byte_count = words_locked * otp_dev->word_sz;
+	if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+		ret = -EFAULT;
+
+	return ret;
+}
+
+static long otp_ioctl(struct file *filp, unsigned cmd, unsigned long arg)
+{
+	struct otp_region *region = filp->private_data;
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	long ret = -ENOTTY;
+
+	if (mutex_lock_interruptible(&otp_dev->lock))
+		return -ERESTARTSYS;
+
+	switch (cmd) {
+	case OTP_LOCK_AREA:
+		ret = otp_lock_area(region, arg);
+		break;
+
+	default:
+		ret = -ENOIOCTLCMD;
+	}
+
+	mutex_unlock(&otp_dev->lock);
+
+	return ret;
+}
+
+/*
+ * Read an otp region. This switches the otp into the appropriate redundancy
+ * format so we can simply read from the beginning of the region and copy it
+ * into the user buffer.
+ */
+static ssize_t otp_read(struct file *filp, char __user *buf, size_t len,
+			loff_t *offs)
+{
+	ssize_t ret = 0;
+	u64 word;
+	ssize_t bytes_read = 0;
+	struct otp_region *region = filp->private_data;
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	unsigned pos = (unsigned)*offs;
+	enum otp_redundancy_fmt fmt;
+
+	if (mutex_lock_interruptible(&otp_dev->lock))
+		return -ERESTARTSYS;
+
+	if (region->ops->get_fmt)
+		fmt = region->ops->get_fmt(region);
+	else
+		fmt = OTP_REDUNDANCY_FMT_SINGLE_ENDED;
+
+	if (*offs >= region->ops->get_size(region)) {
+		ret = 0;
+		goto out;
+	}
+
+	len = min(len, region->ops->get_size(region) - (unsigned)*offs);
+	if (!len) {
+		ret = 0;
+		goto out;
+	}
+
+	if (otp_dev->ops->set_fmt)
+		otp_dev->ops->set_fmt(otp_dev, fmt);
+
+	if (pos & (OTP_WORD_SIZE - 1)) {
+		/*
+		 * We're not currently on an 8 byte boundary so we need to
+		 * read to a bounce buffer then do the copy_to_user() with an
+		 * offset.
+		 */
+		unsigned word_addr = pos / OTP_WORD_SIZE;
+		unsigned offset = pos % OTP_WORD_SIZE;
+		size_t bytes = min_t(size_t, OTP_WORD_SIZE - offset, len);
+
+		ret = otp_dev->ops->read_word(otp_dev, region, word_addr,
+					      &word);
+		if (ret)
+			goto out;
+
+		if (copy_to_user(buf, (void *)(&word) + offset, bytes)) {
+			ret = -EFAULT;
+			goto out;
+		}
+
+		bytes_read += bytes;
+		len -= bytes;
+		buf += bytes;
+		pos += bytes;
+	}
+
+	/*
+	 * We're now aligned to an 8 byte boundary so we can simply copy words
+	 * from the bounce buffer directly with a copy_to_user().
+	 */
+	while (len >= OTP_WORD_SIZE) {
+		ret = otp_dev->ops->read_word(otp_dev, region,
+					      pos / OTP_WORD_SIZE, &word);
+		if (ret)
+			goto out;
+
+		if (copy_to_user(buf, &word, OTP_WORD_SIZE)) {
+			ret = -EFAULT;
+			goto out;
+		}
+
+		bytes_read += OTP_WORD_SIZE;
+		len -= OTP_WORD_SIZE;
+		buf += OTP_WORD_SIZE;
+		pos += OTP_WORD_SIZE;
+	}
+
+	/*
+	 * We might have less than 8 bytes left so we'll need to do another
+	 * copy_to_user() but with a partial word length.
+	 */
+	if (len) {
+		ret = otp_dev->ops->read_word(otp_dev, region,
+					      pos / OTP_WORD_SIZE, &word);
+		if (ret)
+			goto out;
+
+		if (copy_to_user(buf, &word, len)) {
+			ret = -EFAULT;
+			goto out;
+		}
+
+		bytes_read += len;
+		buf += len;
+		pos += len;
+		len = 0;
+	}
+
+	*offs += bytes_read;
+
+out:
+	mutex_unlock(&otp_dev->lock);
+	return ret ?: bytes_read;
+}
+
+/*
+ * Seek to a specified position in the otp region. This can be used if
+ * userspace doesn't have pread()/pwrite() and needs to write to a specified
+ * offset in the otp.
+ */
+static loff_t otp_llseek(struct file *filp, loff_t offs, int origin)
+{
+	struct otp_region *region = filp->private_data;
+	struct otp_device *otp_dev = to_otp_device(region->dev.parent);
+	int ret = 0;
+	loff_t end;
+
+	if (mutex_lock_interruptible(&otp_dev->lock))
+		return -ERESTARTSYS;
+
+	switch (origin) {
+	case SEEK_CUR:
+		if (filp->f_pos + offs < 0 ||
+		    filp->f_pos + offs >= region->ops->get_size(region))
+			ret = -EINVAL;
+		else
+			filp->f_pos += offs;
+		break;
+
+	case SEEK_SET:
+		if (offs < 0 || offs >= region->ops->get_size(region))
+			ret = -EINVAL;
+		else
+			filp->f_pos = offs;
+		break;
+
+	case SEEK_END:
+		end = region->ops->get_size(region) - 1;
+		if (end + offs < 0 || end + offs >= end)
+			ret = -EINVAL;
+		else
+			filp->f_pos = end + offs;
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&otp_dev->lock);
+
+	return ret ?: filp->f_pos;
+}
+
+static const struct file_operations otp_fops = {
+	.owner		= THIS_MODULE,
+	.open		= otp_open,
+	.release	= otp_release,
+	.write		= otp_write,
+	.read		= otp_read,
+	.llseek		= otp_llseek,
+	.unlocked_ioctl	= otp_ioctl,
+	.compat_ioctl	= otp_ioctl,
+};
+
+struct otp_region *__otp_region_alloc(struct otp_device *dev,
+				      const struct otp_region_ops *ops,
+				      int region_nr, const char *fmt,
+				      va_list vargs)
+{
+	struct otp_region *region;
+	int err = 0;
+	dev_t devno = MKDEV(MAJOR(dev->devno), region_nr + 1);
+
+	region = kzalloc(sizeof(*region), GFP_KERNEL);
+	if (!region) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	region->label		= kvasprintf(GFP_KERNEL, fmt, vargs);
+	if (!region->label)
+		goto out_free;
+
+	region->ops		= ops;
+	region->region_nr	= region_nr;
+	region->dev.parent	= &dev->dev;
+	region->dev.release	= otp_region_release;
+	region->dev.devt	= devno;
+	region->dev.type	= &region_type;
+	dev_set_name(&region->dev, "otpa%d", region_nr + 1);
+
+	cdev_init(&region->cdev, &otp_fops);
+	err = cdev_add(&region->cdev, devno, 1);
+	if (err) {
+		dev_err(&region->dev, "couldn't create cdev\n");
+		goto out_free_name;
+	}
+
+	err = device_register(&region->dev);
+	if (err) {
+		dev_err(&region->dev, "couldn't add device\n");
+		goto out_cdev_del;
+	}
+
+	list_add_tail(&region->head, &dev->regions);
+	goto out;
+
+out_cdev_del:
+	cdev_del(&region->cdev);
+out_free_name:
+	kfree(region->label);
+out_free:
+	kfree(region);
+out:
+	return err ? ERR_PTR(err) : region;
+}
+
+struct otp_region *otp_region_alloc_unlocked(struct otp_device *dev,
+					     const struct otp_region_ops *ops,
+					     int region_nr, const char *fmt,
+					     ...)
+{
+	struct otp_region *ret;
+	va_list vargs;
+
+	va_start(vargs, fmt);
+	ret = __otp_region_alloc(dev, ops, region_nr, fmt, vargs);
+	va_end(vargs);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(otp_region_alloc_unlocked);
+
+struct otp_region *otp_region_alloc(struct otp_device *dev,
+				    const struct otp_region_ops *ops,
+				    int region_nr, const char *fmt, ...)
+{
+	struct otp_region *ret;
+	va_list vargs;
+
+	mutex_lock(&dev->lock);
+	va_start(vargs, fmt);
+	ret = __otp_region_alloc(dev, ops, region_nr, fmt, vargs);
+	va_end(vargs);
+	mutex_unlock(&dev->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(otp_region_alloc);
+
+static int __init otp_init(void)
+{
+	return bus_register(&otp_bus_type);
+}
+module_init(otp_init);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jamie Iles");
+MODULE_DESCRIPTION("OTP bus driver");
diff --git a/include/linux/otp.h b/include/linux/otp.h
new file mode 100644
index 0000000..c981465
--- /dev/null
+++ b/include/linux/otp.h
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2011 Picochip Ltd., Jamie Iles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * All enquiries to support@...ochip.com
+ *
+ * This driver implements a user interface for reading and writing OTP
+ * memory. This OTP can be used for executing secure boot code or for the
+ * secure storage of keys and any other user data.  We support multiple
+ * backends for different OTP macros.
+ *
+ * The OTP is configured through sysfs and is read and written through device
+ * nodes. The top level OTP device gains write_enable, num_regions, and
+ * strict_programming attributes. We also create an otp bus to which we add a
+ * device per region. The OTP can supports multiple regions and when we divide
+ * the regions down we create a new child device on the otp bus. This child
+ * device has format and size attributes.
+ *
+ * To update the number of regions, the format of a region or to program a
+ * region, the write_enable attribute of the OTP device must be set to
+ * "enabled".
+ */
+#ifndef __OTP_H__
+#define __OTP_H__
+
+#include <linux/types.h>
+
+#ifdef __KERNEL__
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/module.h>
+
+enum otp_redundancy_fmt {
+	OTP_REDUNDANCY_FMT_SINGLE_ENDED,
+	OTP_REDUNDANCY_FMT_REDUNDANT,
+	OTP_REDUNDANCY_FMT_DIFFERENTIAL,
+	OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT,
+	OTP_REDUNDANCY_FMT_ECC,
+	OTP_REDUNDANCY_NR_FMTS,
+};
+
+struct otp_device;
+struct otp_region;
+
+/**
+ * struct otp_device_ops - operations for the OTP device.
+ *
+ * @name:		The name of the driver backend.
+ * @owner:		The owning module.
+ * @get_nr_regions:	Get the number of regions that the OTP is partitioned
+ *			into.  Note that this is the number of regions in the
+ *			device, not the number of regions registered.
+ * @set_nr_regions:	Increase the number of partitions in the device.
+ *			Returns zero on success, negative errno on failure.
+ * @set_fmt:		Set the read-mode redundancy for the region.  The OTP
+ *			devices need to be put into the right redundancy mode
+ *			before reading/writing.
+ * @write_word:		Write a 64-bit word to the OTP.
+ * @read_word:		Read a 64-bit word from the OTP.
+ * @lock_word:		Lock a word to prevent further writes.  Optional for
+ *			some OTP devices.
+ */
+struct otp_device_ops {
+	const char	*name;
+	struct module	*owner;
+	ssize_t		(*get_nr_regions)(struct otp_device *dev);
+	int		(*set_nr_regions)(struct otp_device *dev,
+					  int nr_regions);
+	int		(*set_fmt)(struct otp_device *dev,
+				   enum otp_redundancy_fmt fmt);
+	int		(*write_word)(struct otp_device *dev,
+				      struct otp_region *region,
+				      unsigned long word_addr, u64 value);
+	int		(*read_word)(struct otp_device *dev,
+				     struct otp_region *region,
+				     unsigned long word_addr, u64 *value);
+	long		(*lock_word)(struct otp_device *dev,
+				     struct otp_region *region,
+				     unsigned long word_addr);
+};
+
+/**
+ * enum otp_device_caps - Flags to indicate capabilities for the OTP device.
+ *
+ * @OTP_CAPS_NO_SUBWORD_WRITE:	only full word sized writes may be performed.
+ *				Don't use read-modify-write cycles for
+ *				performing unaligned writes.
+ */
+enum otp_device_caps {
+	OTP_CAPS_NO_SUBWORD_WRITE	= (1 << 0),
+};
+
+/**
+ * struct otp_device - a picoxcell OTP device.
+ *
+ * @ops:		The operations to use for manipulating the device.
+ * @dev:		The parent device (typically a platform_device) that
+ *			provides the OTP.
+ * @regions:		The regions registered to the device.
+ * @size:		The size of the OTP in bytes.
+ * @max_regions:	The maximum number of regions the device may have.
+ * @dev_nr:		The OTP device ID, used for creating the otp%c
+ *			identifier.
+ * @flags:		Flags to indicate features of the OTP that the upper
+ *			layer should handle.
+ * @word_sz:		The size of the words of storage in the OTP (in
+ *			bytes).
+ */
+struct otp_device {
+	struct mutex			lock;
+	int				write_enable;
+	int				strict_programming;
+	const struct otp_device_ops	*ops;
+	struct device			dev;
+	struct list_head		regions;
+	size_t				size;
+	dev_t				devno;
+	unsigned			max_regions;
+	int				dev_nr;
+	size_t				word_sz;
+	unsigned long			flags;
+};
+
+static inline void *otp_dev_get_drvdata(struct otp_device *otp_dev)
+{
+	return dev_get_drvdata(&otp_dev->dev);
+}
+
+static inline void otp_dev_set_drvdata(struct otp_device *otp_dev,
+				       void *data)
+{
+	dev_set_drvdata(&otp_dev->dev, data);
+}
+
+/**
+ * struct otp_region_ops - operations to manipulate OTP regions.
+ *
+ * @set_fmt:		Permanently set the format of the region.  Returns
+ *			zero on success.
+ * @get_fmt:		Get the redundancy format of the region.
+ * @get_size:		Get the effective storage size of the region.
+ *			Depending on the number of regions in the device and
+ *			the redundancy format of the region, this may vary.
+ */
+struct otp_region_ops {
+	int			(*set_fmt)(struct otp_region *region,
+					   enum otp_redundancy_fmt fmt);
+	enum otp_redundancy_fmt	(*get_fmt)(struct otp_region *region);
+	ssize_t			(*get_size)(struct otp_region *region);
+};
+
+/**
+ * struct otp_region: a single region of OTP.
+ *
+ * @ops:	Operations for manipulating the region.
+ * @label:	The label of the region to export as a device attribute.
+ * @dev:	The device to register with the driver model.
+ * @cdev:	The character device used to provide userspace access to the
+ *		region.
+ * @head:	The position in the devices region list.
+ * @region_nr:	The region number of the region.  Devices number their regions
+ *		from 1 upwards.
+ */
+struct otp_region {
+	const struct otp_region_ops	*ops;
+	const char			*label;
+	struct device			dev;
+	struct cdev			cdev;
+	struct list_head		head;
+	unsigned			region_nr;
+};
+
+/**
+ * otp_device_alloc - create a new OTP device.
+ *
+ * Returns the newly created OTP device on success or a ERR_PTR() encoded
+ * errno on failure.  The new device is automatically registered and can be
+ * released with otp_device_unregister().  This will increase the reference
+ * count on dev.
+ *
+ * @dev:	The parent device that provides the OTP implementation.
+ * @ops:	The operations to manipulate the OTP device.
+ * @size:	The size, in bytes of the OTP device.
+ * @word_sz:	The size of the words in the OTP memory.
+ * @max_regions:The maximum number of regions in the device.
+ * @flags:	Bitmask of enum otp_device_flags for the device.
+ */
+struct otp_device *otp_device_alloc(struct device *dev,
+				    const struct otp_device_ops *ops,
+				    size_t size, size_t word_sz,
+				    unsigned max_regions,
+				    unsigned long flags);
+
+/**
+ * otp_device_unregister - unregister an existing struct otp_device.
+ *
+ * This unregisters an otp_device and any regions that have been registered to
+ * it.  Once all regions have been released, the parent reference count is
+ * decremented and the otp_device will be freed.  Callers must assume that dev
+ * is invalidated during this call.
+ *
+ * @dev:	The otp_device to unregister.
+ */
+void otp_device_unregister(struct otp_device *dev);
+
+/**
+ * otp_region_alloc - create and register a new OTP region.
+ *
+ * Create and register a new region in an existing device with specified
+ * region operations.  Returns a pointer to the region on success, or an
+ * ERR_PTR() encoded errno on failure.
+ *
+ * Note: this takes the OTP semaphore so may not be called from one of the
+ * otp_device_ops or otp_region_ops callbacks or this may lead to deadlock.
+ *
+ * @dev:	The device to add the region to.
+ * @ops:	The operations for the region.
+ * @region_nr:	The region ID.  Must be unique for the region.
+ * @fmt:	The format string for the region label.
+ */
+struct otp_region *otp_region_alloc(struct otp_device *dev,
+				    const struct otp_region_ops *ops,
+				    int region_nr, const char *fmt, ...);
+
+/**
+ * otp_region_alloc_unlocked - create and register a new OTP region.
+ *
+ * This is the same as otp_region_alloc() but does not take the OTP semaphore
+ * so may only be called from inside one of the otp_device_ops or
+ * otp_region_ops callbacks.
+ *
+ * @dev:	The device to add the region to.
+ * @ops:	The operations for the region.
+ * @region_nr:	The region ID.  Must be unique for the region.
+ * @fmt:	The format string for the region label.
+ */
+struct otp_region *otp_region_alloc_unlocked(struct otp_device *dev,
+					     const struct otp_region_ops *ops,
+					     int region_nr, const char *fmt,
+					     ...);
+
+/**
+ * otp_region_unregister - unregister a given OTP region.
+ *
+ * This unregisters a region from the device and forms part of
+ * otp_device_unregister().
+ *
+ * @region:	The region to unregister.
+ */
+#define otp_region_unregister(region)	device_unregister(&(region)->dev)
+
+/**
+ * otp_strict_programming_enabled - check if we are in strict programming
+ * mode.
+ *
+ * Some OTP devices support different redundancy modes.  These devices often
+ * need multiple words programmed to represent a single word in that
+ * redundancy format.  If strict programming is enabled then all of the
+ * redundancy words must program correctly to indicate success.  If strict
+ * programming is disabled then we will allow errors in the redundant word as
+ * long as the contents of the whole word are read back correctly with the
+ * required redundancy mode.
+ *
+ * @otp_dev:	The device to interrogate.
+ */
+bool otp_strict_programming_enabled(struct otp_device *otp_dev);
+
+/**
+ * otp_write_enabled - check whether writes are allowed to the device.
+ */
+bool otp_write_enabled(struct otp_device *otp_dev);
+
+static inline struct otp_region *to_otp_region(struct device *dev)
+{
+	return container_of(dev, struct otp_region, dev);
+}
+
+static inline struct otp_device *to_otp_device(struct device *dev)
+{
+	return container_of(dev, struct otp_device, dev);
+}
+
+#endif /* __KERNEL__ */
+
+/*
+ * struct otp_lock_area_info - Lock an area of OTP memory in a given region.
+ *
+ * @byte_addr:	The byte offset from the beginning of the region.  This must
+ *		be a multiple of the OTP word size.
+ * @byte_count:	The number of bytes to lock down.  This must be a multiple of
+ *		the OTP word size.  After the ioctl() completes this will be
+ *		updated with the number of bytes that were actually locked.
+ */
+struct otp_lock_area_info {
+	__u32		byte_addr;
+	__u32		byte_count;
+};
+#define OTP_LOCK_AREA	_IOWR('O', 0x10, struct otp_lock_area_info)
+
+#endif /* __OTP_H__ */
-- 
1.7.4

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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ