2011-03-29 12:08:11

by Jamie Iles

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

Hi all,

This series of the OTP memory support has been updated since v3 with the
following main changes:

- The device specific ioctl() has now been eliminated and an
OTP_LOCK_AREA ioctl() has been added that uses the new lock_word()
operation to perform locking of OTP.
- The read_word/write_word operations are now per-device operations
rather than per-region.
- A word_size attribute has been added so that users can get the OTP
word size (useful for the area locking).
- Regions can have labels (a bit like partition names).
- The sysfs show/store functions now take a backend module reference
before accessing the operations to prevent a possible invalid
reference when the backend module is unloaded.

Thanks,

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

Documentation/ABI/testing/sysfs-bus-otp | 93 +++
Documentation/ioctl/ioctl-number.txt | 1 +
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 | 261 +++----
drivers/otp/otp.c | 1051 ++++++++++++++++++++++++
drivers/otp/otp_pc3x3.c | 1102 ++++++++++++++++++++++++++
include/linux/otp.h | 305 +++++++
21 files changed, 2819 insertions(+), 169 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 (44%)
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-29 12:08:14

by Jamie Iles

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

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

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

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

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

diff --git a/Documentation/ABI/testing/sysfs-bus-otp b/Documentation/ABI/testing/sysfs-bus-otp
new file mode 100644
index 0000000..02ff12a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-otp
@@ -0,0 +1,93 @@
+What: /sys/bus/otp/
+Date: March 2011
+KernelVersion: 2.6.40+
+Contact: Jamie Iles <[email protected]>
+Description:
+ The otp (One Time Programmable memory) bus presents a number
+ of devices where each device represents and OTP device in the
+ system. An OTP device may have a number of regions (which can
+ be thought of as partitions) and the regions are child
+ devices. Each region will create a device node which allows
+ the region to be written with read()/write() calls and the
+ device on the bus has attributes for controlling the
+ redundancy format and getting the region size.
+
+What: /sys/bus/devices/otp#/
+Date: March 2011
+KernelVersion: 2.6.40+
+Contact: Jamie Iles <[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/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index a0a5d82..d2953d2 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -150,6 +150,7 @@ Code Seq#(hex) Include File Comments
'M' 00-0F drivers/video/fsl-diu-fb.h conflict!
'N' 00-1F drivers/usb/scanner.h
'O' 00-06 mtd/ubi-user.h UBI
+'O' 10-1F include/linux/otp.h OTP
'P' all linux/soundcard.h conflict!
'P' 60-6F sound/sscape_ioctl.h conflict!
'P' 00-0F drivers/usb/class/usblp.c conflict!
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 177c7d1..77da156 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -119,4 +119,6 @@ source "drivers/platform/Kconfig"
source "drivers/clk/Kconfig"

source "drivers/hwspinlock/Kconfig"
+
+source "drivers/otp/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 3f135b6..6ae2f815 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -119,3 +119,4 @@ obj-y += ieee802154/
obj-y += clk/

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

2011-03-29 12:08:19

by Jamie Iles

[permalink] [raw]
Subject: [RFC PATCHv4 3/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-29 12:08:28

by Jamie Iles

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

Convert the blackfin OTP driver to the generic OTP layer.

Changes since v3:
- Use the lock_word device operation to allow OTP are locking
through the OTP_LOCK_AREA ioctl().
- Convert read_word/write_word to device operations.

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 | 261 ++++++++++++++++++--------------------
5 files changed, 138 insertions(+), 169 deletions(-)
rename drivers/{char => otp}/bfin-otp.c (44%)

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 edac4d5..feee010 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 44%
rename from drivers/char/bfin-otp.c
rename to drivers/otp/bfin-otp.c
index 44660f1..ecff490 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,41 @@

#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;

/**
* 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_read_word(struct otp_device *otp_dev,
+ 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;
+ 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,118 +102,128 @@ 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_write_word(struct otp_device *otp_dev,
+ 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();
-
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_lock_word(struct otp_device *otp_dev,
+ struct otp_region *region, unsigned long addr)
{
+ u32 timing;
+ int ret = -EIO;
+
stampit();

- switch (cmd) {
- case OTPLOCK: {
- u32 timing;
- int ret = -EIO;
+ if (!otp_write_enabled(otp_dev))
+ return -EACCES;

- if (!allow_writes)
- return -EACCES;
+ timing = bfin_otp_init_timing();
+ if (timing) {
+ u32 otp_result = bfrom_OtpWrite(addr, OTP_LOCK, NULL);
+ stamp("locking page %lu resulted in 0x%x", addr, otp_result);
+ if (!(otp_result & OTP_MASTER_ERROR))
+ ret = 0;

- if (mutex_lock_interruptible(&bfin_otp_lock))
- return -ERESTARTSYS;
+ bfin_otp_deinit_timing(timing);
+ }

- timing = bfin_otp_init_timing();
- if (timing) {
- u32 otp_result = bfrom_OtpWrite(arg, OTP_LOCK, NULL);
- stamp("locking page %lu resulted in 0x%x", arg, otp_result);
- if (!(otp_result & OTP_MASTER_ERROR))
- ret = 0;
+ return ret;
+}
+#else /* CONFIG_OTP_WRITE_ENABLE */
+#define bfin_write_word NULL
+#define bfin_lock_word NULL
+#endif /* CONFIG_OTP_WRITE_ENABLE */

- bfin_otp_deinit_timing(timing);
- }
+static ssize_t bfin_otp_get_nr_regions(struct otp_device *dev)
+{
+ return 1;
+}

- mutex_unlock(&bfin_otp_lock);
+static const struct otp_device_ops bfin_otp_ops = {
+ .name = "bfin-otp",
+ .owner = THIS_MODULE,
+ .get_nr_regions = bfin_otp_get_nr_regions,
+ .read_word = bfin_read_word,
+ .write_word = bfin_write_word,
+ .lock_word = bfin_lock_word,
+};

- return ret;
- }
+static ssize_t bfin_region_get_size(struct otp_region *region)
+{
+ return BFIN_OTP_SIZE;
+}

- case MEMLOCK:
- allow_writes = false;
- return 0;
+static enum otp_redundancy_fmt bfin_region_get_fmt(struct otp_region *region)
+{
+ return OTP_REDUNDANCY_FMT_ECC;
+}

- case MEMUNLOCK:
- allow_writes = true;
- return 0;
+static const struct otp_region_ops bfin_region_ops = {
+ .get_size = bfin_region_get_size,
+ .get_fmt = bfin_region_get_fmt,
+};
+
+static int __devinit bfin_otp_probe(struct platform_device *pdev)
+{
+ struct otp_region *region;
+
+ stampit();
+
+ bfin_otp = otp_device_alloc(&pdev->dev, &bfin_otp_ops, BFIN_OTP_SIZE,
+ 8, 1, OTP_CAPS_NO_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, "region1");
+ if (IS_ERR(region)) {
+ otp_device_unregister(bfin_otp);
+ return PTR_ERR(region);
}
+ pr_init(KERN_INFO PFX "initialized\n");

- return -EINVAL;
+ return 0;
}
-#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,
-};

-static struct miscdevice bfin_otp_misc_device = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = DRIVER_NAME,
- .fops = &bfin_otp_fops,
+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",
};

/**
@@ -239,19 +234,7 @@ static struct miscdevice bfin_otp_misc_device = {
*/
static int __init bfin_otp_init(void)
{
- int ret;
-
- stampit();
-
- ret = misc_register(&bfin_otp_misc_device);
- if (ret) {
- pr_init(KERN_ERR PFX "unable to register a misc device\n");
- return ret;
- }
-
- pr_init(KERN_INFO PFX "initialized\n");
-
- return 0;
+ return platform_driver_register(&bfin_otp_driver);
}

/**
@@ -262,9 +245,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-29 12:08:48

by Jamie Iles

[permalink] [raw]
Subject: [RFC PATCHv4 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 v3:
- Move the read_word/write_word ops into device ops.

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 | 1102 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1110 insertions(+), 0 deletions(-)
create mode 100644 drivers/otp/otp_pc3x3.c

diff --git a/drivers/otp/Kconfig b/drivers/otp/Kconfig
index a1ce310..edac4d5 100644
--- a/drivers/otp/Kconfig
+++ b/drivers/otp/Kconfig
@@ -25,4 +25,11 @@ config OTP_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..cde677e
--- /dev/null
+++ b/drivers/otp/otp_pc3x3.c
@@ -0,0 +1,1102 @@
+/*
+ * 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_write_word(struct otp_device *otp_dev,
+ struct otp_region *region, unsigned long addr,
+ u64 word)
+{
+ struct pc3x3_otp *otp = otp_dev_get_drvdata(otp_dev);
+ 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 (otp_dev->ops->read_word(otp_dev, 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_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_read_word(struct otp_device *otp_dev,
+ struct otp_region *region, unsigned long addr,
+ u64 *word)
+{
+ struct pc3x3_otp *otp = otp_dev_get_drvdata(otp_dev);
+ 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,
+ .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,
+ "region%d", i) :
+ otp_region_alloc(otp, &pc3x3_region_ops, i,
+ "region%d", i);
+ if (IS_ERR(region)) {
+ err = PTR_ERR(region);
+ break;
+ }
+ }
+
+ return err;
+}
+
+static ssize_t 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,
+ .write_word = pc3x3_otp_write_word,
+ .read_word = pc3x3_otp_read_word,
+};
+
+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