// SPDX-License-Identifier: GPL-2.0/* * nvmem framework core. * * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> */ #include <linux/device.h>#include <linux/export.h>#include <linux/fs.h>#include <linux/idr.h>#include <linux/init.h>#include <linux/kref.h>#include <linux/module.h>#include <linux/nvmem-consumer.h>#include <linux/nvmem-provider.h>#include <linux/gpio/consumer.h>#include <linux/of.h>#include <linux/slab.h> struct nvmem_device { struct module *owner; struct device dev; int stride; int word_size; int id; struct kref refcnt; size_t size; bool read_only; bool root_only; int flags; enum nvmem_type type; struct bin_attribute eeprom; struct device *base_dev; struct list_head cells; const struct nvmem_keepout *keepout; unsigned int nkeepout; nvmem_reg_read_t reg_read; nvmem_reg_write_t reg_write; struct gpio_desc *wp_gpio; void *priv;}; #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) #define FLAG_COMPAT BIT(0) struct nvmem_cell { const char *name; int offset; int bytes; int bit_offset; int nbits; struct device_node *np; struct nvmem_device *nvmem; struct list_head node;}; static DEFINE_MUTEX(nvmem_mutex);static DEFINE_IDA(nvmem_ida); static DEFINE_MUTEX(nvmem_cell_mutex);static LIST_HEAD(nvmem_cell_tables); static DEFINE_MUTEX(nvmem_lookup_mutex);static LIST_HEAD(nvmem_lookup_list); static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes){ if (nvmem->reg_read) return nvmem->reg_read(nvmem->priv, offset, val, bytes); return -EINVAL;} static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes){ int ret; if (nvmem->reg_write) { gpiod_set_value_cansleep(nvmem->wp_gpio, 0); ret = nvmem->reg_write(nvmem->priv, offset, val, bytes); gpiod_set_value_cansleep(nvmem->wp_gpio, 1); return ret; } return -EINVAL;} static int nvmem_access_with_keepouts(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes, int write){ unsigned int end = offset + bytes; unsigned int kend, ksize; const struct nvmem_keepout *keepout = nvmem->keepout; const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout; int rc; /* * Skip all keepouts before the range being accessed. * Keepouts are sorted. */ while ((keepout < keepoutend) && (keepout->end <= offset)) keepout++; while ((offset < end) && (keepout < keepoutend)) { /* Access the valid portion before the keepout. */ if (offset < keepout->start) { kend = min(end, keepout->start); ksize = kend - offset; if (write) rc = __nvmem_reg_write(nvmem, offset, val, ksize); else rc = __nvmem_reg_read(nvmem, offset, val, ksize); if (rc) return rc; offset += ksize; val += ksize; } /* * Now we're aligned to the start of this keepout zone. Go * through it. */ kend = min(end, keepout->end); ksize = kend - offset; if (!write) memset(val, keepout->value, ksize); val += ksize; offset += ksize; keepout++; } /* * If we ran out of keepouts but there's still stuff to do, send it * down directly */ if (offset < end) { ksize = end - offset; if (write) return __nvmem_reg_write(nvmem, offset, val, ksize); else return __nvmem_reg_read(nvmem, offset, val, ksize); } return 0;} static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes){ if (!nvmem->nkeepout) return __nvmem_reg_read(nvmem, offset, val, bytes); return nvmem_access_with_keepouts(nvmem, offset, val, bytes, false);} static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes){ if (!nvmem->nkeepout) return __nvmem_reg_write(nvmem, offset, val, bytes); return nvmem_access_with_keepouts(nvmem, offset, val, bytes, true);} #ifdef CONFIG_NVMEM_SYSFSstatic const char * const nvmem_type_str[] = { [NVMEM_TYPE_UNKNOWN] = "Unknown", [NVMEM_TYPE_EEPROM] = "EEPROM", [NVMEM_TYPE_OTP] = "OTP", [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed",}; #ifdef CONFIG_DEBUG_LOCK_ALLOCstatic struct lock_class_key eeprom_lock_key;#endif static ssize_t type_show(struct device *dev, struct device_attribute *attr, char *buf){ struct nvmem_device *nvmem = to_nvmem_device(dev); return sprintf(buf, "%s\n", nvmem_type_str[nvmem->type]);} static DEVICE_ATTR_RO(type); static struct attribute *nvmem_attrs[] = { &dev_attr_type.attr, NULL,}; static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t pos, size_t count){ struct device *dev; struct nvmem_device *nvmem; int rc; if (attr->private) dev = attr->private; else dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); /* Stop the user from reading */ if (pos >= nvmem->size) return 0; if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; if (pos + count > nvmem->size) count = nvmem->size - pos; count = round_down(count, nvmem->word_size); if (!nvmem->reg_read) return -EPERM; rc = nvmem_reg_read(nvmem, pos, buf, count); if (rc) return rc; return count;} static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t pos, size_t count){ struct device *dev; struct nvmem_device *nvmem; int rc; if (attr->private) dev = attr->private; else dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); /* Stop the user from writing */ if (pos >= nvmem->size) return -EFBIG; if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; if (pos + count > nvmem->size) count = nvmem->size - pos; count = round_down(count, nvmem->word_size); if (!nvmem->reg_write) return -EPERM; rc = nvmem_reg_write(nvmem, pos, buf, count); if (rc) return rc; return count;} static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem){ umode_t mode = 0400; if (!nvmem->root_only) mode |= 0044; if (!nvmem->read_only) mode |= 0200; if (!nvmem->reg_write) mode &= ~0200; if (!nvmem->reg_read) mode &= ~0444; return mode;} static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, struct bin_attribute *attr, int i){ struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); return nvmem_bin_attr_get_umode(nvmem);} /* default read/write permissions */static struct bin_attribute bin_attr_rw_nvmem = { .attr = { .name = "nvmem", .mode = 0644, }, .read = bin_attr_nvmem_read, .write = bin_attr_nvmem_write,}; static struct bin_attribute *nvmem_bin_attributes[] = { &bin_attr_rw_nvmem, NULL,}; static const struct attribute_group nvmem_bin_group = { .bin_attrs = nvmem_bin_attributes, .attrs = nvmem_attrs, .is_bin_visible = nvmem_bin_attr_is_visible,}; static const struct attribute_group *nvmem_dev_groups[] = { &nvmem_bin_group, NULL,}; static struct bin_attribute bin_attr_nvmem_eeprom_compat = { .attr = { .name = "eeprom", }, .read = bin_attr_nvmem_read, .write = bin_attr_nvmem_write,}; /* * nvmem_setup_compat() - Create an additional binary entry in * drivers sys directory, to be backwards compatible with the older * drivers/misc/eeprom drivers. */static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, const struct nvmem_config *config){ int rval; if (!config->compat) return 0; if (!config->base_dev) return -EINVAL; nvmem->eeprom = bin_attr_nvmem_eeprom_compat; nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem); nvmem->eeprom.size = nvmem->size;#ifdef CONFIG_DEBUG_LOCK_ALLOC nvmem->eeprom.attr.key = &eeprom_lock_key;#endif nvmem->eeprom.private = &nvmem->dev; nvmem->base_dev = config->base_dev; rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom); if (rval) { dev_err(&nvmem->dev, "Failed to create eeprom binary file %d\n", rval); return rval; } nvmem->flags |= FLAG_COMPAT; return 0;} static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, const struct nvmem_config *config){ if (config->compat) device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);} #else /* CONFIG_NVMEM_SYSFS */ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, const struct nvmem_config *config){ return -ENOSYS;}static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, const struct nvmem_config *config){} #endif /* CONFIG_NVMEM_SYSFS */ static void nvmem_release(struct device *dev){ struct nvmem_device *nvmem = to_nvmem_device(dev); ida_free(&nvmem_ida, nvmem->id); gpiod_put(nvmem->wp_gpio); kfree(nvmem);} static const struct device_type nvmem_provider_type = { .release = nvmem_release,}; static struct bus_type nvmem_bus_type = { .name = "nvmem",}; static void nvmem_cell_drop(struct nvmem_cell *cell){ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell); mutex_lock(&nvmem_mutex); list_del(&cell->node); mutex_unlock(&nvmem_mutex); of_node_put(cell->np); kfree_const(cell->name); kfree(cell);} static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem){ struct nvmem_cell *cell, *p; list_for_each_entry_safe(cell, p, &nvmem->cells, node) nvmem_cell_drop(cell);} static void nvmem_cell_add(struct nvmem_cell *cell){ mutex_lock(&nvmem_mutex); list_add_tail(&cell->node, &cell->nvmem->cells); mutex_unlock(&nvmem_mutex); blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_ADD, cell);} static int nvmem_cell_info_to_nvmem_cell_nodup(struct nvmem_device *nvmem, const struct nvmem_cell_info *info, struct nvmem_cell *cell){ cell->nvmem = nvmem; cell->offset = info->offset; cell->bytes = info->bytes; cell->name = info->name; cell->bit_offset = info->bit_offset; cell->nbits = info->nbits; if (cell->nbits) cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, BITS_PER_BYTE); if (!IS_ALIGNED(cell->offset, nvmem->stride)) { dev_err(&nvmem->dev, "cell %s unaligned to nvmem stride %d\n", cell->name ?: "<unknown>", nvmem->stride); return -EINVAL; } return 0;} static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem, const struct nvmem_cell_info *info, struct nvmem_cell *cell){ int err; err = nvmem_cell_info_to_nvmem_cell_nodup(nvmem, info, cell); if (err) return err; cell->name = kstrdup_const(info->name, GFP_KERNEL); if (!cell->name) return -ENOMEM; return 0;} /** * nvmem_add_cells() - Add cell information to an nvmem device * * @nvmem: nvmem device to add cells to. * @info: nvmem cell info to add to the device * @ncells: number of cells in info * * Return: 0 or negative error code on failure. */static int nvmem_add_cells(struct nvmem_device *nvmem,Lines 1–500 of 1910. Load more
// SPDX-License-Identifier: GPL-2.0 /* * nvmem framework core. * * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> */ #include <linux/device.h> #include <linux/export.h> #include <linux/fs.h> #include <linux/idr.h> #include <linux/init.h> #include <linux/kref.h> #include <linux/module.h> #include <linux/nvmem-consumer.h> #include <linux/nvmem-provider.h> #include <linux/gpio/consumer.h> #include <linux/of.h> #include <linux/slab.h> struct nvmem_device { struct module *owner; struct device dev; int stride; int word_size; int id; struct kref refcnt; size_t size; bool read_only; bool root_only; int flags; enum nvmem_type type; struct bin_attribute eeprom; struct device *base_dev; struct list_head cells; const struct nvmem_keepout *keepout; unsigned int nkeepout; nvmem_reg_read_t reg_read; nvmem_reg_write_t reg_write; struct gpio_desc *wp_gpio; void *priv; }; #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) #define FLAG_COMPAT BIT(0) struct nvmem_cell { const char *name; int offset; int bytes; int bit_offset; int nbits; struct device_node *np; struct nvmem_device *nvmem; struct list_head node; }; static DEFINE_MUTEX(nvmem_mutex); static DEFINE_IDA(nvmem_ida); static DEFINE_MUTEX(nvmem_cell_mutex); static LIST_HEAD(nvmem_cell_tables); static DEFINE_MUTEX(nvmem_lookup_mutex); static LIST_HEAD(nvmem_lookup_list); static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { if (nvmem->reg_read) return nvmem->reg_read(nvmem->priv, offset, val, bytes); return -EINVAL; } static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { int ret; if (nvmem->reg_write) { gpiod_set_value_cansleep(nvmem->wp_gpio, 0); ret = nvmem->reg_write(nvmem->priv, offset, val, bytes); gpiod_set_value_cansleep(nvmem->wp_gpio, 1); return ret; } return -EINVAL; } static int nvmem_access_with_keepouts(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes, int write) { unsigned int end = offset + bytes; unsigned int kend, ksize; const struct nvmem_keepout *keepout = nvmem->keepout; const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout; int rc; /* * Skip all keepouts before the range being accessed. * Keepouts are sorted. */ while ((keepout < keepoutend) && (keepout->end <= offset)) keepout++; while ((offset < end) && (keepout < keepoutend)) { /* Access the valid portion before the keepout. */ if (offset < keepout->start) { kend = min(end, keepout->start); ksize = kend - offset; if (write) rc = __nvmem_reg_write(nvmem, offset, val, ksize); else rc = __nvmem_reg_read(nvmem, offset, val, ksize); if (rc) return rc; offset += ksize; val += ksize; } /* * Now we're aligned to the start of this keepout zone. Go * through it. */ kend = min(end, keepout->end); ksize = kend - offset; if (!write) memset(val, keepout->value, ksize); val += ksize; offset += ksize; keepout++; } /* * If we ran out of keepouts but there's still stuff to do, send it * down directly */ if (offset < end) { ksize = end - offset; if (write) return __nvmem_reg_write(nvmem, offset, val, ksize); else return __nvmem_reg_read(nvmem, offset, val, ksize); } return 0; } static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { if (!nvmem->nkeepout) return __nvmem_reg_read(nvmem, offset, val, bytes); return nvmem_access_with_keepouts(nvmem, offset, val, bytes, false); } static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { if (!nvmem->nkeepout) return __nvmem_reg_write(nvmem, offset, val, bytes); return nvmem_access_with_keepouts(nvmem, offset, val, bytes, true); } #ifdef CONFIG_NVMEM_SYSFS static const char * const nvmem_type_str[] = { [NVMEM_TYPE_UNKNOWN] = "Unknown", [NVMEM_TYPE_EEPROM] = "EEPROM", [NVMEM_TYPE_OTP] = "OTP", [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed", }; #ifdef CONFIG_DEBUG_LOCK_ALLOC static struct lock_class_key eeprom_lock_key; #endif static ssize_t type_show(struct device *dev, struct device_attribute *attr, char *buf) { struct nvmem_device *nvmem = to_nvmem_device(dev); return sprintf(buf, "%s\n", nvmem_type_str[nvmem->type]); } static DEVICE_ATTR_RO(type); static struct attribute *nvmem_attrs[] = { &dev_attr_type.attr, NULL, }; static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct device *dev; struct nvmem_device *nvmem; int rc; if (attr->private) dev = attr->private; else dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); /* Stop the user from reading */ if (pos >= nvmem->size) return 0; if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; if (pos + count > nvmem->size) count = nvmem->size - pos; count = round_down(count, nvmem->word_size); if (!nvmem->reg_read) return -EPERM; rc = nvmem_reg_read(nvmem, pos, buf, count); if (rc) return rc; return count; } static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct device *dev; struct nvmem_device *nvmem; int rc; if (attr->private) dev = attr->private; else dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); /* Stop the user from writing */ if (pos >= nvmem->size) return -EFBIG; if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; if (pos + count > nvmem->size) count = nvmem->size - pos; count = round_down(count, nvmem->word_size); if (!nvmem->reg_write) return -EPERM; rc = nvmem_reg_write(nvmem, pos, buf, count); if (rc) return rc; return count; } static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem) { umode_t mode = 0400; if (!nvmem->root_only) mode |= 0044; if (!nvmem->read_only) mode |= 0200; if (!nvmem->reg_write) mode &= ~0200; if (!nvmem->reg_read) mode &= ~0444; return mode; } static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, struct bin_attribute *attr, int i) { struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); return nvmem_bin_attr_get_umode(nvmem); } /* default read/write permissions */ static struct bin_attribute bin_attr_rw_nvmem = { .attr = { .name = "nvmem", .mode = 0644, }, .read = bin_attr_nvmem_read, .write = bin_attr_nvmem_write, }; static struct bin_attribute *nvmem_bin_attributes[] = { &bin_attr_rw_nvmem, NULL, }; static const struct attribute_group nvmem_bin_group = { .bin_attrs = nvmem_bin_attributes, .attrs = nvmem_attrs, .is_bin_visible = nvmem_bin_attr_is_visible, }; static const struct attribute_group *nvmem_dev_groups[] = { &nvmem_bin_group, NULL, }; static struct bin_attribute bin_attr_nvmem_eeprom_compat = { .attr = { .name = "eeprom", }, .read = bin_attr_nvmem_read, .write = bin_attr_nvmem_write, }; /* * nvmem_setup_compat() - Create an additional binary entry in * drivers sys directory, to be backwards compatible with the older * drivers/misc/eeprom drivers. */ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, const struct nvmem_config *config) { int rval; if (!config->compat) return 0; if (!config->base_dev) return -EINVAL; nvmem->eeprom = bin_attr_nvmem_eeprom_compat; nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem); nvmem->eeprom.size = nvmem->size; #ifdef CONFIG_DEBUG_LOCK_ALLOC nvmem->eeprom.attr.key = &eeprom_lock_key; #endif nvmem->eeprom.private = &nvmem->dev; nvmem->base_dev = config->base_dev; rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom); if (rval) { dev_err(&nvmem->dev, "Failed to create eeprom binary file %d\n", rval); return rval; } nvmem->flags |= FLAG_COMPAT; return 0; } static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, const struct nvmem_config *config) { if (config->compat) device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); } #else /* CONFIG_NVMEM_SYSFS */ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, const struct nvmem_config *config) { return -ENOSYS; } static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, const struct nvmem_config *config) { } #endif /* CONFIG_NVMEM_SYSFS */ static void nvmem_release(struct device *dev) { struct nvmem_device *nvmem = to_nvmem_device(dev); ida_free(&nvmem_ida, nvmem->id); gpiod_put(nvmem->wp_gpio); kfree(nvmem); } static const struct device_type nvmem_provider_type = { .release = nvmem_release, }; static struct bus_type nvmem_bus_type = { .name = "nvmem", }; static void nvmem_cell_drop(struct nvmem_cell *cell) { blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell); mutex_lock(&nvmem_mutex); list_del(&cell->node); mutex_unlock(&nvmem_mutex); of_node_put(cell->np); kfree_const(cell->name); kfree(cell); } static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem) { struct nvmem_cell *cell, *p; list_for_each_entry_safe(cell, p, &nvmem->cells, node) nvmem_cell_drop(cell); } static void nvmem_cell_add(struct nvmem_cell *cell) { mutex_lock(&nvmem_mutex); list_add_tail(&cell->node, &cell->nvmem->cells); mutex_unlock(&nvmem_mutex); blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_ADD, cell); } static int nvmem_cell_info_to_nvmem_cell_nodup(struct nvmem_device *nvmem, const struct nvmem_cell_info *info, struct nvmem_cell *cell) { cell->nvmem = nvmem; cell->offset = info->offset; cell->bytes = info->bytes; cell->name = info->name; cell->bit_offset = info->bit_offset; cell->nbits = info->nbits; if (cell->nbits) cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, BITS_PER_BYTE); if (!IS_ALIGNED(cell->offset, nvmem->stride)) { dev_err(&nvmem->dev, "cell %s unaligned to nvmem stride %d\n", cell->name ?: "<unknown>", nvmem->stride); return -EINVAL; } return 0; } static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem, const struct nvmem_cell_info *info, struct nvmem_cell *cell) { int err; err = nvmem_cell_info_to_nvmem_cell_nodup(nvmem, info, cell); if (err) return err; cell->name = kstrdup_const(info->name, GFP_KERNEL); if (!cell->name) return -ENOMEM; return 0; } /** * nvmem_add_cells() - Add cell information to an nvmem device * * @nvmem: nvmem device to add cells to. * @info: nvmem cell info to add to the device * @ncells: number of cells in info * * Return: 0 or negative error code on failure. */ static int nvmem_add_cells(struct nvmem_device *nvmem,