diff options
Diffstat (limited to 'drivers/nvmem/core.c')
-rw-r--r-- | drivers/nvmem/core.c | 169 |
1 files changed, 161 insertions, 8 deletions
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 22024b830788..a62973d010ff 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); } @@ -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)) |