diff options
Diffstat (limited to 'drivers/nvmem/core.c')
-rw-r--r-- | drivers/nvmem/core.c | 365 |
1 files changed, 320 insertions, 45 deletions
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index ef326f243f36..05c6ae4b0b97 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -18,7 +18,31 @@ #include <linux/gpio/consumer.h> #include <linux/of.h> #include <linux/slab.h> -#include "nvmem.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; + 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; @@ -42,6 +66,250 @@ static LIST_HEAD(nvmem_lookup_list); static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); +#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 = container_of(kobj, struct device, kobj); + nvmem = to_nvmem_device(dev); + + /* Stop the user from reading */ + if (pos >= nvmem->size) + return 0; + + 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->priv, 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 = container_of(kobj, struct device, kobj); + nvmem = to_nvmem_device(dev); + + /* Stop the user from writing */ + if (pos >= nvmem->size) + return -EFBIG; + + 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->priv, pos, buf, count); + + if (rc) + return rc; + + return count; +} + +static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, + struct bin_attribute *attr, int i) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + 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; +} + +/* 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, +}; + +/* read only permission */ +static struct bin_attribute bin_attr_ro_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0444, + }, + .read = bin_attr_nvmem_read, +}; + +/* default read/write permissions, root only */ +static struct bin_attribute bin_attr_rw_root_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0600, + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +/* read only permission, root only */ +static struct bin_attribute bin_attr_ro_root_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0400, + }, + .read = bin_attr_nvmem_read, +}; + +/* + * 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; + + if (nvmem->read_only) { + if (config->root_only) + nvmem->eeprom = bin_attr_ro_root_nvmem; + else + nvmem->eeprom = bin_attr_ro_nvmem; + } else { + if (config->root_only) + nvmem->eeprom = bin_attr_rw_root_nvmem; + else + nvmem->eeprom = bin_attr_rw_nvmem; + } + nvmem->eeprom.attr.name = "eeprom"; + 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 int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) @@ -72,6 +340,7 @@ static void nvmem_release(struct device *dev) struct nvmem_device *nvmem = to_nvmem_device(dev); ida_simple_remove(&nvmem_ida, nvmem->id); + gpiod_put(nvmem->wp_gpio); kfree(nvmem); } @@ -338,6 +607,9 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) if (!config->dev) return ERR_PTR(-EINVAL); + if (!config->reg_read && !config->reg_write) + return ERR_PTR(-EINVAL); + nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL); if (!nvmem) return ERR_PTR(-ENOMEM); @@ -347,14 +619,18 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) kfree(nvmem); return ERR_PTR(rval); } + if (config->wp_gpio) nvmem->wp_gpio = config->wp_gpio; else nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp", GPIOD_OUT_HIGH); - if (IS_ERR(nvmem->wp_gpio)) - return ERR_CAST(nvmem->wp_gpio); - + if (IS_ERR(nvmem->wp_gpio)) { + ida_simple_remove(&nvmem_ida, nvmem->id); + rval = PTR_ERR(nvmem->wp_gpio); + kfree(nvmem); + return ERR_PTR(rval); + } kref_init(&nvmem->refcnt); INIT_LIST_HEAD(&nvmem->cells); @@ -369,6 +645,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->dev.type = &nvmem_provider_type; nvmem->dev.bus = &nvmem_bus_type; nvmem->dev.parent = config->dev; + nvmem->root_only = config->root_only; nvmem->priv = config->priv; nvmem->type = config->type; nvmem->reg_read = config->reg_read; @@ -387,13 +664,13 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->read_only = device_property_present(config->dev, "read-only") || config->read_only || !nvmem->reg_write; - nvmem->dev.groups = nvmem_sysfs_get_groups(nvmem, config); - - device_initialize(&nvmem->dev); +#ifdef CONFIG_NVMEM_SYSFS + nvmem->dev.groups = nvmem_dev_groups; +#endif dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); - rval = device_add(&nvmem->dev); + rval = device_register(&nvmem->dev); if (rval) goto err_put_device; @@ -447,8 +724,7 @@ static void nvmem_device_release(struct kref *kref) device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); nvmem_device_remove_all_cells(nvmem); - device_del(&nvmem->dev); - put_device(&nvmem->dev); + device_unregister(&nvmem->dev); } /** @@ -1088,16 +1364,8 @@ int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len) } EXPORT_SYMBOL_GPL(nvmem_cell_write); -/** - * nvmem_cell_read_u16() - Read a cell value as an u16 - * - * @dev: Device that requests the nvmem cell. - * @cell_id: Name of nvmem cell to read. - * @val: pointer to output value. - * - * Return: 0 on success or negative errno. - */ -int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val) +static int nvmem_cell_read_common(struct device *dev, const char *cell_id, + void *val, size_t count) { struct nvmem_cell *cell; void *buf; @@ -1112,17 +1380,31 @@ int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val) nvmem_cell_put(cell); return PTR_ERR(buf); } - if (len != sizeof(*val)) { + if (len != count) { kfree(buf); nvmem_cell_put(cell); return -EINVAL; } - memcpy(val, buf, sizeof(*val)); + memcpy(val, buf, count); kfree(buf); nvmem_cell_put(cell); return 0; } + +/** + * nvmem_cell_read_u16() - Read a cell value as an u16 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} EXPORT_SYMBOL_GPL(nvmem_cell_read_u16); /** @@ -1136,33 +1418,26 @@ EXPORT_SYMBOL_GPL(nvmem_cell_read_u16); */ int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val) { - struct nvmem_cell *cell; - void *buf; - size_t len; - - cell = nvmem_cell_get(dev, cell_id); - if (IS_ERR(cell)) - return PTR_ERR(cell); - - buf = nvmem_cell_read(cell, &len); - if (IS_ERR(buf)) { - nvmem_cell_put(cell); - return PTR_ERR(buf); - } - if (len != sizeof(*val)) { - kfree(buf); - nvmem_cell_put(cell); - return -EINVAL; - } - memcpy(val, buf, sizeof(*val)); - - kfree(buf); - nvmem_cell_put(cell); - return 0; + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); } EXPORT_SYMBOL_GPL(nvmem_cell_read_u32); /** + * nvmem_cell_read_u64() - Read a cell value as an u64 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u64(struct device *dev, const char *cell_id, u64 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_u64); + +/** * nvmem_device_cell_read() - Read a given nvmem device and cell * * @nvmem: nvmem device to read from. |