nvmem: core: introduce NVMEM layouts
[platform/kernel/linux-starfive.git] / drivers / nvmem / core.c
index 22024b8..b9be1fa 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)) {