Merge tag 'modules-6.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mcgrof...
[linux-2.6-microblaze.git] / drivers / nvmem / core.c
index 8de427d..342cd38 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/nvmem-provider.h>
 #include <linux/gpio/consumer.h>
 #include <linux/of.h>
+#include <linux/of_device.h>
 #include <linux/slab.h>
 
 struct nvmem_device {
@@ -38,8 +39,8 @@ struct nvmem_device {
        unsigned int            nkeepout;
        nvmem_reg_read_t        reg_read;
        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;
 };
 
@@ -49,9 +50,12 @@ struct nvmem_device {
 struct nvmem_cell_entry {
        const char              *name;
        int                     offset;
+       size_t                  raw_len;
        int                     bytes;
        int                     bit_offset;
        int                     nbits;
+       nvmem_cell_post_process_t read_post_process;
+       void                    *priv;
        struct device_node      *np;
        struct nvmem_device     *nvmem;
        struct list_head        node;
@@ -74,6 +78,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)
 {
@@ -463,8 +470,11 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
 {
        cell->nvmem = nvmem;
        cell->offset = info->offset;
+       cell->raw_len = info->raw_len ?: info->bytes;
        cell->bytes = info->bytes;
        cell->name = info->name;
+       cell->read_post_process = info->read_post_process;
+       cell->priv = info->priv;
 
        cell->bit_offset = info->bit_offset;
        cell->nbits = info->nbits;
@@ -688,6 +698,7 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem)
 
 static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
 {
+       struct nvmem_layout *layout = nvmem->layout;
        struct device *dev = &nvmem->dev;
        struct device_node *child;
        const __be32 *addr;
@@ -717,6 +728,9 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem)
 
                info.np = of_node_get(child);
 
+               if (layout && layout->fixup_cell_info)
+                       layout->fixup_cell_info(nvmem, layout, &info);
+
                ret = nvmem_add_one_cell(nvmem, &info);
                kfree(info.name);
                if (ret) {
@@ -728,6 +742,108 @@ 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 = ERR_PTR(-EPROBE_DEFER);
+
+       layout_np = of_get_child_by_name(np, "nvmem-layout");
+       if (!layout_np)
+               return NULL;
+
+       /*
+        * In case the nvmem device was built-in while the layout was built as a
+        * module, we shall manually request the layout driver loading otherwise
+        * we'll never have any match.
+        */
+       of_request_module(layout_np);
+
+       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
@@ -790,7 +906,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
        nvmem->type = config->type;
        nvmem->reg_read = config->reg_read;
        nvmem->reg_write = config->reg_write;
-       nvmem->cell_post_process = config->cell_post_process;
        nvmem->keepout = config->keepout;
        nvmem->nkeepout = config->nkeepout;
        if (config->of_node)
@@ -834,6 +949,19 @@ 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 (IS_ERR(nvmem->layout)) {
+               rval = PTR_ERR(nvmem->layout);
+               nvmem->layout = NULL;
+
+               if (rval == -EPROBE_DEFER)
+                       goto err_teardown_compat;
+       }
+
        if (config->cells) {
                rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
                if (rval)
@@ -854,12 +982,18 @@ 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);
+err_teardown_compat:
        if (config->compat)
                nvmem_sysfs_remove_compat(nvmem, config);
 err_put_device:
@@ -881,6 +1015,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);
 }
 
@@ -1231,7 +1366,7 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
                                                  "#nvmem-cell-cells",
                                                  index, &cell_spec);
        if (ret)
-               return ERR_PTR(ret);
+               return ERR_PTR(-ENOENT);
 
        if (cell_spec.args_count > 1)
                return ERR_PTR(-EINVAL);
@@ -1246,6 +1381,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)) {
@@ -1418,7 +1562,7 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
 {
        int rc;
 
-       rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->bytes);
+       rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->raw_len);
 
        if (rc)
                return rc;
@@ -1427,9 +1571,9 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
        if (cell->bit_offset || cell->nbits)
                nvmem_shift_read_buffer_in_place(cell, buf);
 
-       if (nvmem->cell_post_process) {
-               rc = nvmem->cell_post_process(nvmem->priv, id, index,
-                                             cell->offset, buf, cell->bytes);
+       if (cell->read_post_process) {
+               rc = cell->read_post_process(cell->priv, id, index,
+                                            cell->offset, buf, cell->raw_len);
                if (rc)
                        return rc;
        }
@@ -1452,14 +1596,15 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
  */
 void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
 {
-       struct nvmem_device *nvmem = cell->entry->nvmem;
+       struct nvmem_cell_entry *entry = cell->entry;
+       struct nvmem_device *nvmem = entry->nvmem;
        u8 *buf;
        int rc;
 
        if (!nvmem)
                return ERR_PTR(-EINVAL);
 
-       buf = kzalloc(cell->entry->bytes, GFP_KERNEL);
+       buf = kzalloc(max_t(size_t, entry->raw_len, entry->bytes), GFP_KERNEL);
        if (!buf)
                return ERR_PTR(-ENOMEM);
 
@@ -1535,6 +1680,14 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, si
            (cell->bit_offset == 0 && len != cell->bytes))
                return -EINVAL;
 
+       /*
+        * Any cells which have a read_post_process hook are read-only because
+        * we cannot reverse the operation and it might affect other cells,
+        * too.
+        */
+       if (cell->read_post_process)
+               return -EINVAL;
+
        if (cell->bit_offset || cell->nbits) {
                buf = nvmem_cell_prepare_write_buffer(cell, buf, len);
                if (IS_ERR(buf))