+From fd3bb8f54a88107570334c156efb0c724a261003 Mon Sep 17 00:00:00 2001
+From: Evan Green <evgreen@chromium.org>
+Date: Fri, 27 Nov 2020 10:28:34 +0000
+Subject: [PATCH] nvmem: core: Add support for keepout regions
+
+Introduce support into the nvmem core for arrays of register ranges
+that should not result in actual device access. For these regions a
+constant byte (repeated) is returned instead on read, and writes are
+quietly ignored and returned as successful.
+
+This is useful for instance if certain efuse regions are protected
+from access by Linux because they contain secret info to another part
+of the system (like an integrated modem).
+
+Signed-off-by: Evan Green <evgreen@chromium.org>
+Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+Link: https://lore.kernel.org/r/20201127102837.19366-3-srinivas.kandagatla@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvmem/core.c | 153 ++++++++++++++++++++++++++++++++-
+ include/linux/nvmem-provider.h | 17 ++++
+ 2 files changed, 166 insertions(+), 4 deletions(-)
+
+--- a/drivers/nvmem/core.c
++++ b/drivers/nvmem/core.c
+@@ -34,6 +34,8 @@ struct nvmem_device {
+ struct bin_attribute eeprom;
+ struct device *base_dev;
+ struct list_head cells;
++ const struct nvmem_keepout *keepout;
++ unsigned int nkeepout;
+ nvmem_reg_read_t reg_read;
+ nvmem_reg_write_t reg_write;
+ struct gpio_desc *wp_gpio;
+@@ -66,8 +68,8 @@ static LIST_HEAD(nvmem_lookup_list);
+
+ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
+
+-static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
+- void *val, size_t bytes)
++static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
++ void *val, size_t bytes)
+ {
+ if (nvmem->reg_read)
+ return nvmem->reg_read(nvmem->priv, offset, val, bytes);
+@@ -75,8 +77,8 @@ static int nvmem_reg_read(struct nvmem_d
+ return -EINVAL;
+ }
+
+-static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
+- void *val, size_t bytes)
++static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
++ void *val, size_t bytes)
+ {
+ int ret;
+
+@@ -90,6 +92,88 @@ static int nvmem_reg_write(struct nvmem_
+ return -EINVAL;
+ }
+
++static int nvmem_access_with_keepouts(struct nvmem_device *nvmem,
++ unsigned int offset, void *val,
++ size_t bytes, int write)
++{
++
++ unsigned int end = offset + bytes;
++ unsigned int kend, ksize;
++ const struct nvmem_keepout *keepout = nvmem->keepout;
++ const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout;
++ int rc;
++
++ /*
++ * Skip all keepouts before the range being accessed.
++ * Keepouts are sorted.
++ */
++ while ((keepout < keepoutend) && (keepout->end <= offset))
++ keepout++;
++
++ while ((offset < end) && (keepout < keepoutend)) {
++ /* Access the valid portion before the keepout. */
++ if (offset < keepout->start) {
++ kend = min(end, keepout->start);
++ ksize = kend - offset;
++ if (write)
++ rc = __nvmem_reg_write(nvmem, offset, val, ksize);
++ else
++ rc = __nvmem_reg_read(nvmem, offset, val, ksize);
++
++ if (rc)
++ return rc;
++
++ offset += ksize;
++ val += ksize;
++ }
++
++ /*
++ * Now we're aligned to the start of this keepout zone. Go
++ * through it.
++ */
++ kend = min(end, keepout->end);
++ ksize = kend - offset;
++ if (!write)
++ memset(val, keepout->value, ksize);
++
++ val += ksize;
++ offset += ksize;
++ keepout++;
++ }
++
++ /*
++ * If we ran out of keepouts but there's still stuff to do, send it
++ * down directly
++ */
++ if (offset < end) {
++ ksize = end - offset;
++ if (write)
++ return __nvmem_reg_write(nvmem, offset, val, ksize);
++ else
++ return __nvmem_reg_read(nvmem, offset, val, ksize);
++ }
++
++ return 0;
++}
++
++static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
++ void *val, size_t bytes)
++{
++ if (!nvmem->nkeepout)
++ return __nvmem_reg_read(nvmem, offset, val, bytes);
++
++ return nvmem_access_with_keepouts(nvmem, offset, val, bytes, false);
++}
++
++static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
++ void *val, size_t bytes)
++{
++ if (!nvmem->nkeepout)
++ return __nvmem_reg_write(nvmem, offset, val, bytes);
++
++ return nvmem_access_with_keepouts(nvmem, offset, val, bytes, true);
++}
++
+ #ifdef CONFIG_NVMEM_SYSFS
+ static const char * const nvmem_type_str[] = {
+ [NVMEM_TYPE_UNKNOWN] = "Unknown",
+@@ -535,6 +619,59 @@ nvmem_find_cell_by_name(struct nvmem_dev
+ return cell;
+ }
+
++static int nvmem_validate_keepouts(struct nvmem_device *nvmem)
++{
++ unsigned int cur = 0;
++ const struct nvmem_keepout *keepout = nvmem->keepout;
++ const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout;
++
++ while (keepout < keepoutend) {
++ /* Ensure keepouts are sorted and don't overlap. */
++ if (keepout->start < cur) {
++ dev_err(&nvmem->dev,
++ "Keepout regions aren't sorted or overlap.\n");
++
++ return -ERANGE;
++ }
++
++ if (keepout->end < keepout->start) {
++ dev_err(&nvmem->dev,
++ "Invalid keepout region.\n");
++
++ return -EINVAL;
++ }
++
++ /*
++ * Validate keepouts (and holes between) don't violate
++ * word_size constraints.
++ */
++ if ((keepout->end - keepout->start < nvmem->word_size) ||
++ ((keepout->start != cur) &&
++ (keepout->start - cur < nvmem->word_size))) {
++
++ dev_err(&nvmem->dev,
++ "Keepout regions violate word_size constraints.\n");
++
++ return -ERANGE;
++ }
++
++ /* Validate keepouts don't violate stride (alignment). */
++ if (!IS_ALIGNED(keepout->start, nvmem->stride) ||
++ !IS_ALIGNED(keepout->end, nvmem->stride)) {
++
++ dev_err(&nvmem->dev,
++ "Keepout regions violate stride.\n");
++
++ return -EINVAL;
++ }
++
++ cur = keepout->end;
++ keepout++;
++ }
++
++ return 0;
++}
++
+ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
+ {
+ struct device_node *parent, *child;
+@@ -655,6 +792,8 @@ struct nvmem_device *nvmem_register(cons
+ nvmem->type = config->type;
+ nvmem->reg_read = config->reg_read;
+ nvmem->reg_write = config->reg_write;
++ nvmem->keepout = config->keepout;
++ nvmem->nkeepout = config->nkeepout;
+ if (!config->no_of_node)
+ nvmem->dev.of_node = config->dev->of_node;
+
+@@ -679,6 +818,12 @@ struct nvmem_device *nvmem_register(cons
+ nvmem->dev.groups = nvmem_dev_groups;
+ #endif
+
++ if (nvmem->nkeepout) {
++ rval = nvmem_validate_keepouts(nvmem);
++ if (rval)
++ goto err_put_device;
++ }
++
+ dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
+
+ rval = device_register(&nvmem->dev);
+--- a/include/linux/nvmem-provider.h
++++ b/include/linux/nvmem-provider.h
+@@ -31,6 +31,19 @@ enum nvmem_type {
+ #define NVMEM_DEVID_AUTO (-2)
+
+ /**
++ * struct nvmem_keepout - NVMEM register keepout range.
++ *
++ * @start: The first byte offset to avoid.
++ * @end: One beyond the last byte offset to avoid.
++ * @value: The byte to fill reads with for this region.
++ */
++struct nvmem_keepout {
++ unsigned int start;
++ unsigned int end;
++ unsigned char value;
++};
++
++/**
+ * struct nvmem_config - NVMEM device configuration
+ *
+ * @dev: Parent device.
+@@ -39,6 +52,8 @@ enum nvmem_type {
+ * @owner: Pointer to exporter module. Used for refcounting.
+ * @cells: Optional array of pre-defined NVMEM cells.
+ * @ncells: Number of elements in cells.
++ * @keepout: Optional array of keepout ranges (sorted ascending by start).
++ * @nkeepout: Number of elements in the keepout array.
+ * @type: Type of the nvmem storage
+ * @read_only: Device is read-only.
+ * @root_only: Device is accessibly to root only.
+@@ -66,6 +81,8 @@ struct nvmem_config {
+ struct gpio_desc *wp_gpio;
+ const struct nvmem_cell_info *cells;
+ int ncells;
++ const struct nvmem_keepout *keepout;
++ unsigned int nkeepout;
+ enum nvmem_type type;
+ bool read_only;
+ bool root_only;