2011-03-25 17:14:51

by Jamie Iles

[permalink] [raw]
Subject: [RFC PATCHv3 0/4] Support for OTP memory

Hi,

I've updated this series following some great feedback from Greg and
Mike, and aside from the code quality improvements, the main changes
since v2 are:

- The OTP regions don't live directly in /sys/bus/otp/devices
anymore but just as a child device of the OTP device.
- CONFIG_OTP_WRITE_ENABLE has been created that replaces the
bfin-otp specific option and this disables all OTP writing at the
higher levels. The PC3X3 OTP driver has also been converted to use
this.
- Multiple OTP devices can now be registered at the same time.
- The bfin-otp driver has been converted to a platform_driver and
the relevant Blackfin platforms have had a bfin-otp device added.
- The Blackfin OTP is now represented as a single region that does
not include the ECC or control bits. This prevents users from
accidentally scribbling over those bits.

Greg, Mike, thanks again for taking the time to review, it's much
appreciated.

Thanks,

Jamie Iles (4):
drivers/otp: add initial support for OTP memory
drivers/otp: add support for Picoxcell PC3X3 OTP
drivers/otp: convert bfin otp to generic OTP
Blackfin: add the OTP device as a platform device

Documentation/ABI/testing/sysfs-bus-otp | 93 +++
arch/blackfin/mach-bf518/boards/ezbrd.c | 10 +
arch/blackfin/mach-bf518/boards/tcm-bf518.c | 10 +
arch/blackfin/mach-bf527/boards/ad7160eval.c | 10 +
arch/blackfin/mach-bf527/boards/cm_bf527.c | 10 +
arch/blackfin/mach-bf527/boards/ezbrd.c | 10 +
arch/blackfin/mach-bf527/boards/ezkit.c | 10 +
arch/blackfin/mach-bf527/boards/tll6527m.c | 10 +
arch/blackfin/mach-bf548/boards/cm_bf548.c | 9 +
arch/blackfin/mach-bf548/boards/ezkit.c | 10 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/char/Kconfig | 28 -
drivers/char/Makefile | 1 -
drivers/otp/Kconfig | 51 ++
drivers/otp/Makefile | 3 +
drivers/{char => otp}/bfin-otp.c | 275 ++++---
drivers/otp/otp.c | 913 +++++++++++++++++++++
drivers/otp/otp_pc3x3.c | 1099 ++++++++++++++++++++++++++
include/linux/otp.h | 277 +++++++
20 files changed, 2677 insertions(+), 155 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-bus-otp
create mode 100644 drivers/otp/Kconfig
create mode 100644 drivers/otp/Makefile
rename drivers/{char => otp}/bfin-otp.c (40%)
create mode 100644 drivers/otp/otp.c
create mode 100644 drivers/otp/otp_pc3x3.c
create mode 100644 include/linux/otp.h

--
1.7.4


2011-03-25 17:14:56

by Jamie Iles

[permalink] [raw]
Subject: [RFC PATCHv3 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:
- 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 <[email protected]>
---
Documentation/ABI/testing/sysfs-bus-otp | 93 ++++
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/otp/Kconfig | 28 +
drivers/otp/Makefile | 1 +
drivers/otp/otp.c | 913 +++++++++++++++++++++++++++++++
include/linux/otp.h | 277 ++++++++++
7 files changed, 1315 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..fb98efa
--- /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 <[email protected]>
+Description:
+ The otp 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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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/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..9a529c6
--- /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 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 characterstics. This provides a character device
+ interface to these devices to allow the OTP to be programmed and
+ read.
+
+if OTP
+
+config 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..42e3016
--- /dev/null
+++ b/drivers/otp/otp.c
@@ -0,0 +1,913 @@
+/*
+ * 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 [email protected]
+ */
+#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 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;
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+
+ if (region->ops->get_fmt(region))
+ fmt = region->ops->get_fmt(region);
+ else
+ fmt = OTP_REDUNDANCY_FMT_SINGLE_ENDED;
+
+ mutex_unlock(&otp_dev->lock);
+
+ if (fmt == OTP_REDUNDANCY_FMT_SINGLE_ENDED)
+ fmt_string = "single-ended";
+ else if (fmt == OTP_REDUNDANCY_FMT_REDUNDANT)
+ fmt_string = "redundant";
+ else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL)
+ fmt_string = "differential";
+ else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT)
+ fmt_string = "differential-redundant";
+ else if (fmt == OTP_REDUNDANCY_FMT_ECC)
+ fmt_string = "ecc";
+ else
+ 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)
+{
+ int 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;
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+
+ /* This is irreversible so don't make it too easy to break it! */
+ if (!otp_write_enabled(otp_dev)) {
+ err = -EPERM;
+ goto out;
+ }
+
+ if (sysfs_streq(buf, "single-ended"))
+ new_fmt = OTP_REDUNDANCY_FMT_SINGLE_ENDED;
+ else if (sysfs_streq(buf, "redundant"))
+ new_fmt = OTP_REDUNDANCY_FMT_REDUNDANT;
+ else if (sysfs_streq(buf, "differential"))
+ new_fmt = OTP_REDUNDANCY_FMT_DIFFERENTIAL;
+ else if (sysfs_streq(buf, "differential-redundant"))
+ new_fmt = OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT;
+ else if (sysfs_streq(buf, "ecc"))
+ new_fmt = OTP_REDUNDANCY_FMT_ECC;
+ else {
+ err = -EINVAL;
+ goto out;
+ }
+
+ region->ops->set_fmt(region, new_fmt);
+
+out:
+ mutex_unlock(&otp_dev->lock);
+
+ 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;
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+
+ region_sz = region->ops->get_size(region);
+
+ mutex_unlock(&otp_dev->lock);
+
+ return sprintf(buf, "%zu\n", region_sz);
+}
+static DEVICE_ATTR(size, S_IRUSR, otp_size_show, NULL);
+
+static struct bus_type otp_bus_type = {
+ .name = "otp",
+};
+
+static struct attribute *region_attrs[] = {
+ &dev_attr_format.attr,
+ &dev_attr_size.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);
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+
+ if (sysfs_streq(buf, "enabled"))
+ *param = 1;
+ else if (sysfs_streq(buf, "disabled"))
+ *param = 0;
+ else
+ err = -EINVAL;
+
+ mutex_unlock(&otp_dev->lock);
+
+ 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);
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+
+ ret = sprintf(buf, "%s\n", param ? "enabled" : "disabled");
+
+ mutex_unlock(&otp_dev->lock);
+
+ 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);
+ int nr_regions;
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+ nr_regions = otp_dev->ops->get_nr_regions(otp_dev);
+ mutex_unlock(&otp_dev->lock);
+
+ if (nr_regions < 0)
+ return (ssize_t)nr_regions;
+
+ return sprintf(buf, "%d\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;
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+
+ if (!otp_write_enabled(otp_dev)) {
+ err = -EPERM;
+ goto out;
+ }
+
+ err = otp_dev->ops->set_nr_regions(otp_dev, nr_regions);
+
+out:
+ mutex_unlock(&otp_dev->lock);
+
+ return err ?: len;
+}
+static DEVICE_ATTR(num_regions, S_IRUSR | S_IWUSR, otp_num_regions_show,
+ otp_num_regions_store);
+
+static struct attribute *otp_attrs[] = {
+ &dev_attr_strict_programming.attr,
+ &dev_attr_num_regions.attr,
+ &dev_attr_write_enable.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);
+
+ 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_DEVICE_FNO_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 = region->ops->read_word(region, word_addr, &word);
+ if (ret)
+ goto out;
+
+ if (copy_from_user((void *)(&word) + offset, buf, bytes)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = region->ops->write_word(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 = region->ops->write_word(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 = region->ops->read_word(region, pos / OTP_WORD_SIZE,
+ &word);
+ if (ret)
+ goto out;
+
+ if (copy_from_user(&word, buf, len)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = region->ops->write_word(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 */
+
+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 (!region->ops->ioctl)
+ return -ENOTTY;
+
+ if (mutex_lock_interruptible(&otp_dev->lock))
+ return -ERESTARTSYS;
+ ret = region->ops->ioctl(region, cmd, arg);
+ 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 = region->ops->read_word(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 = region->ops->read_word(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 = region->ops->read_word(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,
+};
+
+struct otp_region *otp_region_alloc_unlocked(struct otp_device *dev,
+ const struct otp_region_ops *ops,
+ int region_nr)
+{
+ 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->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;
+ }
+
+ 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:
+ kfree(region);
+out:
+ return err ? ERR_PTR(err) : region;
+}
+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)
+{
+ struct otp_region *ret;
+
+ mutex_lock(&dev->lock);
+ ret = otp_region_alloc_unlocked(dev, ops, region_nr);
+ 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..51ee6ae
--- /dev/null
+++ b/include/linux/otp.h
@@ -0,0 +1,277 @@
+/*
+ * 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 [email protected]
+ *
+ * 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/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,
+};
+
+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.
+ */
+struct otp_device_ops {
+ const char *name;
+ struct module *owner;
+ int (*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);
+};
+
+/**
+ * enum otp_device_flags - Flags to indicate capabilities for the OTP device.
+ *
+ * @OTP_DEVICE_FNO_SUBWORD_WRITE: only full word sized writes may be
+ * performed. Don't use
+ * read-modify-write cycles for
+ * performing unaligned writes.
+ */
+enum otp_device_flags {
+ OTP_DEVICE_FNO_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.
+ * @write_word: Write a 64-bit word to the OTP.
+ * @read_word: Read a 64-bit word from the OTP.
+ * @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.
+ * @ioctl: Optional region ioctl.
+ */
+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);
+ int (*write_word)(struct otp_region *region,
+ unsigned long word_addr,
+ u64 value);
+ int (*read_word)(struct otp_region *region,
+ unsigned long word_addr,
+ u64 *value);
+ ssize_t (*get_size)(struct otp_region *region);
+ long (*ioctl)(struct otp_region *dev,
+ unsigned cmd, unsigned long arg);
+};
+
+/**
+ * struct otp_region: a single region of OTP.
+ *
+ * @ops: Operations for manipulating the region.
+ * @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;
+ 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.
+ */
+struct otp_region *otp_region_alloc(struct otp_device *dev,
+ const struct otp_region_ops *ops,
+ int region_nr);
+
+/**
+ * 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.
+ */
+struct otp_region *otp_region_alloc_unlocked(struct otp_device *dev,
+ const struct otp_region_ops *ops,
+ int region_nr);
+
+/**
+ * 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 /* __OTP_H__ */
--
1.7.4

2011-03-25 17:15:05

by Jamie Iles

[permalink] [raw]
Subject: [RFC PATCHv3 3/4] drivers/otp: convert bfin otp to generic OTP

Convert the blackfin OTP driver to the generic OTP layer.

Changes since v2:
- Convert bfin-otp to a platform_driver.
- Hide the ECC and control bits from the character device
interface.

Cc: Mike Frysinger <[email protected]>
Signed-off-by: Jamie Iles <[email protected]>
---
drivers/char/Kconfig | 28 ----
drivers/char/Makefile | 1 -
drivers/otp/Kconfig | 16 +++
drivers/otp/Makefile | 1 +
drivers/{char => otp}/bfin-otp.c | 275 ++++++++++++++++++++-----------------
5 files changed, 166 insertions(+), 155 deletions(-)
rename drivers/{char => otp}/bfin-otp.c (40%)

diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index ad59b4e..a078362 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -81,34 +81,6 @@ config BRIQ_PANEL

It's safe to say N here.

-config BFIN_OTP
- tristate "Blackfin On-Chip OTP Memory Support"
- depends on BLACKFIN && (BF51x || BF52x || BF54x)
- default y
- help
- If you say Y here, you will get support for a character device
- interface into the One Time Programmable memory pages that are
- stored on the Blackfin processor. This will not get you access
- to the secure memory pages however. You will need to write your
- own secure code and reader for that.
-
- To compile this driver as a module, choose M here: the module
- will be called bfin-otp.
-
- If unsure, it is safe to say Y.
-
-config BFIN_OTP_WRITE_ENABLE
- bool "Enable writing support of OTP pages"
- depends on BFIN_OTP
- 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.
-
config PRINTER
tristate "Parallel printer support"
depends on PARPORT
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 7a00672..bc436a6 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -17,7 +17,6 @@ obj-$(CONFIG_VIOTAPE) += viotape.o
obj-$(CONFIG_IBM_BSR) += bsr.o
obj-$(CONFIG_SGI_MBCS) += mbcs.o
obj-$(CONFIG_BRIQ_PANEL) += briq_panel.o
-obj-$(CONFIG_BFIN_OTP) += bfin-otp.o

obj-$(CONFIG_PRINTER) += lp.o

diff --git a/drivers/otp/Kconfig b/drivers/otp/Kconfig
index c1a3f5e..74c1f92 100644
--- a/drivers/otp/Kconfig
+++ b/drivers/otp/Kconfig
@@ -32,4 +32,20 @@ config OTP_PC3X3
Say Y or M here to allow support for the OTP found in PC3X3 devices.
If you say M then the module will be called otp_pc3x3.

+config BFIN_OTP
+ tristate "Blackfin On-Chip OTP Memory Support"
+ depends on BLACKFIN && (BF51x || BF52x || BF54x)
+ default y
+ help
+ If you say Y here, you will get support for a character device
+ interface into the One Time Programmable memory pages that are
+ stored on the Blackfin processor. This will not get you access
+ to the secure memory pages however. You will need to write your
+ own secure code and reader for that.
+
+ To compile this driver as a module, choose M here: the module
+ will be called bfin-otp.
+
+ If unsure, it is safe to say Y.
+
endif
diff --git a/drivers/otp/Makefile b/drivers/otp/Makefile
index c710ec4..db79667 100644
--- a/drivers/otp/Makefile
+++ b/drivers/otp/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_OTP) += otp.o
obj-$(CONFIG_OTP_PC3X3) += otp_pc3x3.o
+obj-$(CONFIG_BFIN_OTP) += bfin-otp.o
diff --git a/drivers/char/bfin-otp.c b/drivers/otp/bfin-otp.c
similarity index 40%
rename from drivers/char/bfin-otp.c
rename to drivers/otp/bfin-otp.c
index 44660f1..2edf6ae 100644
--- a/drivers/char/bfin-otp.c
+++ b/drivers/otp/bfin-otp.c
@@ -9,12 +9,14 @@
*/

#include <linux/device.h>
+#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
-#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/otp.h>
#include <linux/types.h>
#include <mtd/mtd-abi.h>

@@ -28,58 +30,62 @@

#define DRIVER_NAME "bfin-otp"
#define PFX DRIVER_NAME ": "
+#define BFIN_OTP_SIZE (8 * 1024)
+#define BFIN_PAGE_SIZE 16
+#define BFIN_OTP_WORDS_PER_PAGE 2

-static DEFINE_MUTEX(bfin_otp_lock);
+static struct otp_device *bfin_otp;
+
+/*
+ * Given the data storage address in a region, find the real address in the
+ * OTP. This takes into account the control bit storage at the start of the
+ * first region and the ECC pages.
+ */
+static inline unsigned long bfin_region_to_raw_addr(unsigned long region_addr)
+{
+ unsigned long raw_addr;
+ unsigned control_words = BFIN_OTP_WORDS_PER_PAGE * 4;
+
+ /*
+ * Skip the control pages then if we would run into the ECC area skip
+ * past to the next data region.
+ */
+ raw_addr = region_addr + control_words;
+ if (raw_addr > 0x80 * BFIN_OTP_WORDS_PER_PAGE)
+ raw_addr += 0x20 * BFIN_OTP_WORDS_PER_PAGE;
+
+ return raw_addr;
+}

/**
* bfin_otp_read - Read OTP pages
*
* All reads must be in half page chunks (half page == 64 bits).
*/
-static ssize_t bfin_otp_read(struct file *file, char __user *buff, size_t count, loff_t *pos)
+static int bfin_region_read_word(struct otp_region *region, unsigned long addr,
+ u64 *word)
{
- ssize_t bytes_done;
+ int err;
u32 page, flags, ret;
- u64 content;

stampit();
-
- if (count % sizeof(u64))
- return -EMSGSIZE;
-
- if (mutex_lock_interruptible(&bfin_otp_lock))
- return -ERESTARTSYS;
-
- bytes_done = 0;
- page = *pos / (sizeof(u64) * 2);
- while (bytes_done < count) {
- flags = (*pos % (sizeof(u64) * 2) ? OTP_UPPER_HALF : OTP_LOWER_HALF);
- stamp("processing page %i (0x%x:%s)", page, flags,
- (flags & OTP_UPPER_HALF ? "upper" : "lower"));
- ret = bfrom_OtpRead(page, flags, &content);
- if (ret & OTP_MASTER_ERROR) {
- stamp("error from otp: 0x%x", ret);
- bytes_done = -EIO;
- break;
- }
- if (copy_to_user(buff + bytes_done, &content, sizeof(content))) {
- bytes_done = -EFAULT;
- break;
- }
- if (flags & OTP_UPPER_HALF)
- ++page;
- bytes_done += sizeof(content);
- *pos += sizeof(content);
- }
-
- mutex_unlock(&bfin_otp_lock);
-
- return bytes_done;
+ addr = bfin_region_to_raw_addr(addr);
+ page = addr / 2;
+ flags = (addr & 0x1) ? OTP_UPPER_HALF : OTP_LOWER_HALF;
+ stamp("processing page %i (0x%x:%s)", page, flags,
+ (flags & OTP_UPPER_HALF ? "upper" : "lower"));
+
+ err = bfrom_OtpRead(page, flags, word);
+ if (err & OTP_MASTER_ERROR) {
+ stamp("error from otp: 0x%x", ret);
+ err = -EIO;
+ } else
+ err = 0;
+
+ return err;
}

-#ifdef CONFIG_BFIN_OTP_WRITE_ENABLE
-static bool allow_writes;
-
+#ifdef CONFIG_OTP_WRITE_ENABLE
/**
* bfin_otp_init_timing - setup OTP timing parameters
*
@@ -117,62 +123,39 @@ static void bfin_otp_deinit_timing(u32 timing)
*
* All writes must be in half page chunks (half page == 64 bits).
*/
-static ssize_t bfin_otp_write(struct file *filp, const char __user *buff, size_t count, loff_t *pos)
+static int bfin_region_write_word(struct otp_region *region, unsigned long addr,
+ u64 content)
{
- ssize_t bytes_done;
+ int err;
u32 timing, page, base_flags, flags, ret;
- u64 content;
-
- if (!allow_writes)
- return -EACCES;
-
- if (count % sizeof(u64))
- return -EMSGSIZE;
-
- if (mutex_lock_interruptible(&bfin_otp_lock))
- return -ERESTARTSYS;

stampit();
-
+ addr = bfin_region_to_raw_addr(region->region_nr, addr);
timing = bfin_otp_init_timing();
- if (timing == 0) {
- mutex_unlock(&bfin_otp_lock);
+ if (timing == 0)
return -EIO;
- }
-
base_flags = OTP_CHECK_FOR_PREV_WRITE;

- bytes_done = 0;
- page = *pos / (sizeof(u64) * 2);
- while (bytes_done < count) {
- flags = base_flags | (*pos % (sizeof(u64) * 2) ? OTP_UPPER_HALF : OTP_LOWER_HALF);
- stamp("processing page %i (0x%x:%s) from %p", page, flags,
- (flags & OTP_UPPER_HALF ? "upper" : "lower"), buff + bytes_done);
- if (copy_from_user(&content, buff + bytes_done, sizeof(content))) {
- bytes_done = -EFAULT;
- break;
- }
- ret = bfrom_OtpWrite(page, flags, &content);
- if (ret & OTP_MASTER_ERROR) {
- stamp("error from otp: 0x%x", ret);
- bytes_done = -EIO;
- break;
- }
- if (flags & OTP_UPPER_HALF)
- ++page;
- bytes_done += sizeof(content);
- *pos += sizeof(content);
- }
+ page = addr / 2;
+ flags = base_flags | (addr & 0x1) ? OTP_UPPER_HALF : OTP_LOWER_HALF;
+ stamp("processing page %i (0x%x:%s)", page, flags,
+ (flags & OTP_UPPER_HALF ? "upper" : "lower"));
+ ret = bfrom_OtpWrite(page, flags, &content);
+ if (ret & OTP_MASTER_ERROR) {
+ stamp("error from otp: 0x%x", ret);
+ err = -EIO;
+ } else
+ err = 0;

bfin_otp_deinit_timing(timing);

- mutex_unlock(&bfin_otp_lock);
-
- return bytes_done;
+ return err;
}

-static long bfin_otp_ioctl(struct file *filp, unsigned cmd, unsigned long arg)
+static long bfin_region_ioctl(struct otp_region *region, unsigned cmd,
+ unsigned long arg)
{
+ struct otp_device *otp_dev = to_otp_device(region->dev.parent);
stampit();

switch (cmd) {
@@ -180,12 +163,9 @@ static long bfin_otp_ioctl(struct file *filp, unsigned cmd, unsigned long arg)
u32 timing;
int ret = -EIO;

- if (!allow_writes)
+ if (!otp_write_enabled(otp_dev))
return -EACCES;

- if (mutex_lock_interruptible(&bfin_otp_lock))
- return -ERESTARTSYS;
-
timing = bfin_otp_init_timing();
if (timing) {
u32 otp_result = bfrom_OtpWrite(arg, OTP_LOCK, NULL);
@@ -196,64 +176,109 @@ static long bfin_otp_ioctl(struct file *filp, unsigned cmd, unsigned long arg)
bfin_otp_deinit_timing(timing);
}

- mutex_unlock(&bfin_otp_lock);
-
return ret;
}
-
- case MEMLOCK:
- allow_writes = false;
- return 0;
-
- case MEMUNLOCK:
- allow_writes = true;
- return 0;
}

return -EINVAL;
}
-#else
-# define bfin_otp_write NULL
-# define bfin_otp_ioctl NULL
-#endif
-
-static const struct file_operations bfin_otp_fops = {
- .owner = THIS_MODULE,
- .unlocked_ioctl = bfin_otp_ioctl,
- .read = bfin_otp_read,
- .write = bfin_otp_write,
- .llseek = default_llseek,
-};
+#else /* CONFIG_OTP_WRITE_ENABLE */
+static int bfin_region_write_word(struct otp_region *region, unsigned long addr,
+ u64 content)
+{
+ return -EACCES;
+}
+#define bfin_region_ioctl NULL
+#endif /* CONFIG_OTP_WRITE_ENABLE */

-static struct miscdevice bfin_otp_misc_device = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = DRIVER_NAME,
- .fops = &bfin_otp_fops,
+static int bfin_otp_get_nr_regions(struct otp_device *dev)
+{
+ return 1;
+}
+
+static const struct otp_device_ops bfin_otp_ops = {
+ .name = "BFIN",
+ .owner = THIS_MODULE,
+ .get_nr_regions = bfin_otp_get_nr_regions,
};

-/**
- * bfin_otp_init - Initialize module
- *
- * Registers the device and notifier handler. Actual device
- * initialization is handled by bfin_otp_open().
+/*
+ * Blackfin has 0x200 pages, each of 128 bits giving 4KB of storage. This is
+ * further split into two regions of 0x100 pages where the top 0x20 pages are
+ * used for ECC correction automatically by the ROM. The first region also
+ * has 4 pages reserved as control bits for preventing futher writes to a
+ * page. To make it easier for the user we make this look like a single
+ * contiguous region that doesn't include the control or ECC bits. The ECC
+ * bits are written and checked by the lower layers and the control bits can
+ * be set by using the OTPLOCK ioctl().
*/
-static int __init bfin_otp_init(void)
+static ssize_t bfin_region_get_size(struct otp_region *region)
+{
+ return (2 * 0x80 * BFIN_PAGE_SIZE) - (4 * BFIN_PAGE_SIZE);
+}
+
+static enum otp_redundancy_fmt bfin_region_get_fmt(struct otp_region *region)
+{
+ return OTP_REDUNDANCY_FMT_ECC;
+}
+
+static const struct otp_region_ops bfin_region_ops = {
+ .read_word = bfin_region_read_word,
+ .write_word = bfin_region_write_word,
+ .get_size = bfin_region_get_size,
+ .get_fmt = bfin_region_get_fmt,
+ .ioctl = bfin_region_ioctl,
+};
+
+static int __devinit bfin_otp_probe(struct platform_device *pdev)
{
- int ret;
+ struct otp_region *region;

stampit();

- ret = misc_register(&bfin_otp_misc_device);
- if (ret) {
- pr_init(KERN_ERR PFX "unable to register a misc device\n");
- return ret;
+ bfin_otp = otp_device_alloc(&pdev->dev, &bfin_otp_ops, BFIN_OTP_SIZE,
+ 8, 2, OTP_DEVICE_FNO_SUBWORD_WRITE);
+ if (IS_ERR(bfin_otp)) {
+ pr_init(KERN_ERR PFX "failed to create OTP device\n");
+ return PTR_ERR(bfin_otp);
}

+ region = otp_region_alloc(bfin_otp, &bfin_region_ops, 1);
+ if (IS_ERR(region)) {
+ otp_device_unregister(bfin_otp);
+ return PTR_ERR(region);
+ }
pr_init(KERN_INFO PFX "initialized\n");

return 0;
}

+static int __devexit bfin_otp_remove(struct platform_device *pdev)
+{
+ stampit();
+
+ otp_device_unregister(bfin_otp);
+
+ return 0;
+}
+
+static struct platform_driver bfin_otp_driver = {
+ .probe = bfin_otp_probe,
+ .remove = __devexit_p(bfin_otp_remove),
+ .driver.name = "bfin-otp",
+};
+
+/**
+ * bfin_otp_init - Initialize module
+ *
+ * Registers the device and notifier handler. Actual device
+ * initialization is handled by bfin_otp_open().
+ */
+static int __init bfin_otp_init(void)
+{
+ return platform_driver_register(&bfin_otp_driver);
+}
+
/**
* bfin_otp_exit - Deinitialize module
*
@@ -262,9 +287,7 @@ static int __init bfin_otp_init(void)
*/
static void __exit bfin_otp_exit(void)
{
- stampit();
-
- misc_deregister(&bfin_otp_misc_device);
+ platform_driver_unregister(&bfin_otp_driver);
}

module_init(bfin_otp_init);
--
1.7.4

2011-03-25 17:15:21

by Jamie Iles

[permalink] [raw]
Subject: [RFC PATCHv3 4/4] Blackfin: add the OTP device as a platform device

Register the OTP as a platform device so that we can use the generic
OTP subsystem.

Cc: Mike Frysinger <[email protected]>
Signed-off-by: Jamie Iles <[email protected]>
---
arch/blackfin/mach-bf518/boards/ezbrd.c | 10 ++++++++++
arch/blackfin/mach-bf518/boards/tcm-bf518.c | 10 ++++++++++
arch/blackfin/mach-bf527/boards/ad7160eval.c | 10 ++++++++++
arch/blackfin/mach-bf527/boards/cm_bf527.c | 10 ++++++++++
arch/blackfin/mach-bf527/boards/ezbrd.c | 10 ++++++++++
arch/blackfin/mach-bf527/boards/ezkit.c | 10 ++++++++++
arch/blackfin/mach-bf527/boards/tll6527m.c | 10 ++++++++++
arch/blackfin/mach-bf548/boards/cm_bf548.c | 9 +++++++++
arch/blackfin/mach-bf548/boards/ezkit.c | 10 ++++++++++
9 files changed, 89 insertions(+), 0 deletions(-)

diff --git a/arch/blackfin/mach-bf518/boards/ezbrd.c b/arch/blackfin/mach-bf518/boards/ezbrd.c
index c0ccadc..7328ea1 100644
--- a/arch/blackfin/mach-bf518/boards/ezbrd.c
+++ b/arch/blackfin/mach-bf518/boards/ezbrd.c
@@ -747,6 +747,12 @@ static struct platform_device bfin_dpmc = {
},
};

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *stamp_devices[] __initdata = {

&bfin_dpmc,
@@ -814,6 +820,10 @@ static struct platform_device *stamp_devices[] __initdata = {
#if defined(CONFIG_MTD_PHYSMAP) || defined(CONFIG_MTD_PHYSMAP_MODULE)
&ezbrd_flash_device,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init ezbrd_init(void)
diff --git a/arch/blackfin/mach-bf518/boards/tcm-bf518.c b/arch/blackfin/mach-bf518/boards/tcm-bf518.c
index 50fc5c8..a36a1b2 100644
--- a/arch/blackfin/mach-bf518/boards/tcm-bf518.c
+++ b/arch/blackfin/mach-bf518/boards/tcm-bf518.c
@@ -659,6 +659,12 @@ static struct platform_device bfin_dpmc = {
},
};

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *tcm_devices[] __initdata = {

&bfin_dpmc,
@@ -719,6 +725,10 @@ static struct platform_device *tcm_devices[] __initdata = {
#if defined(CONFIG_MTD_PHYSMAP) || defined(CONFIG_MTD_PHYSMAP_MODULE)
&tcm_flash_device,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init tcm_init(void)
diff --git a/arch/blackfin/mach-bf527/boards/ad7160eval.c b/arch/blackfin/mach-bf527/boards/ad7160eval.c
index ccab4c6..05ac322 100644
--- a/arch/blackfin/mach-bf527/boards/ad7160eval.c
+++ b/arch/blackfin/mach-bf527/boards/ad7160eval.c
@@ -731,6 +731,12 @@ static struct platform_device bfin_dpmc = {
},
};

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *stamp_devices[] __initdata = {

&bfin_dpmc,
@@ -806,6 +812,10 @@ static struct platform_device *stamp_devices[] __initdata = {
#if defined(CONFIG_SND_BF5XX_TDM) || defined(CONFIG_SND_BF5XX_TDM_MODULE)
&bfin_tdm,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init ad7160eval_init(void)
diff --git a/arch/blackfin/mach-bf527/boards/cm_bf527.c b/arch/blackfin/mach-bf527/boards/cm_bf527.c
index c9d6dc8..a8e807a 100644
--- a/arch/blackfin/mach-bf527/boards/cm_bf527.c
+++ b/arch/blackfin/mach-bf527/boards/cm_bf527.c
@@ -896,6 +896,12 @@ static struct platform_device bfin_dpmc = {
},
};

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *cmbf527_devices[] __initdata = {

&bfin_dpmc,
@@ -979,6 +985,10 @@ static struct platform_device *cmbf527_devices[] __initdata = {
#if defined(CONFIG_MTD_GPIO_ADDR) || defined(CONFIG_MTD_GPIO_ADDR_MODULE)
&cm_flash_device,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init cm_init(void)
diff --git a/arch/blackfin/mach-bf527/boards/ezbrd.c b/arch/blackfin/mach-bf527/boards/ezbrd.c
index b7101aa..aabfb72 100644
--- a/arch/blackfin/mach-bf527/boards/ezbrd.c
+++ b/arch/blackfin/mach-bf527/boards/ezbrd.c
@@ -806,6 +806,12 @@ static struct platform_device bfin_lq035q1_device = {
};
#endif

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *stamp_devices[] __initdata = {

&bfin_dpmc,
@@ -873,6 +879,10 @@ static struct platform_device *stamp_devices[] __initdata = {
#if defined(CONFIG_MTD_PHYSMAP) || defined(CONFIG_MTD_PHYSMAP_MODULE)
&ezbrd_flash_device,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init ezbrd_init(void)
diff --git a/arch/blackfin/mach-bf527/boards/ezkit.c b/arch/blackfin/mach-bf527/boards/ezkit.c
index 2cd2ff6..8e1d6eb 100644
--- a/arch/blackfin/mach-bf527/boards/ezkit.c
+++ b/arch/blackfin/mach-bf527/boards/ezkit.c
@@ -1115,6 +1115,12 @@ static struct platform_device bfin_dpmc = {
},
};

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *stamp_devices[] __initdata = {

&bfin_dpmc,
@@ -1218,6 +1224,10 @@ static struct platform_device *stamp_devices[] __initdata = {
#if defined(CONFIG_SND_BF5XX_TDM) || defined(CONFIG_SND_BF5XX_TDM_MODULE)
&bfin_tdm,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init ezkit_init(void)
diff --git a/arch/blackfin/mach-bf527/boards/tll6527m.c b/arch/blackfin/mach-bf527/boards/tll6527m.c
index 18d303d..f1de548 100644
--- a/arch/blackfin/mach-bf527/boards/tll6527m.c
+++ b/arch/blackfin/mach-bf527/boards/tll6527m.c
@@ -873,6 +873,12 @@ static struct platform_device bfin_dpmc = {
},
};

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *tll6527m_devices[] __initdata = {

&bfin_dpmc,
@@ -941,6 +947,10 @@ static struct platform_device *tll6527m_devices[] __initdata = {
#if defined(CONFIG_GPIO_DECODER) || defined(CONFIG_GPIO_DECODER_MODULE)
&spi_decoded_gpio,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init tll6527m_init(void)
diff --git a/arch/blackfin/mach-bf548/boards/cm_bf548.c b/arch/blackfin/mach-bf548/boards/cm_bf548.c
index d11502a..746b3df 100644
--- a/arch/blackfin/mach-bf548/boards/cm_bf548.c
+++ b/arch/blackfin/mach-bf548/boards/cm_bf548.c
@@ -1093,6 +1093,12 @@ static struct platform_device bfin_dpmc = {
},
};

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *cm_bf548_devices[] __initdata = {

&bfin_dpmc,
@@ -1198,6 +1204,9 @@ static struct platform_device *cm_bf548_devices[] __initdata = {
&bfin_can_device,
#endif

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init cm_bf548_init(void)
diff --git a/arch/blackfin/mach-bf548/boards/ezkit.c b/arch/blackfin/mach-bf548/boards/ezkit.c
index 93e19a5..6385c4e 100644
--- a/arch/blackfin/mach-bf548/boards/ezkit.c
+++ b/arch/blackfin/mach-bf548/boards/ezkit.c
@@ -1336,6 +1336,12 @@ static struct platform_device bfin_ac97 = {
};
#endif

+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+static struct platform_device bfin_otp_device = {
+ .name = "bfin-otp",
+};
+#endif
+
static struct platform_device *ezkit_devices[] __initdata = {

&bfin_dpmc,
@@ -1461,6 +1467,10 @@ static struct platform_device *ezkit_devices[] __initdata = {
#if defined(CONFIG_SND_BF5XX_AC97) || defined(CONFIG_SND_BF5XX_AC97_MODULE)
&bfin_ac97,
#endif
+
+#if defined(CONFIG_BFIN_OTP) || defined(CONFIG_BFIN_OTP_MODULE)
+ &bfin_otp_device,
+#endif
};

static int __init ezkit_init(void)
--
1.7.4

2011-03-25 17:15:01

by Jamie Iles

[permalink] [raw]
Subject: [RFC PATCHv3 2/4] drivers/otp: add support for Picoxcell PC3X3 OTP

The OTP in PC3X3 is 16KB and can be split into 1, 2, 4 or 8
regions each taking a different redundancy format. The formats
increase the redundancy of the memory at the expense of bit storage
to help protect against programming errors for devices in the field.

Changes since v2:
- Add support for CONFIG_OTP_WRITE_ENABLE.

Signed-off-by: Jamie Iles <[email protected]>
---
drivers/otp/Kconfig | 7 +
drivers/otp/Makefile | 1 +
drivers/otp/otp_pc3x3.c | 1099 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1107 insertions(+), 0 deletions(-)
create mode 100644 drivers/otp/otp_pc3x3.c

diff --git a/drivers/otp/Kconfig b/drivers/otp/Kconfig
index 9a529c6..c1a3f5e 100644
--- a/drivers/otp/Kconfig
+++ b/drivers/otp/Kconfig
@@ -25,4 +25,11 @@ config WRITE_ENABLE

If unsure, say N.

+config OTP_PC3X3
+ tristate "Enable support for Picochip PC3X3 OTP"
+ depends on ARCH_PICOXCELL
+ help
+ Say Y or M here to allow support for the OTP found in PC3X3 devices.
+ If you say M then the module will be called otp_pc3x3.
+
endif
diff --git a/drivers/otp/Makefile b/drivers/otp/Makefile
index 84fd03e..c710ec4 100644
--- a/drivers/otp/Makefile
+++ b/drivers/otp/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_OTP) += otp.o
+obj-$(CONFIG_OTP_PC3X3) += otp_pc3x3.o
diff --git a/drivers/otp/otp_pc3x3.c b/drivers/otp/otp_pc3x3.c
new file mode 100644
index 0000000..e0e7caa
--- /dev/null
+++ b/drivers/otp/otp_pc3x3.c
@@ -0,0 +1,1099 @@
+/*
+ * 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 [email protected]
+ *
+ * This driver implements a picoxcellotp backend for reading and writing the
+ * OTP memory in Picochip PC3X3 devices. This OTP can be used for executing
+ * secure boot code or for the secure storage of keys and any other user data.
+ */
+#define pr_fmt(fmt) "pc3x3otp: " fmt
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/otp.h>
+#include <linux/platform_device.h>
+
+/*
+ * To test the user interface and most of the driver logic, we have a test
+ * mode whereby rather than writing to OTP we have a RAM buffer that simulates
+ * the OTP. This means that we can test everything apart from:
+ *
+ * - The OTP state machines and commands.
+ * - Failure to program bits.
+ */
+static int test_mode;
+module_param(test_mode, bool, S_IRUSR);
+MODULE_PARM_DESC(test_mode,
+ "Run in test mode (use a memory buffer rather than OTP");
+
+
+/* The control and status registers follow the AXI OTP map. */
+#define OTP_CTRL_BASE 0x4000
+
+/*
+ * This is the maximum number of times to try and soak a failed bit. We get
+ * this from the Sidense documentation. After 16 attempts it is very unlikely
+ * that anything will change.
+ */
+#define MAX_PROGRAM_RETRIES 16
+#define OTP_MACRO_CMD_REG_OFFSET 0x00
+#define OTP_MACRO_STATUS_REG_OFFSET 0x04
+#define OTP_MACRO_CONFIG_REG_OFFSET 0x08
+#define OTP_MACRO_ADDR_REG_OFFSET 0x0C
+#define OTP_MACRO_D_LO_REG_OFFSET 0x10
+#define OTP_MACRO_D_HI_REG_OFFSET 0x14
+#define OTP_MACRO_Q_LO_REG_OFFSET 0x20
+#define OTP_MACRO_Q_HI_REG_OFFSET 0x24
+#define OTP_MACRO_Q_MR_REG_OFFSET 0x28
+#define OTP_MACRO_Q_MRAB_REG_OFFSET 0x2C
+#define OTP_MACRO_Q_SR_LO_REG_OFFSET 0x30
+#define OTP_MACRO_Q_SR_HI_REG_OFFSET 0x34
+#define OTP_MACRO_Q_RR_LO_REG_OFFSET 0x38
+#define OTP_MACRO_Q_RR_HI_REG_OFFSET 0x3C
+#define OTP_MACRO_TIME_RD_REG_OFFSET 0x40
+#define OTP_MACRO_TIME_WR_REG_OFFSET 0x44
+#define OTP_MACRO_TIME_PGM_REG_OFFSET 0x48
+#define OTP_MACRO_TIME_PCH_REG_OFFSET 0x4C
+#define OTP_MACRO_TIME_CMP_REG_OFFSET 0x50
+#define OTP_MACRO_TIME_RST_REG_OFFSET 0x54
+#define OTP_MACRO_TIME_PWR_REG_OFFSET 0x58
+#define OTP_MACRO_DIRECT_IO_REG_OFFSET 0x5C
+
+/*
+ * The OTP addresses of the special register. This is in the boot
+ * sector and we use words 0 and 2 of sector 0 in redundant format.
+ */
+#define SR_ADDRESS_0 ((1 << 11) | 0x0)
+#define SR_ADDRESS_2 ((1 << 11) | 0x2)
+#define SR_AXI_ADDRESS_MASK 0x7
+
+#define OTP_MR_REDUNDANT_READ_MASK (1 << 4)
+#define OTP_MR_DIFFERENTIAL_READ_MASK (1 << 0)
+#define OTP_MRA_CHARGE_PUMP_ENABLE_MASK (1 << 12)
+#define OTP_MRA_CHARGE_PUMP_MONITOR_MASK (1 << 15)
+#define OTP_MRA_READ_REFERENCE_LEVEL9_MASK (1 << 9)
+#define OTP_MRA_READ_REFERENCE_LEVEL5_MASK (1 << 5)
+#define OTP_STATUS_VPP_APPLIED (1 << 4)
+#define OTP_TIME_PGM_PULSE_MASK 0x7FF
+#define OTP_STATUS_LCS (1 << 1)
+
+#define OTP_MR_SELF_TIMING (1 << 2)
+#define OTP_MR_PROGRAMMABLE_DELAY (1 << 5)
+#define OTP_MR_PROGRAMMABLE_DELAY_CONTROL (1 << 8)
+
+#define OTP_MRB_VREF_ADJUST_0 (1 << 0)
+#define OTP_MRB_VREF_ADJUST_1 (1 << 1)
+#define OTP_MRB_VREF_ADJUST_3 (1 << 3)
+#define OTP_MRB_READ_TIMER_DELAY_CONTROL (1 << 12)
+
+/*
+ * Programming pulse times. For the normal pulse, we use a programming time of
+ * 51.2uS. For a soak pulse where bits fail to program we use a 1mS pulse.
+ */
+#define OTP_NORMAL_PGM_PULSE_LENGTH 0x50
+#define OTP_SOAK_PGM_PULSE_LENGTH 0x61B
+
+enum otp_command {
+ OTP_COMMAND_IDLE,
+ OTP_COMMAND_WRITE,
+ OTP_COMMAND_PROGRAM,
+ OTP_COMMAND_READ,
+ OTP_COMMAND_WRITE_MR,
+ OTP_COMMAND_PRECHARGE,
+ OTP_COMMAND_COMPARE,
+ OTP_COMMAND_RESET,
+ OTP_COMMAND_RESET_M,
+ OTP_COMMAND_POWER_DOWN,
+ OTP_COMMAND_AUX_UPDATE_A,
+ OTP_COMMAND_AUX_UPDATE_B,
+ OTP_COMMAND_WRITE_PROGRAM,
+ OTP_COMMAND_WRITE_MRA,
+ OTP_COMMAND_WRITE_MRB,
+ OTP_COMMAND_RESET_MR,
+};
+
+#define PC3X3_OTP_WORD_SIZE 8
+
+/*
+ * The number of words in the OTP device. The device is 16K bytes and the word
+ * size is 64 bits.
+ */
+#define OTP_NUM_WORDS (SZ_16K / PC3X3_OTP_WORD_SIZE)
+
+/*
+ * The OTP device representation. We can have a static structure as there is
+ * only ever one OTP device in a system.
+ *
+ * @iomem: the io memory for the device that should be accessed with the I/O
+ * accessors.
+ * @mem: the 16KB of OTP memory that can be accessed like normal memory. When
+ * we probe, we force the __iomem away so we can read it directly.
+ * @test_mode_sr0, test_mode_sr2 the values of the special register when we're
+ * in test mode.
+ */
+struct pc3x3_otp {
+ struct otp_device *dev;
+ void __iomem *iomem;
+ void *mem;
+ struct clk *clk;
+ u64 test_mode_sr0, test_mode_sr2;
+ unsigned long registered_regions;
+};
+
+static int pc3x3_otp_register_regions(struct pc3x3_otp *dev,
+ bool need_unlocked);
+
+static inline void pc3x3_otp_write_reg(struct pc3x3_otp *otp, unsigned reg_num,
+ u32 value)
+{
+ writel(value, otp->iomem + OTP_CTRL_BASE + reg_num);
+}
+
+static inline u32 pc3x3_otp_read_reg(struct pc3x3_otp *otp, unsigned reg_num)
+{
+ return readl(otp->iomem + OTP_CTRL_BASE + reg_num);
+}
+
+static inline u32 pc3x3_otp_read_sr(struct pc3x3_otp *otp)
+{
+ if (test_mode)
+ return otp->test_mode_sr0 | otp->test_mode_sr2;
+
+ return pc3x3_otp_read_reg(otp, OTP_MACRO_Q_SR_LO_REG_OFFSET);
+}
+
+/*
+ * Get the region format. The region format encoding and number of regions are
+ * encoded in the bottom 32 bis of the special register:
+ *
+ * 20: enable redundancy replacement.
+ * [2:0]: AXI address mask - determines the number of address bits to use for
+ * selecting the region to read from.
+ * [m:n]: the format for region X where n := (X * 2) + 4 and m := n + 1.
+ */
+static enum otp_redundancy_fmt
+__pc3x3_otp_region_get_fmt(struct pc3x3_otp *otp,
+ const struct otp_region *region)
+{
+ unsigned shift = (region->region_nr * 2) + 4;
+
+ return (pc3x3_otp_read_sr(otp) >> shift) & 0x3;
+}
+
+static enum otp_redundancy_fmt
+pc3x3_otp_region_get_fmt(struct otp_region *region)
+{
+ struct pc3x3_otp *otp = dev_get_drvdata(region->dev.parent);
+
+ return __pc3x3_otp_region_get_fmt(otp, region);
+}
+
+/*
+ * Find out how many regions the OTP is partitioned into. This can be 1, 2, 4
+ * or 8.
+ */
+static int pc3x3_otp_num_regions(struct pc3x3_otp *otp)
+{
+ u32 addr_mask;
+ int nr_regions;
+
+ addr_mask = pc3x3_otp_read_sr(otp) & SR_AXI_ADDRESS_MASK;
+
+ switch (addr_mask) {
+ case 0:
+ nr_regions = 1;
+ break;
+ case 4:
+ nr_regions = 2;
+ break;
+ case 6:
+ nr_regions = 4;
+ break;
+ case 7:
+ nr_regions = 8;
+ break;
+ default:
+ WARN(1, "invalid special register region mask\n");
+ nr_regions = -EINVAL;
+ }
+
+ return nr_regions;
+}
+
+/*
+ * Find the byte offset of the first word in the region from the base of the
+ * OTP.
+ */
+static unsigned pc3x3_otp_region_base(struct pc3x3_otp *otp,
+ const struct otp_region *region)
+{
+ int num_regions = pc3x3_otp_num_regions(otp);
+ unsigned real_region_sz = SZ_16K / num_regions;
+
+ return (region->region_nr * real_region_sz) / PC3X3_OTP_WORD_SIZE;
+}
+
+static void pc3x3_otp_do_cmd(struct pc3x3_otp *otp, enum otp_command cmd)
+{
+ pc3x3_otp_write_reg(otp, OTP_MACRO_CMD_REG_OFFSET, cmd);
+ wmb();
+
+ /*
+ * If we're talking to OTP then we need to wait for the command to
+ * finish.
+ */
+ if (!test_mode)
+ while (pc3x3_otp_read_reg(otp, OTP_MACRO_CMD_REG_OFFSET) !=
+ OTP_COMMAND_IDLE)
+ cpu_relax();
+}
+
+/*
+ * Read a word from OTP.
+ *
+ * @addr the word address to read from.
+ * @val the destination to store the value in.
+ *
+ * Prerequisites: the OTP must be in single-ended read mode so that we can
+ * correctly read the raw word.
+ */
+static int pc3x3_otp_raw_read_word(struct pc3x3_otp *otp, unsigned addr,
+ u64 *val)
+{
+ if (addr == SR_ADDRESS_0 && test_mode)
+ *val = otp->test_mode_sr0;
+ else if (addr == SR_ADDRESS_2 && test_mode)
+ *val = otp->test_mode_sr2;
+ else {
+ union {
+ u64 d64;
+ u32 d32[2];
+ } converter;
+
+ pc3x3_otp_write_reg(otp, OTP_MACRO_ADDR_REG_OFFSET, addr);
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_READ);
+
+ converter.d32[0] =
+ pc3x3_otp_read_reg(otp, OTP_MACRO_Q_LO_REG_OFFSET);
+ converter.d32[1] =
+ pc3x3_otp_read_reg(otp, OTP_MACRO_Q_HI_REG_OFFSET);
+
+ if (!test_mode)
+ *val = converter.d64;
+ else
+ memcpy(val, otp->mem + addr * sizeof(u64), sizeof(u64));
+ }
+
+ return 0;
+}
+
+/*
+ * Set the redundancy mode to a specific format. This only affects the
+ * readback through the AXI map and does not store the redundancy format in
+ * the special register.
+ */
+static void __pc3x3_otp_redundancy_mode_set(struct pc3x3_otp *otp,
+ enum otp_redundancy_fmt fmt)
+{
+ u32 mr_lo = 0;
+
+ if (fmt == OTP_REDUNDANCY_FMT_REDUNDANT)
+ mr_lo |= OTP_MR_REDUNDANT_READ_MASK;
+ else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL)
+ mr_lo |= OTP_MR_DIFFERENTIAL_READ_MASK;
+ else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT)
+ mr_lo |= OTP_MR_REDUNDANT_READ_MASK |
+ OTP_MR_DIFFERENTIAL_READ_MASK;
+
+ /* Load the data register with the new MR contents. */
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET, mr_lo);
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET, 0);
+
+ /* Write the MR and wait for the write to complete. */
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_WRITE_MR);
+}
+
+static int pc3x3_otp_redundancy_mode_set(struct otp_device *dev,
+ enum otp_redundancy_fmt fmt)
+{
+ struct pc3x3_otp *otp = dev_get_drvdata(&dev->dev);
+
+ __pc3x3_otp_redundancy_mode_set(otp, fmt);
+
+ return 0;
+}
+
+#ifdef CONFIG_OTP_WRITE_ENABLE
+static void pc3x3_otp_write_MR(struct pc3x3_otp *otp, u32 value)
+{
+ /* Load the data register with the new contents. */
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET, value);
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET, 0);
+
+ /* Write the register and wait for the write to complete. */
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_WRITE_MR);
+}
+
+/*
+ * Create a write function for a given OTP auxillary mode register. This
+ * writes the auxillary mode register through the mode register then restores
+ * the contents of the mode register.
+ */
+#define OTP_REG_WRITE_FUNCTIONS(_name) \
+static void pc3x3_otp_write_##_name(struct pc3x3_otp *otp, u32 value) \
+{ \
+ u32 mr = pc3x3_otp_read_reg(otp, OTP_MACRO_Q_MR_REG_OFFSET); \
+ \
+ /* Load the data register with the new contents. */ \
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET, value); \
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET, 0); \
+ \
+ /* Write the register and wait for the write to complete. */ \
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_WRITE_##_name); \
+ \
+ /* Restore the original value of the MR. */ \
+ pc3x3_otp_write_MR(otp, mr); \
+}
+
+OTP_REG_WRITE_FUNCTIONS(MRA);
+OTP_REG_WRITE_FUNCTIONS(MRB);
+
+/*
+ * Enable the charge pump. This monitors the VPP voltage and waits for it to
+ * reach the correct programming level.
+ *
+ * @enable set to non-zero to enable the charge pump, zero to disable it.
+ */
+static void pc3x3_otp_charge_pump_enable(struct pc3x3_otp *otp, int enable)
+{
+ u32 mra = enable ?
+ (OTP_MRA_CHARGE_PUMP_ENABLE_MASK |
+ OTP_MRA_CHARGE_PUMP_MONITOR_MASK |
+ OTP_MRA_READ_REFERENCE_LEVEL9_MASK |
+ OTP_MRA_READ_REFERENCE_LEVEL5_MASK) : 0;
+
+ pc3x3_otp_write_MRA(otp, mra);
+
+ /* Now wait for VPP to reach the correct level. */
+ if (enable && !test_mode) {
+ while (!(pc3x3_otp_read_reg(otp, OTP_MACRO_STATUS_REG_OFFSET) &
+ OTP_STATUS_VPP_APPLIED))
+ cpu_relax();
+ }
+
+ udelay(1);
+}
+
+/*
+ * Program a word of OTP to a raw address. This will program an absolute value
+ * into the OTP so if the current word needs to be modified then this needs to
+ * be done with a read-modify-write cycle with the read-modify handled above.
+ *
+ * The actual write operation can't fail here but we don't do any verification
+ * to make sure that the correct data got written. That must be handled by the
+ * layer above.
+ */
+static void pc3x3_otp_raw_program_word(struct pc3x3_otp *otp, unsigned addr,
+ u64 v)
+{
+ unsigned bit_offs;
+ u64 tmp;
+ int set_to_program = addr & 1 ? 0 : 1;
+
+ if (test_mode) {
+ if (addr != SR_ADDRESS_0 && addr != SR_ADDRESS_2) {
+ u64 old;
+
+ if (pc3x3_otp_raw_read_word(otp, addr, &old))
+ return;
+
+ v = (addr & 1) ? old & ~v : old | v;
+
+ memcpy(otp->mem + (addr * PC3X3_OTP_WORD_SIZE), &v,
+ sizeof(v));
+ } else {
+ /*
+ * The special register OTP values are stored in the
+ * boot rows that live outside of the 16KB of normal
+ * OTP so we can't address them directly.
+ */
+ if (addr == SR_ADDRESS_0)
+ otp->test_mode_sr0 |= v;
+ else
+ otp->test_mode_sr2 |= v;
+ }
+ }
+
+ /* Set the address of the word that we're writing. */
+ pc3x3_otp_write_reg(otp, OTP_MACRO_ADDR_REG_OFFSET, addr);
+
+ for (bit_offs = 0; v && bit_offs < 64; ++bit_offs, v >>= 1) {
+ if (!(v & 0x1))
+ continue;
+
+ tmp = set_to_program ? ~(1LLU << bit_offs) :
+ (1LLU << bit_offs);
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET,
+ (u32)tmp & 0xFFFFFFFF);
+ pc3x3_otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET,
+ (u32)(tmp >> 32) & 0xFFFFFFFF);
+
+ /* Start programming the bit and wait for it to complete. */
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_WRITE_PROGRAM);
+ }
+}
+
+static inline void pc3x3_otp_set_program_pulse_len(struct pc3x3_otp *otp,
+ unsigned len)
+{
+ u32 v = pc3x3_otp_read_reg(otp, OTP_MACRO_TIME_PGM_REG_OFFSET);
+ v &= ~OTP_TIME_PGM_PULSE_MASK;
+ v |= len;
+ pc3x3_otp_write_reg(otp, OTP_MACRO_TIME_PGM_REG_OFFSET, v);
+}
+
+/*
+ * Write a raw word in OTP. This will program a word into OTP memory and do
+ * any read-modify-write that is necessary. For example if address 0 contains
+ * 0x00ef, then writing 0xbe00 will result in address 0 containing 0xbeef.
+ * This does not handle redundancy - this should be done at a higher level.
+ *
+ * @addr the word address to write to.
+ * @val the value to program into the OTP.
+ *
+ * Prerequisites: the OTP must be in single-ended read mode so that we can
+ * correctly verify the word.
+ */
+static int pc3x3_otp_raw_write_word(struct pc3x3_otp *otp, unsigned addr,
+ u64 val)
+{
+ /*
+ * We program even addresses by setting 0 bits to one and programm odd
+ * addresses by clearing 1 bits to 0.
+ */
+ int set_to_program = addr & 1 ? 0 : 1;
+ int retries = 0, err = 0;
+ u64 orig, v;
+
+ if (pc3x3_otp_raw_read_word(otp, addr, &orig))
+ return -EIO;
+
+ v = set_to_program ? val & ~orig : ~val & orig;
+
+ /*
+ * Enable the charge pump and configure initial timing to begin
+ * programming.
+ */
+ pc3x3_otp_charge_pump_enable(otp, 1);
+ pc3x3_otp_write_MRB(otp, OTP_MRB_VREF_ADJUST_3 |
+ OTP_MRB_READ_TIMER_DELAY_CONTROL);
+ pc3x3_otp_write_MR(otp, OTP_MR_SELF_TIMING |
+ OTP_MR_PROGRAMMABLE_DELAY |
+ OTP_MR_PROGRAMMABLE_DELAY_CONTROL);
+ pc3x3_otp_raw_program_word(otp, addr, v);
+ udelay(1);
+
+ while (retries < MAX_PROGRAM_RETRIES) {
+ /* Update orig so we only reprogram the unprogrammed bits. */
+ if (pc3x3_otp_raw_read_word(otp, addr, &orig)) {
+ err = -EIO;
+ break;
+ }
+
+ /* If we've programmed correctly we have nothing else to do. */
+ if (val == orig) {
+ err = 0;
+ break;
+ }
+
+ /* Reset the mode register. */
+ pc3x3_otp_write_MRB(otp, OTP_MRB_VREF_ADJUST_0 |
+ OTP_MRB_VREF_ADJUST_1 |
+ OTP_MRB_VREF_ADJUST_3 |
+ OTP_MRB_READ_TIMER_DELAY_CONTROL);
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_RESET_MR);
+
+ /* Increase the programming pulse length. */
+ pc3x3_otp_set_program_pulse_len(otp, OTP_SOAK_PGM_PULSE_LENGTH);
+
+ /* Work out the failed bits. */
+ v = set_to_program ? val & ~orig : ~val & orig;
+ pc3x3_otp_raw_program_word(otp, addr, v);
+
+ /* Restore the programming pulse length. */
+ pc3x3_otp_set_program_pulse_len(otp,
+ OTP_NORMAL_PGM_PULSE_LENGTH);
+
+ /* Update orig so we only reprogram the unprogrammed bits. */
+ if (pc3x3_otp_raw_read_word(otp, addr, &orig)) {
+ err = -EIO;
+ break;
+ }
+
+ /* If we've programmed correctly we have nothing else to do. */
+ if (val == orig) {
+ err = 0;
+ break;
+ }
+
+ pc3x3_otp_write_MRB(otp, OTP_MRB_VREF_ADJUST_3 |
+ OTP_MRB_READ_TIMER_DELAY_CONTROL);
+ pc3x3_otp_write_MR(otp, OTP_MR_SELF_TIMING |
+ OTP_MR_PROGRAMMABLE_DELAY |
+ OTP_MR_PROGRAMMABLE_DELAY_CONTROL);
+ udelay(1);
+ ++retries;
+ }
+
+ /* Disable the charge pump. We're done now. */
+ pc3x3_otp_charge_pump_enable(otp, 0);
+ pc3x3_otp_write_MRB(otp, 0);
+ pc3x3_otp_write_MRA(otp, 0);
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_RESET_MR);
+
+ if (!err && retries >= MAX_PROGRAM_RETRIES) {
+ dev_warn(&otp->dev->dev,
+ "writing to raw address %x failed to program after %d attempts\n",
+ addr, MAX_PROGRAM_RETRIES);
+ err = -EBADMSG;
+ }
+
+ return err;
+}
+
+/*
+ * Write a data word to an OTP region. The value will be used in a
+ * read-modify-write cycle to ensure that bits can't be flipped if they have
+ * already programmed (the hardware isn't capable of this). This also takes
+ * into account the redundancy addressing and formatting.
+ */
+static int pc3x3_otp_region_write_word(struct otp_region *region,
+ unsigned long addr,
+ u64 word)
+{
+ struct pc3x3_otp *otp = dev_get_drvdata(region->dev.parent);
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ unsigned i, num_words, raw_addresses[4];
+ u64 result;
+ int err = 0;
+
+ /* Enter the single-ended read mode. */
+ __pc3x3_otp_redundancy_mode_set(otp, OTP_REDUNDANCY_FMT_SINGLE_ENDED);
+
+ /*
+ * Work out what raw addresses and values we need to write into the
+ * OTP to make sure that the value we want gets read back out
+ * correctly.
+ */
+ switch (fmt) {
+ case OTP_REDUNDANCY_FMT_SINGLE_ENDED:
+ num_words = 1;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) + addr;
+ break;
+
+ case OTP_REDUNDANCY_FMT_REDUNDANT:
+ num_words = 2;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1));
+ raw_addresses[1] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1) | 2);
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL:
+ num_words = 2;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1));
+ raw_addresses[1] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1) | 1);
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT:
+ num_words = 4;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) +
+ ((addr & 0xFFFF) << 2);
+ raw_addresses[1] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x1);
+ raw_addresses[2] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x2);
+ raw_addresses[3] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x3);
+ break;
+
+ default:
+ err = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Verify the raw words. If we are doing strict programming then they
+ * must all program correctly. If we aren't doing strict programming
+ * then allow failures to 'slip through' for now. If the word can be
+ * read back correctly in the redundant mode then that's fine with the
+ * user.
+ */
+ for (i = 0; i < num_words; ++i)
+ err = pc3x3_otp_raw_write_word(otp, raw_addresses[i], word);
+ if (err && otp_strict_programming_enabled(otp->dev))
+ goto out;
+
+ /* Go back to the real redundancy mode and verify the whole word. */
+ __pc3x3_otp_redundancy_mode_set(otp, fmt);
+
+ if (region->ops->read_word(region, addr, &result)) {
+ err = -EIO;
+ goto out;
+ }
+
+ /*
+ * Now check that the word has been correctly programmed with the
+ * region formatting.
+ */
+ if (result == word) {
+ err = 0;
+ } else {
+ dev_warn(&region->dev,
+ "word at address %lx write failed %llx != %llx (result != expected)\n",
+ addr, result, word);
+ err = -EBADMSG;
+ }
+
+out:
+ return err;
+}
+
+/*
+ * Write the special register. In PC3X3, we only use the lower 32 bits of the
+ * SR to indicate the partitioning and the region formats so we do a
+ * read-modify-write of the whole 64 bit value.
+ */
+static int pc3x3_otp_write_sr(struct pc3x3_otp *otp, u32 sr_lo)
+{
+ if (pc3x3_otp_raw_write_word(otp, SR_ADDRESS_0, sr_lo)) {
+ dev_warn(&otp->dev->dev,
+ "failed to write special register (word 0)\n");
+ return -EIO;
+ }
+
+ if (pc3x3_otp_raw_write_word(otp, SR_ADDRESS_2, sr_lo)) {
+ dev_warn(&otp->dev->dev,
+ "failed to write special register (word 0)\n");
+ return -EIO;
+ }
+
+ /*
+ * Reset the OTP so that when we read the special register again we
+ * get the value that we've just written.
+ */
+ pc3x3_otp_do_cmd(otp, OTP_COMMAND_RESET);
+
+ return 0;
+}
+
+static int pc3x3_otp_region_set_fmt(struct otp_region *region,
+ enum otp_redundancy_fmt new_fmt)
+{
+ int err;
+ struct pc3x3_otp *otp = dev_get_drvdata(region->dev.parent);
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ unsigned shift = (region->region_nr * 2) + 4;
+ unsigned long sr;
+
+ /*
+ * We can't clear format bits so we can only do certain transitions.
+ * It is possible to go from redundant to differential-redundant or
+ * differential to differential redundant but if the region is already
+ * programmed this could give unexpected results. However, the user
+ * _might_ know what they're doing.
+ */
+ if (fmt & ~new_fmt) {
+ err = -EINVAL;
+ goto out;
+ }
+ if (fmt == new_fmt) {
+ err = 0;
+ goto out;
+ }
+
+ sr = pc3x3_otp_read_sr(otp);
+ sr |= new_fmt << shift;
+ err = pc3x3_otp_write_sr(otp, sr);
+
+out:
+ return err;
+}
+
+static int pc3x3_otp_set_nr_regions(struct otp_device *dev, int nr_regions)
+{
+ struct pc3x3_otp *otp = dev_get_drvdata(&dev->dev);
+ unsigned long sr = pc3x3_otp_read_sr(otp);
+ u32 new_mask, addr_mask = sr & SR_AXI_ADDRESS_MASK;
+ int err = 0;
+
+ switch (nr_regions) {
+ case 1:
+ new_mask = 0;
+ break;
+ case 2:
+ new_mask = 4;
+ break;
+ case 4:
+ new_mask = 6;
+ break;
+ case 8:
+ new_mask = 7;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Check that we aren't trying to clear any bits and reduce the number
+ * of regions. This is OTP so we can only increase.
+ */
+ if (addr_mask & ~new_mask)
+ return -EINVAL;
+
+ if (addr_mask == new_mask)
+ return 0;
+
+ err = pc3x3_otp_write_sr(otp, sr | new_mask);
+ if (err)
+ return err;
+
+ return pc3x3_otp_register_regions(otp, true);
+}
+#else /* CONFIG_OTP_WRITE_ENABLE */
+#define pc3x3_otp_region_set_fmt NULL
+#define pc3x3_otp_region_write_word NULL
+#define pc3x3_otp_set_nr_regions NULL
+#endif /* CONFIG_OTP_WRITE_ENABLE */
+
+/*
+ * Read a word from a specificied OTP region. The address is the user address
+ * for the word to be read and should not take the redundancy into account.
+ */
+static int pc3x3_otp_region_read_word(struct otp_region *region,
+ unsigned long addr, u64 *word)
+{
+ struct pc3x3_otp *otp = dev_get_drvdata(region->dev.parent);
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ unsigned num_words, raw_addresses[4];
+ u64 result = 0, raw_values[4];
+ int err = 0;
+
+ /* Enter the single-ended read mode. */
+ __pc3x3_otp_redundancy_mode_set(otp, OTP_REDUNDANCY_FMT_SINGLE_ENDED);
+
+ /*
+ * If we're running with real OTP then the read is simple, just copy
+ * it from the AXI map.
+ */
+ if (!test_mode) {
+ memcpy(word,
+ otp->mem + (pc3x3_otp_region_base(otp, region) + addr) *
+ PC3X3_OTP_WORD_SIZE, sizeof(*word));
+ return 0;
+ }
+
+ /*
+ * If we're in test mode then this is slightly more complicated. We
+ * need to decode the address into the raw address(s) that the block
+ * uses and handle the redundancy format. This allows us to test that
+ * we've programmed all of the redundant words in the correct format.
+ */
+ switch (fmt) {
+ case OTP_REDUNDANCY_FMT_SINGLE_ENDED:
+ num_words = 1;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) + addr;
+ pc3x3_otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ result = raw_values[0];
+ break;
+
+ case OTP_REDUNDANCY_FMT_REDUNDANT:
+ num_words = 2;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1));
+ raw_addresses[1] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1) | 2);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[1], &raw_values[1]);
+ result = raw_values[0] | raw_values[1];
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL:
+ num_words = 2;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1));
+ raw_addresses[1] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1) | 1);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[1], &raw_values[1]);
+ result = raw_values[0] | ~raw_values[1];
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT:
+ num_words = 4;
+ raw_addresses[0] = pc3x3_otp_region_base(otp, region) +
+ ((addr & 0xFFFF) << 2);
+ raw_addresses[1] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x1);
+ raw_addresses[2] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x2);
+ raw_addresses[3] = pc3x3_otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x3);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[1], &raw_values[1]);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[2], &raw_values[2]);
+ pc3x3_otp_raw_read_word(otp, raw_addresses[3], &raw_values[3]);
+ result = (raw_values[0] | ~raw_values[1]) |
+ (raw_values[2] | ~raw_values[3]);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ if (!err)
+ *word = result;
+
+ return err;
+}
+
+/*
+ * Find out how big the region is. We have a 16KB device which can be split
+ * equally into 1, 2, 4 or 8 regions. If a partition is redundant or
+ * differential redundancy then this is 2 bits of storage per data bit so half
+ * the size. For differential-redundant redundancy, 1 bit of data takes 4 bits
+ * of storage so divide by 4.
+ */
+static ssize_t pc3x3_otp_region_get_size(struct otp_region *region)
+{
+ struct pc3x3_otp *otp = dev_get_drvdata(region->dev.parent);
+ int num_regions = pc3x3_otp_num_regions(otp);
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ ssize_t region_sz;
+
+ region_sz = (SZ_16K / num_regions);
+ if (OTP_REDUNDANCY_FMT_REDUNDANT == fmt ||
+ OTP_REDUNDANCY_FMT_DIFFERENTIAL == fmt)
+ region_sz /= 2;
+ else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT)
+ region_sz /= 4;
+
+ return region_sz;
+}
+
+static const struct otp_region_ops pc3x3_region_ops = {
+ .set_fmt = pc3x3_otp_region_set_fmt,
+ .get_fmt = pc3x3_otp_region_get_fmt,
+ .write_word = pc3x3_otp_region_write_word,
+ .read_word = pc3x3_otp_region_read_word,
+ .get_size = pc3x3_otp_region_get_size,
+};
+
+static int pc3x3_otp_register_regions(struct pc3x3_otp *dev,
+ bool need_unlocked)
+{
+ struct otp_device *otp = dev->dev;
+ int err = 0, i, nr_regions = otp->ops->get_nr_regions(otp);
+
+ for (i = 0; i < nr_regions; ++i) {
+ struct otp_region *region;
+
+ if (test_and_set_bit(i, &dev->registered_regions))
+ continue;
+
+ region = need_unlocked ?
+ otp_region_alloc_unlocked(otp, &pc3x3_region_ops, i) :
+ otp_region_alloc(otp, &pc3x3_region_ops, i);
+ if (IS_ERR(region)) {
+ err = PTR_ERR(region);
+ break;
+ }
+ }
+
+ return err;
+}
+
+static int pc3x3_otp_get_nr_regions(struct otp_device *dev)
+{
+ struct pc3x3_otp *otp = dev_get_drvdata(&dev->dev);
+ unsigned long sr = pc3x3_otp_read_sr(otp);
+ u32 addr_mask = sr & SR_AXI_ADDRESS_MASK;
+
+ if (0 == addr_mask)
+ return 1;
+ else if (4 == addr_mask)
+ return 2;
+ else if (6 == addr_mask)
+ return 4;
+ else if (7 == addr_mask)
+ return 8;
+
+ return -EINVAL;
+}
+
+static const struct otp_device_ops pc3x3_otp_ops = {
+ .name = "PC3X3",
+ .owner = THIS_MODULE,
+ .get_nr_regions = pc3x3_otp_get_nr_regions,
+ .set_nr_regions = pc3x3_otp_set_nr_regions,
+ .set_fmt = pc3x3_otp_redundancy_mode_set,
+};
+
+static int __devinit pc3x3_otp_probe(struct platform_device *pdev)
+{
+ int err;
+ struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct otp_device *otp;
+ struct pc3x3_otp *pc3x3_dev;
+
+ if (!mem) {
+ dev_err(&pdev->dev, "no i/o memory\n");
+ return -ENXIO;
+ }
+
+ if (!devm_request_mem_region(&pdev->dev, mem->start,
+ resource_size(mem), "otp")) {
+ dev_err(&pdev->dev, "unable to request i/o memory\n");
+ return -EBUSY;
+ }
+
+ pc3x3_dev = devm_kzalloc(&pdev->dev, sizeof(*pc3x3_dev), GFP_KERNEL);
+ if (!pc3x3_dev)
+ return -ENOMEM;
+
+ if (test_mode) {
+ u64 *p = devm_kzalloc(&pdev->dev, SZ_16K + SZ_1K, GFP_KERNEL);
+ int i;
+
+ if (!p) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ pc3x3_dev->mem = p;
+ pc3x3_dev->iomem = (void __force __iomem *)p;
+
+ for (i = 0; (u8 *)p < (u8 *)pc3x3_dev->mem + SZ_16K + SZ_1K;
+ ++p, ++i)
+ *p = (i & 1) ? ~0LLU : 0LLU;
+ } else {
+ pc3x3_dev->iomem = devm_ioremap(&pdev->dev, mem->start,
+ resource_size(mem));
+ if (!pc3x3_dev->iomem) {
+ err = -ENOMEM;
+ goto out;
+ }
+ pc3x3_dev->mem = (void __force *)pc3x3_dev->iomem;
+ }
+
+ pc3x3_dev->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pc3x3_dev->clk)) {
+ dev_err(&pdev->dev, "device has no clk\n");
+ err = PTR_ERR(pc3x3_dev->clk);
+ goto out;
+ }
+ clk_enable(pc3x3_dev->clk);
+
+ otp = otp_device_alloc(&pdev->dev, &pc3x3_otp_ops, SZ_16K, 8, 8, 0);
+ if (IS_ERR(otp)) {
+ err = PTR_ERR(otp);
+ goto out_clk_disable;
+ }
+ otp_dev_set_drvdata(otp, pc3x3_dev);
+
+ pc3x3_dev->dev = otp;
+ platform_set_drvdata(pdev, pc3x3_dev);
+
+ err = pc3x3_otp_register_regions(pc3x3_dev, false);
+ if (err)
+ goto out_unregister;
+
+ goto out;
+
+out_unregister:
+ otp_device_unregister(otp);
+out_clk_disable:
+ clk_disable(pc3x3_dev->clk);
+ clk_put(pc3x3_dev->clk);
+out:
+ return err;
+}
+
+static int __devexit pc3x3_otp_remove(struct platform_device *pdev)
+{
+ struct pc3x3_otp *otp = platform_get_drvdata(pdev);
+
+ otp_device_unregister(otp->dev);
+ clk_disable(otp->clk);
+ clk_put(otp->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pc3x3_otp_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pc3x3_otp *otp = platform_get_drvdata(pdev);
+
+ pc3x3_otp_write_reg(otp, OTP_MACRO_CMD_REG_OFFSET,
+ OTP_COMMAND_POWER_DOWN);
+ clk_disable(otp->clk);
+
+ return 0;
+}
+
+static int pc3x3_otp_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pc3x3_otp *otp = platform_get_drvdata(pdev);
+
+ clk_enable(otp->clk);
+ pc3x3_otp_write_reg(otp, OTP_MACRO_CMD_REG_OFFSET, OTP_COMMAND_IDLE);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pc3x3_otp_pm_ops = {
+ .suspend = pc3x3_otp_suspend,
+ .resume = pc3x3_otp_resume,
+};
+#endif /* CONFIG_PM */
+
+static struct platform_driver pc3x3_otp_driver = {
+ .probe = pc3x3_otp_probe,
+ .remove = __devexit_p(pc3x3_otp_remove),
+ .driver = {
+ .name = "picoxcell-otp-pc3x3",
+#ifdef CONFIG_PM
+ .pm = &pc3x3_otp_pm_ops,
+#endif /* CONFIG_PM */
+ },
+};
+
+static int __init pc3x3_otp_init(void)
+{
+ return platform_driver_register(&pc3x3_otp_driver);
+}
+module_init(pc3x3_otp_init);
+
+static void __exit pc3x3_otp_exit(void)
+{
+ platform_driver_unregister(&pc3x3_otp_driver);
+}
+module_exit(pc3x3_otp_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jamie Iles");
+MODULE_DESCRIPTION("OTP memory driver for Picochip PC3X3 devices");
--
1.7.4

2011-03-25 21:38:14

by Mike Frysinger

[permalink] [raw]
Subject: Re: [RFC PATCHv3 4/4] Blackfin: add the OTP device as a platform device

On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
> Register the OTP as a platform device so that we can use the generic
> OTP subsystem.

i would move this to patch 3/4 so that the boards are updated before
the driver is converted to a platform_driver

otherwise, this patch looks good to me (i'll hold off on actually
testing things until common code has settled a bit)
-mike

2011-03-25 21:58:27

by Mike Frysinger

[permalink] [raw]
Subject: Re: [RFC PATCHv3 1/4] drivers/otp: add initial support for OTP memory

On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
> --- /dev/null
> +++ b/drivers/otp/Kconfig
> + may have different characterstics. This provides a character device

characterstics -> characteristics

> +if OTP
> +
> +config WRITE_ENABLE
> + bool "Enable writing support of OTP pages"
> + default n
> + help

does this show correctly in the kconfig by putting this under "if otp"
instead of "depends otp" ? it should show the write option indented
rather than at the same level.

> +/* We'll allow OTP devices to be named otpa-otpz. */
> +#define MAX_OTP_DEVICES 26

mmm is that still true ?

> +static unsigned long registered_otp_map[BITS_TO_LONGS(MAX_OTP_DEVICES)];
> +static DEFINE_MUTEX(otp_register_mutex);

do we really need this ? if we let the minor number dictate
availability, then we can increment until that errors/wraps, and we
dont need to do any tracking ...

> + if (fmt == OTP_REDUNDANCY_FMT_SINGLE_ENDED)
> + fmt_string = "single-ended";
> + else if (fmt == OTP_REDUNDANCY_FMT_REDUNDANT)
> + fmt_string = "redundant";
> + else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL)
> + fmt_string = "differential";
> + else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT)
> + fmt_string = "differential-redundant";
> + else if (fmt == OTP_REDUNDANCY_FMT_ECC)
> + fmt_string = "ecc";
> + else
> + return -EINVAL;

i wonder if the code would be simpler if we had a local static array.
then the show/store funcs would simply walk that tree, and when you
add a new format in the future, you only have to update one place.

static const char * const otp_redundancy_str[] = {
[OTP_REDUNDANCY_FMT_SINGLE_ENDED] = "single-ended",
[OTP_REDUNDANCY_FMT_REDUNDANT] = "redundant",
........
};

> +static ssize_t otp_num_regions_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + int nr_regions;
> +
> + nr_regions = otp_dev->ops->get_nr_regions(otp_dev);
> +
> + if (nr_regions < 0)
> + return (ssize_t)nr_regions;

we could make get_nr_regions() return a ssize_t ...

> + err = alloc_chrdev_region(&otp_dev->devno, 0, max_regions, "otp");

hmm, i was thinking that we'd have 1 major for otp devices. isnt this
how MTD does it ?

> --- /dev/null
> +++ b/include/linux/otp.h
> +/**
> + * enum otp_device_flags - Flags to indicate capabilities for the OTP device.
> + *
> + * @OTP_DEVICE_FNO_SUBWORD_WRITE: only full word sized writes may be
> + * performed. Don't use
> + * read-modify-write cycles for
> + * performing unaligned writes.
> + */
> +enum otp_device_flags {
> + OTP_DEVICE_FNO_SUBWORD_WRITE = (1 << 0),
> +};

use OTP_CAPS_xxx instead ?
-mike

2011-03-25 22:47:31

by Jamie Iles

[permalink] [raw]
Subject: Re: [RFC PATCHv3 1/4] drivers/otp: add initial support for OTP memory

Hi Mike,

On Fri, Mar 25, 2011 at 05:58:05PM -0400, Mike Frysinger wrote:
> On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
> > --- /dev/null
> > +++ b/drivers/otp/Kconfig
> > + may have different characterstics. This provides a character device
>
> characterstics -> characteristics

Doh!

> > +if OTP
> > +
> > +config WRITE_ENABLE
> > + bool "Enable writing support of OTP pages"
> > + default n
> > + help
>
> does this show correctly in the kconfig by putting this under "if otp"
> instead of "depends otp" ? it should show the write option indented
> rather than at the same level.

I think it's OK because it's a menuconfig thing so the toplevel OTP
thing is at the same level as misc devices and staging drivers etc.

> > +/* We'll allow OTP devices to be named otpa-otpz. */
> > +#define MAX_OTP_DEVICES 26
>
> mmm is that still true ?

I think so - the actual devices should be otpa-otpz, but when you
register regions they could be otpa1, otpa2, otpb1, otpb2 etc.

>
> > +static unsigned long registered_otp_map[BITS_TO_LONGS(MAX_OTP_DEVICES)];
> > +static DEFINE_MUTEX(otp_register_mutex);
>
> do we really need this ? if we let the minor number dictate
> availability, then we can increment until that errors/wraps, and we
> dont need to do any tracking ...

OK, so it would be nice to get rid of this but afaict we still need to
do some accounting of available minor numbers in the range that we've
allocated. We could do a simple increment % 255 for the minor number
but if OTP devices are removed at runtime then that may get fragmented
and we would need to do retries of device_register() which feels a bit
too easy to mess up.

Certainly allocating one major number for OTP devices then allocating
the minors one by one would be much better than what I have now.

We probably also want it so that if you remove the OTP device that has
had regions called otpaN then reinsert it they doesn't suddenly become
otpbN.

>
> > + if (fmt == OTP_REDUNDANCY_FMT_SINGLE_ENDED)
> > + fmt_string = "single-ended";
> > + else if (fmt == OTP_REDUNDANCY_FMT_REDUNDANT)
> > + fmt_string = "redundant";
> > + else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL)
> > + fmt_string = "differential";
> > + else if (fmt == OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT)
> > + fmt_string = "differential-redundant";
> > + else if (fmt == OTP_REDUNDANCY_FMT_ECC)
> > + fmt_string = "ecc";
> > + else
> > + return -EINVAL;
>
> i wonder if the code would be simpler if we had a local static array.
> then the show/store funcs would simply walk that tree, and when you
> add a new format in the future, you only have to update one place.
>
> static const char * const otp_redundancy_str[] = {
> [OTP_REDUNDANCY_FMT_SINGLE_ENDED] = "single-ended",
> [OTP_REDUNDANCY_FMT_REDUNDANT] = "redundant",
> ........
> };

Yes, that would be a lot cleaner.

> > +static ssize_t otp_num_regions_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + int nr_regions;
> > +
> > + nr_regions = otp_dev->ops->get_nr_regions(otp_dev);
> > +
> > + if (nr_regions < 0)
> > + return (ssize_t)nr_regions;
>
> we could make get_nr_regions() return a ssize_t ...

OK, will do.

>
> > + err = alloc_chrdev_region(&otp_dev->devno, 0, max_regions, "otp");
>
> hmm, i was thinking that we'd have 1 major for otp devices. isnt this
> how MTD does it ?
>
> > --- /dev/null
> > +++ b/include/linux/otp.h
> > +/**
> > + * enum otp_device_flags - Flags to indicate capabilities for the OTP device.
> > + *
> > + * @OTP_DEVICE_FNO_SUBWORD_WRITE: only full word sized writes may be
> > + * performed. Don't use
> > + * read-modify-write cycles for
> > + * performing unaligned writes.
> > + */
> > +enum otp_device_flags {
> > + OTP_DEVICE_FNO_SUBWORD_WRITE = (1 << 0),
> > +};
>
> use OTP_CAPS_xxx instead ?

Sounds like a plan! Thanks again for the review!

Jamie

2011-03-25 22:50:58

by Mike Frysinger

[permalink] [raw]
Subject: Re: [RFC PATCHv3 1/4] drivers/otp: add initial support for OTP memory

On Fri, Mar 25, 2011 at 18:47, Jamie Iles wrote:
> On Fri, Mar 25, 2011 at 05:58:05PM -0400, Mike Frysinger wrote:
>> On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
>> > +/* We'll allow OTP devices to be named otpa-otpz. */
>> > +#define MAX_OTP_DEVICES                26
>>
>> mmm is that still true ?
>
> I think so - the actual devices should be otpa-otpz, but when you
> register regions they could be otpa1, otpa2, otpb1, otpb2 etc.
>
>>
>> > +static unsigned long registered_otp_map[BITS_TO_LONGS(MAX_OTP_DEVICES)];
>> > +static DEFINE_MUTEX(otp_register_mutex);
>>
>> do we really need this ?  if we let the minor number dictate
>> availability, then we can increment until that errors/wraps, and we
>> dont need to do any tracking ...
>
> OK, so it would be nice to get rid of this but afaict we still need to
> do some accounting of available minor numbers in the range that we've
> allocated.  We could do a simple increment % 255 for the minor number
> but if OTP devices are removed at runtime then that may get fragmented
> and we would need to do retries of device_register() which feels a bit
> too easy to mess up.
>
> Certainly allocating one major number for OTP devices then allocating
> the minors one by one would be much better than what I have now.
>
> We probably also want it so that if you remove the OTP device that has
> had regions called otpaN then reinsert it they doesn't suddenly become
> otpbN.

yeah that's true. guess i'll leave it be then ;).

whatever naming is picked in /dev/ should match the stuff in /sys/ btw ...
-mike

2011-03-25 22:55:28

by Jamie Iles

[permalink] [raw]
Subject: Re: [RFC PATCHv3 1/4] drivers/otp: add initial support for OTP memory

On Fri, Mar 25, 2011 at 06:50:36PM -0400, Mike Frysinger wrote:
> On Fri, Mar 25, 2011 at 18:47, Jamie Iles wrote:
> > On Fri, Mar 25, 2011 at 05:58:05PM -0400, Mike Frysinger wrote:
> >> On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
> >> > +/* We'll allow OTP devices to be named otpa-otpz. */
> >> > +#define MAX_OTP_DEVICES ? ? ? ? ? ? ? ?26
> >>
> >> mmm is that still true ?
> >
> > I think so - the actual devices should be otpa-otpz, but when you
> > register regions they could be otpa1, otpa2, otpb1, otpb2 etc.
> >
> >>
> >> > +static unsigned long registered_otp_map[BITS_TO_LONGS(MAX_OTP_DEVICES)];
> >> > +static DEFINE_MUTEX(otp_register_mutex);
> >>
> >> do we really need this ? ?if we let the minor number dictate
> >> availability, then we can increment until that errors/wraps, and we
> >> dont need to do any tracking ...
> >
> > OK, so it would be nice to get rid of this but afaict we still need to
> > do some accounting of available minor numbers in the range that we've
> > allocated. ?We could do a simple increment % 255 for the minor number
> > but if OTP devices are removed at runtime then that may get fragmented
> > and we would need to do retries of device_register() which feels a bit
> > too easy to mess up.
> >
> > Certainly allocating one major number for OTP devices then allocating
> > the minors one by one would be much better than what I have now.
> >
> > We probably also want it so that if you remove the OTP device that has
> > had regions called otpaN then reinsert it they doesn't suddenly become
> > otpbN.
>
> yeah that's true. guess i'll leave it be then ;).

OK, but it still might be worth pruning it down to a single major number
or is that something worth doing later on if it becomes needed?

> whatever naming is picked in /dev/ should match the stuff in /sys/ btw ...

I think that's working ok. On my system I have:

/sys/bus/otp/devices/otpa/otpa1/
/sys/bus/otp/devices/otpa/otpa2/

and

/dev/otpa1
/dev/otpa2

Jamie

2011-03-25 22:56:26

by Mike Frysinger

[permalink] [raw]
Subject: Re: [RFC PATCHv3 3/4] drivers/otp: convert bfin otp to generic OTP

On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
> + /*
> + * Skip the control pages then if we would run into the ECC area skip
> + * past to the next data region.
> + */
> + raw_addr = region_addr + control_words;
> + if (raw_addr > 0x80 * BFIN_OTP_WORDS_PER_PAGE)
> + raw_addr += 0x20 * BFIN_OTP_WORDS_PER_PAGE;

mmm, no, we dont want to do that. the Blackfin documentation is very
exact when it maps out pages, and we want the driver to match the
documentation.

but i guess in the other discussion we had, this would be removed anyways.

> +static int bfin_region_write_word(struct otp_region *region, unsigned long addr,
> +                                 u64 content)
> +{
> +       return -EACCES;
> +}

i think we'd just stub this out as NULL and let the common layer take
care of rejecting it ?

> +static const struct otp_device_ops bfin_otp_ops = {
> +       .name           = "BFIN",

guess this should be "bfin-otp"

> +static const struct otp_region_ops bfin_region_ops = {
> +       .read_word      = bfin_region_read_word,
> +       .write_word     = bfin_region_write_word,
> +       .get_size       = bfin_region_get_size,
> +       .get_fmt        = bfin_region_get_fmt,
> +       .ioctl          = bfin_region_ioctl,
> +};

hmm, i just realized this stuff is per-region. wouldnt the
read/write/ioctl make more sense as per-device ?
-mike

2011-03-25 22:59:12

by Mike Frysinger

[permalink] [raw]
Subject: Re: [RFC PATCHv3 1/4] drivers/otp: add initial support for OTP memory

On Fri, Mar 25, 2011 at 18:55, Jamie Iles wrote:
> On Fri, Mar 25, 2011 at 06:50:36PM -0400, Mike Frysinger wrote:
>> On Fri, Mar 25, 2011 at 18:47, Jamie Iles wrote:
>> > On Fri, Mar 25, 2011 at 05:58:05PM -0400, Mike Frysinger wrote:
>> >> On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
>> >> > +static unsigned long registered_otp_map[BITS_TO_LONGS(MAX_OTP_DEVICES)];
>> >> > +static DEFINE_MUTEX(otp_register_mutex);
>> >>
>> >> do we really need this ?  if we let the minor number dictate
>> >> availability, then we can increment until that errors/wraps, and we
>> >> dont need to do any tracking ...
>> >
>> > OK, so it would be nice to get rid of this but afaict we still need to
>> > do some accounting of available minor numbers in the range that we've
>> > allocated.  We could do a simple increment % 255 for the minor number
>> > but if OTP devices are removed at runtime then that may get fragmented
>> > and we would need to do retries of device_register() which feels a bit
>> > too easy to mess up.
>> >
>> > Certainly allocating one major number for OTP devices then allocating
>> > the minors one by one would be much better than what I have now.
>> >
>> > We probably also want it so that if you remove the OTP device that has
>> > had regions called otpaN then reinsert it they doesn't suddenly become
>> > otpbN.
>>
>> yeah that's true.  guess i'll leave it be then ;).
>
> OK, but it still might be worth pruning it down to a single major number
> or is that something worth doing later on if it becomes needed?

i dont think we'll see an explosion of OTP devices where we have to
worry about major # exhaustion. if that day comes, we'll worry about
things then.

as long as we stick to dynamic device numbers, we have the flexibility
to screw around later.
-mike

2011-03-26 00:11:50

by Jamie Iles

[permalink] [raw]
Subject: Re: [RFC PATCHv3 3/4] drivers/otp: convert bfin otp to generic OTP

On Fri, Mar 25, 2011 at 06:56:03PM -0400, Mike Frysinger wrote:
> On Fri, Mar 25, 2011 at 13:14, Jamie Iles wrote:
> > + /*
> > + * Skip the control pages then if we would run into the ECC area skip
> > + * past to the next data region.
> > + */
> > + raw_addr = region_addr + control_words;
> > + if (raw_addr > 0x80 * BFIN_OTP_WORDS_PER_PAGE)
> > + raw_addr += 0x20 * BFIN_OTP_WORDS_PER_PAGE;
>
> mmm, no, we dont want to do that. the Blackfin documentation is very
> exact when it maps out pages, and we want the driver to match the
> documentation.
>
> but i guess in the other discussion we had, this would be removed anyways.

OK, so in that case could we initially have:

- region for control bits
- region for the first data bits
- region for the ecc for the first data
- region for the second data bits
- region for the ecc for the second data

or shall I just leave it as one big region for now?

>
> > +static int bfin_region_write_word(struct otp_region *region, unsigned long addr,
> > + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? u64 content)
> > +{
> > + ? ? ? return -EACCES;
> > +}
>
> i think we'd just stub this out as NULL and let the common layer take
> care of rejecting it ?

Yes, that's probably best. At least we're always building the whole
driver then.

>
> > +static const struct otp_device_ops bfin_otp_ops = {
> > + ? ? ? .name ? ? ? ? ? = "BFIN",
>
> guess this should be "bfin-otp"

Good spot.

>
> > +static const struct otp_region_ops bfin_region_ops = {
> > + ? ? ? .read_word ? ? ?= bfin_region_read_word,
> > + ? ? ? .write_word ? ? = bfin_region_write_word,
> > + ? ? ? .get_size ? ? ? = bfin_region_get_size,
> > + ? ? ? .get_fmt ? ? ? ?= bfin_region_get_fmt,
> > + ? ? ? .ioctl ? ? ? ? ?= bfin_region_ioctl,
> > +};
>
> hmm, i just realized this stuff is per-region. wouldnt the
> read/write/ioctl make more sense as per-device ?

No, I don't think so. The file_operations are all based on the regions
rather than the device so I think it makes sense to have these as region
based operations. We could make them per device and pass the region as
a parameter but I'm not sure that it gains us anything.

Jamie

2011-03-26 02:12:03

by Mike Frysinger

[permalink] [raw]
Subject: Re: [RFC PATCHv3 3/4] drivers/otp: convert bfin otp to generic OTP

On Fri, Mar 25, 2011 at 20:11, Jamie Iles wrote:
> or shall I just leave it as one big region for now?

for now, stick with one region. i can refine it later if i desire.

>> > +static const struct otp_region_ops bfin_region_ops = {
>> > +       .read_word      = bfin_region_read_word,
>> > +       .write_word     = bfin_region_write_word,
>> > +       .get_size       = bfin_region_get_size,
>> > +       .get_fmt        = bfin_region_get_fmt,
>> > +       .ioctl          = bfin_region_ioctl,
>> > +};
>>
>> hmm, i just realized this stuff is per-region.  wouldnt the
>> read/write/ioctl make more sense as per-device ?
>
> No, I don't think so.  The file_operations are all based on the regions
> rather than the device so I think it makes sense to have these as region
> based operations.  We could make them per device and pass the region as
> a parameter but I'm not sure that it gains us anything.

for a device that exports more than one region, you dont need to
duplicate the structure. as you add in more regions, the amount of
duplication increases.

how many regions does your device export ? does it need different
read/write behavior for each ?
-mike

2011-03-26 02:32:58

by Jamie Iles

[permalink] [raw]
Subject: Re: [RFC PATCHv3 3/4] drivers/otp: convert bfin otp to generic OTP

On Fri, Mar 25, 2011 at 10:11:40PM -0400, Mike Frysinger wrote:
> >> > +static const struct otp_region_ops bfin_region_ops = {
> >> > + ? ? ? .read_word ? ? ?= bfin_region_read_word,
> >> > + ? ? ? .write_word ? ? = bfin_region_write_word,
> >> > + ? ? ? .get_size ? ? ? = bfin_region_get_size,
> >> > + ? ? ? .get_fmt ? ? ? ?= bfin_region_get_fmt,
> >> > + ? ? ? .ioctl ? ? ? ? ?= bfin_region_ioctl,
> >> > +};
> >>
> >> hmm, i just realized this stuff is per-region. ?wouldnt the
> >> read/write/ioctl make more sense as per-device ?
> >
> > No, I don't think so. ?The file_operations are all based on the regions
> > rather than the device so I think it makes sense to have these as region
> > based operations. ?We could make them per device and pass the region as
> > a parameter but I'm not sure that it gains us anything.
>
> for a device that exports more than one region, you dont need to
> duplicate the structure. as you add in more regions, the amount of
> duplication increases.
>
> how many regions does your device export ? does it need different
> read/write behavior for each ?

The current devices are up to 8 regions and whilst the behaviour is the
same for each it does need to know what region it's operating on so it
can set the redundancy correctly.

We could move the read and write methods into some device ops but they'd
need to take an otp_region as a parameter. Also, at the moment we don't
duplicate the structure as we're just keeping a pointer to it but I'm
happy to move these into the device ops if you have a strong preference.

Jamie

2011-03-26 02:56:13

by Mike Frysinger

[permalink] [raw]
Subject: Re: [RFC PATCHv3 3/4] drivers/otp: convert bfin otp to generic OTP

On Fri, Mar 25, 2011 at 22:32, Jamie Iles wrote:
> On Fri, Mar 25, 2011 at 10:11:40PM -0400, Mike Frysinger wrote:
>> >> > +static const struct otp_region_ops bfin_region_ops = {
>> >> > +       .read_word      = bfin_region_read_word,
>> >> > +       .write_word     = bfin_region_write_word,
>> >> > +       .get_size       = bfin_region_get_size,
>> >> > +       .get_fmt        = bfin_region_get_fmt,
>> >> > +       .ioctl          = bfin_region_ioctl,
>> >> > +};
>> >>
>> >> hmm, i just realized this stuff is per-region.  wouldnt the
>> >> read/write/ioctl make more sense as per-device ?
>> >
>> > No, I don't think so.  The file_operations are all based on the regions
>> > rather than the device so I think it makes sense to have these as region
>> > based operations.  We could make them per device and pass the region as
>> > a parameter but I'm not sure that it gains us anything.
>>
>> for a device that exports more than one region, you dont need to
>> duplicate the structure.  as you add in more regions, the amount of
>> duplication increases.
>>
>> how many regions does your device export ?  does it need different
>> read/write behavior for each ?
>
> The current devices are up to 8 regions and whilst the behaviour is the
> same for each it does need to know what region it's operating on so it
> can set the redundancy correctly.
>
> We could move the read and write methods into some device ops but they'd
> need to take an otp_region as a parameter.  Also, at the moment we don't
> duplicate the structure as we're just keeping a pointer to it but I'm
> happy to move these into the device ops if you have a strong preference.

i can see keeping the fmt/size in the region, but the read/write/ioctl
really look like they belong at the device level
-mike