diff options
| author | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
| commit | 1ac731c529cd4d6adbce134754b51ff7d822b145 (patch) | |
| tree | 143ab3f35ca5f3b69f583c84e6964b17139c2ec1 /drivers/nvmem/core.c | |
| parent | 07b4c950f27bef0362dc6ad7ee713aab61d58149 (diff) | |
| parent | 54116d442e001e1b6bd482122043b1870998a1f3 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.6 merge window.
Diffstat (limited to 'drivers/nvmem/core.c')
| -rw-r--r-- | drivers/nvmem/core.c | 172 | 
1 files changed, 162 insertions, 10 deletions
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 174ef3574e07..342cd380b420 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -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)) @@ -1958,4 +2111,3 @@ module_exit(nvmem_exit);  MODULE_AUTHOR("Srinivas Kandagatla <[email protected]");  MODULE_AUTHOR("Maxime Ripard <[email protected]");  MODULE_DESCRIPTION("nvmem Driver Core"); -MODULE_LICENSE("GPL v2");  |