Commit 266570f4 authored by Michael Walle's avatar Michael Walle Committed by Greg Kroah-Hartman
Browse files

nvmem: core: introduce NVMEM layouts



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: default avatarMiquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: default avatarMiquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: default avatarMichael Walle <michael@walle.cc>
Signed-off-by: default avatarSrinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20230404172148.82422-14-srinivas.kandagatla@linaro.org


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 2f555f58
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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.
+4 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+120 −0
Original line number Diff line number Diff line
@@ -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)) {
+5 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0

menu "Layout Types"

endmenu
Loading