]> git.itanic.dy.fi Git - linux-stable/commitdiff
nvmem: core: introduce NVMEM layouts
authorMichael Walle <michael@walle.cc>
Tue, 4 Apr 2023 17:21:21 +0000 (18:21 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 5 Apr 2023 17:41:11 +0000 (19:41 +0200)
NVMEM layouts are used to generate NVMEM cells during runtime. Think of
an EEPROM with a well-defined conent. For now, the content can be
described by a device tree or a board file. But this only works if the
offsets and lengths are static and don't change. One could also argue
that putting the layout of the EEPROM in the device tree is the wrong
place. Instead, the device tree should just have a specific compatible
string.

Right now there are two use cases:
 (1) The NVMEM cell needs special processing. E.g. if it only specifies
     a base MAC address offset and you need to add an offset, or it
     needs to parse a MAC from ASCII format or some proprietary format.
     (Post processing of cells is added in a later commit).
 (2) u-boot environment parsing. The cells don't have a particular
     offset but it needs parsing the content to determine the offsets
     and length.

Co-developed-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Michael Walle <michael@walle.cc>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20230404172148.82422-14-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/driver-api/nvmem.rst
drivers/nvmem/Kconfig
drivers/nvmem/Makefile
drivers/nvmem/core.c
drivers/nvmem/layouts/Kconfig [new file with mode: 0644]
drivers/nvmem/layouts/Makefile [new file with mode: 0644]
include/linux/nvmem-consumer.h
include/linux/nvmem-provider.h

index e3366322d46c0cc496cfe9b1a6f94a06355de10b..de221e91c8e34d3ec64e8cc94b88f36a6a2ec738 100644 (file)
@@ -185,3 +185,18 @@ ex::
 =====================
 
 See Documentation/devicetree/bindings/nvmem/nvmem.txt
+
+8. NVMEM layouts
+================
+
+NVMEM layouts are yet another mechanism to create cells. With the device
+tree binding it is possible to specify simple cells by using an offset
+and a length. Sometimes, the cells doesn't have a static offset, but
+the content is still well defined, e.g. tag-length-values. In this case,
+the NVMEM device content has to be first parsed and the cells need to
+be added accordingly. Layouts let you read the content of the NVMEM device
+and let you add cells dynamically.
+
+Another use case for layouts is the post processing of cells. With layouts,
+it is possible to associate a custom post processing hook to a cell. It
+even possible to add this hook to cells not created by the layout itself.
index 3b3832f4dfad1503f8ea43c5f75029d08585af83..a2afba11c8901d384eb638f022b6301b82e7c114 100644 (file)
@@ -21,6 +21,10 @@ config NVMEM_SYSFS
         This interface is mostly used by userspace applications to
         read/write directly into nvmem.
 
+# Layouts
+
+source "drivers/nvmem/layouts/Kconfig"
+
 # Devices
 
 config NVMEM_APPLE_EFUSES
index 6a1efffa88f074571e805b171a3c3a94e3c91ddd..f82431ec8aef0c2ebdc89b83578a8bbe5a188b58 100644 (file)
@@ -5,6 +5,7 @@
 
 obj-$(CONFIG_NVMEM)            += nvmem_core.o
 nvmem_core-y                   := core.o
+obj-y                          += layouts/
 
 # Devices
 obj-$(CONFIG_NVMEM_APPLE_EFUSES)       += nvmem-apple-efuses.o
index 22024b830788f86d23a213809222511c7ae6ea84..b9be1faeb7bec746164ae308fe3ff4c16e68a69a 100644 (file)
@@ -40,6 +40,7 @@ struct nvmem_device {
        nvmem_reg_write_t       reg_write;
        nvmem_cell_post_process_t cell_post_process;
        struct gpio_desc        *wp_gpio;
+       struct nvmem_layout     *layout;
        void *priv;
 };
 
@@ -74,6 +75,9 @@ static LIST_HEAD(nvmem_lookup_list);
 
 static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
 
+static DEFINE_SPINLOCK(nvmem_layout_lock);
+static LIST_HEAD(nvmem_layouts);
+
 static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
                            void *val, size_t bytes)
 {
@@ -728,6 +732,101 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
        return 0;
 }
 
+int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner)
+{
+       layout->owner = owner;
+
+       spin_lock(&nvmem_layout_lock);
+       list_add(&layout->node, &nvmem_layouts);
+       spin_unlock(&nvmem_layout_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(__nvmem_layout_register);
+
+void nvmem_layout_unregister(struct nvmem_layout *layout)
+{
+       spin_lock(&nvmem_layout_lock);
+       list_del(&layout->node);
+       spin_unlock(&nvmem_layout_lock);
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
+
+static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem)
+{
+       struct device_node *layout_np, *np = nvmem->dev.of_node;
+       struct nvmem_layout *l, *layout = NULL;
+
+       layout_np = of_get_child_by_name(np, "nvmem-layout");
+       if (!layout_np)
+               return NULL;
+
+       spin_lock(&nvmem_layout_lock);
+
+       list_for_each_entry(l, &nvmem_layouts, node) {
+               if (of_match_node(l->of_match_table, layout_np)) {
+                       if (try_module_get(l->owner))
+                               layout = l;
+
+                       break;
+               }
+       }
+
+       spin_unlock(&nvmem_layout_lock);
+       of_node_put(layout_np);
+
+       return layout;
+}
+
+static void nvmem_layout_put(struct nvmem_layout *layout)
+{
+       if (layout)
+               module_put(layout->owner);
+}
+
+static int nvmem_add_cells_from_layout(struct nvmem_device *nvmem)
+{
+       struct nvmem_layout *layout = nvmem->layout;
+       int ret;
+
+       if (layout && layout->add_cells) {
+               ret = layout->add_cells(&nvmem->dev, nvmem, layout);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF)
+/**
+ * of_nvmem_layout_get_container() - Get OF node to layout container.
+ *
+ * @nvmem: nvmem device.
+ *
+ * Return: a node pointer with refcount incremented or NULL if no
+ * container exists. Use of_node_put() on it when done.
+ */
+struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+{
+       return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+}
+EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
+#endif
+
+const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+                                       struct nvmem_layout *layout)
+{
+       struct device_node __maybe_unused *layout_np;
+       const struct of_device_id *match;
+
+       layout_np = of_nvmem_layout_get_container(nvmem);
+       match = of_match_node(layout->of_match_table, layout_np);
+
+       return match ? match->data : NULL;
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_get_match_data);
+
 /**
  * nvmem_register() - Register a nvmem device for given nvmem_config.
  * Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
@@ -834,6 +933,12 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
                        goto err_put_device;
        }
 
+       /*
+        * If the driver supplied a layout by config->layout, the module
+        * pointer will be NULL and nvmem_layout_put() will be a noop.
+        */
+       nvmem->layout = config->layout ?: nvmem_layout_get(nvmem);
+
        if (config->cells) {
                rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
                if (rval)
@@ -854,12 +959,17 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
        if (rval)
                goto err_remove_cells;
 
+       rval = nvmem_add_cells_from_layout(nvmem);
+       if (rval)
+               goto err_remove_cells;
+
        blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
 
        return nvmem;
 
 err_remove_cells:
        nvmem_device_remove_all_cells(nvmem);
+       nvmem_layout_put(nvmem->layout);
        if (config->compat)
                nvmem_sysfs_remove_compat(nvmem, config);
 err_put_device:
@@ -881,6 +991,7 @@ static void nvmem_device_release(struct kref *kref)
                device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
 
        nvmem_device_remove_all_cells(nvmem);
+       nvmem_layout_put(nvmem->layout);
        device_unregister(&nvmem->dev);
 }
 
@@ -1246,6 +1357,15 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
                return ERR_PTR(-EINVAL);
        }
 
+       /* nvmem layouts produce cells within the nvmem-layout container */
+       if (of_node_name_eq(nvmem_np, "nvmem-layout")) {
+               nvmem_np = of_get_next_parent(nvmem_np);
+               if (!nvmem_np) {
+                       of_node_put(cell_np);
+                       return ERR_PTR(-EINVAL);
+               }
+       }
+
        nvmem = __nvmem_device_get(nvmem_np, device_match_of_node);
        of_node_put(nvmem_np);
        if (IS_ERR(nvmem)) {
diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig
new file mode 100644 (file)
index 0000000..9ad3911
--- /dev/null
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+menu "Layout Types"
+
+endmenu
diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile
new file mode 100644 (file)
index 0000000..6fdb3c6
--- /dev/null
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for nvmem layouts.
+#
index 1f62f7ba71ca541ba422b708010ad2984e215f04..fa030d93b768e5bf7095b40ee592c4cc35ba6135 100644 (file)
@@ -239,6 +239,7 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
                                     const char *id);
 struct nvmem_device *of_nvmem_device_get(struct device_node *np,
                                         const char *name);
+struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem);
 #else
 static inline struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
                                                   const char *id)
@@ -251,6 +252,12 @@ static inline struct nvmem_device *of_nvmem_device_get(struct device_node *np,
 {
        return ERR_PTR(-EOPNOTSUPP);
 }
+
+static inline struct device_node *
+of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+{
+       return ERR_PTR(-EOPNOTSUPP);
+}
 #endif /* CONFIG_NVMEM && CONFIG_OF */
 
 #endif  /* ifndef _LINUX_NVMEM_CONSUMER_H */
index 0262b86194eb6d48e57733fd43d1b90a96d43466..535c5f9f330985f1449c4904d9901f412d3fbad7 100644 (file)
@@ -88,6 +88,7 @@ struct nvmem_cell_info {
  * @stride:    Minimum read/write access stride.
  * @priv:      User context passed to read/write callbacks.
  * @ignore_wp:  Write Protect pin is managed by the provider.
+ * @layout:    Fixed layout associated with this nvmem device.
  *
  * Note: A default "nvmem<id>" name will be assigned to the device if
  * no name is specified in its configuration. In such case "<id>" is
@@ -109,6 +110,7 @@ struct nvmem_config {
        bool                    read_only;
        bool                    root_only;
        bool                    ignore_wp;
+       struct nvmem_layout     *layout;
        struct device_node      *of_node;
        bool                    no_of_node;
        nvmem_reg_read_t        reg_read;
@@ -142,6 +144,33 @@ struct nvmem_cell_table {
        struct list_head        node;
 };
 
+/**
+ * struct nvmem_layout - NVMEM layout definitions
+ *
+ * @name:              Layout name.
+ * @of_match_table:    Open firmware match table.
+ * @add_cells:         Will be called if a nvmem device is found which
+ *                     has this layout. The function will add layout
+ *                     specific cells with nvmem_add_one_cell().
+ * @owner:             Pointer to struct module.
+ * @node:              List node.
+ *
+ * A nvmem device can hold a well defined structure which can just be
+ * evaluated during runtime. For example a TLV list, or a list of "name=val"
+ * pairs. A nvmem layout can parse the nvmem device and add appropriate
+ * cells.
+ */
+struct nvmem_layout {
+       const char *name;
+       const struct of_device_id *of_match_table;
+       int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
+                        struct nvmem_layout *layout);
+
+       /* private */
+       struct module *owner;
+       struct list_head node;
+};
+
 #if IS_ENABLED(CONFIG_NVMEM)
 
 struct nvmem_device *nvmem_register(const struct nvmem_config *cfg);
@@ -156,6 +185,14 @@ void nvmem_del_cell_table(struct nvmem_cell_table *table);
 int nvmem_add_one_cell(struct nvmem_device *nvmem,
                       const struct nvmem_cell_info *info);
 
+int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner);
+#define nvmem_layout_register(layout) \
+       __nvmem_layout_register(layout, THIS_MODULE)
+void nvmem_layout_unregister(struct nvmem_layout *layout);
+
+const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+                                       struct nvmem_layout *layout);
+
 #else
 
 static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
@@ -179,5 +216,19 @@ static inline int nvmem_add_one_cell(struct nvmem_device *nvmem,
        return -EOPNOTSUPP;
 }
 
+static inline int nvmem_layout_register(struct nvmem_layout *layout)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {}
+
+static inline const void *
+nvmem_layout_get_match_data(struct nvmem_device *nvmem,
+                           struct nvmem_layout *layout)
+{
+       return NULL;
+}
+
 #endif /* CONFIG_NVMEM */
 #endif  /* ifndef _LINUX_NVMEM_PROVIDER_H */