2179 lines · 49.7 KB · C
RawDownload
// 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/cleanup.h>#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/mutex.h>#include <linux/nvmem-consumer.h>#include <linux/nvmem-provider.h>#include <linux/gpio/consumer.h>#include <linux/of.h>#include <linux/slab.h> #include "internals.h" #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) #define FLAG_COMPAT		BIT(0)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;}; struct nvmem_cell {	struct nvmem_cell_entry *entry;	const char		*id;	int			index;}; static DEFINE_MUTEX(nvmem_mutex);static DEFINE_IDA(nvmem_ida); 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){	struct nvmem_operations *ops = nvmem->ops; 	if (!ops->reg_read)		return -EOPNOTSUPP; 	return ops->reg_read(nvmem->priv, offset, val, bytes);} static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,			     void *val, size_t bytes){	struct nvmem_operations *ops = nvmem->ops;	int ret, wr_ok; 	if (!ops->reg_write)		return -EOPNOTSUPP; 	ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 0);	if (ret)		return ret; 	wr_ok = ops->reg_write(nvmem->priv, offset, val, bytes); 	ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 1);	if (ret)		return ret; 	return wr_ok;} 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",	[NVMEM_TYPE_FRAM] = "FRAM",}; #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 sysfs_emit(buf, "%s\n", nvmem_type_str[nvmem->type]);} static DEVICE_ATTR_RO(type); static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr,			     char *buf){	struct nvmem_device *nvmem = to_nvmem_device(dev); 	return sysfs_emit(buf, "%d\n", nvmem->read_only);} static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr,			      const char *buf, size_t count){	struct nvmem_device *nvmem = to_nvmem_device(dev);	int ret = kstrtobool(buf, &nvmem->read_only); 	if (ret < 0)		return ret; 	return count;} static DEVICE_ATTR_RW(force_ro); static struct attribute *nvmem_attrs[] = {	&dev_attr_force_ro.attr,	&dev_attr_type.attr,	NULL,}; static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj,				   const 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); 	if (!IS_ALIGNED(pos, nvmem->stride))		return -EINVAL; 	if (count < nvmem->word_size)		return -EINVAL; 	count = round_down(count, nvmem->word_size); 	rc = nvmem_reg_read(nvmem, pos, buf, count);	if (rc) {		if (rc == -EOPNOTSUPP)			return -EPERM;		return rc;	} 	return count;} static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj,				    const 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); 	if (!IS_ALIGNED(pos, nvmem->stride))		return -EINVAL; 	if (count < nvmem->word_size)		return -EINVAL; 	count = round_down(count, nvmem->word_size); 	if (nvmem->read_only)		return -EPERM; 	rc = nvmem_reg_write(nvmem, pos, buf, count);	if (rc) {		if (rc == -EOPNOTSUPP)			return -EPERM;		return rc;	} 	return count;} static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem){	struct nvmem_operations *ops = nvmem->ops; 	umode_t mode = 0400; 	if (!nvmem->root_only)		mode |= 0044; 	if (!nvmem->read_only)		mode |= 0200; 	if (!ops->reg_write)		mode &= ~0200; 	if (!ops->reg_read)		mode &= ~0444; 	return mode;} static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj,					 const 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);} static size_t nvmem_bin_attr_size(struct kobject *kobj,				  const struct bin_attribute *attr,				  int i){	struct device *dev = kobj_to_dev(kobj);	struct nvmem_device *nvmem = to_nvmem_device(dev); 	return nvmem->size;} static umode_t nvmem_attr_is_visible(struct kobject *kobj,				     struct attribute *attr, int i){	struct device *dev = kobj_to_dev(kobj);	struct nvmem_device *nvmem = to_nvmem_device(dev);	struct nvmem_operations *ops = nvmem->ops; 	/*	 * If the device has no .reg_write operation, do not allow	 * configuration as read-write.	 * If the device is set as read-only by configuration, it	 * can be forced into read-write mode using the 'force_ro'	 * attribute.	 */	if (attr == &dev_attr_force_ro.attr && !ops->reg_write)		return 0;	/* Attribute not visible */ 	return attr->mode;} static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,					    const char *id, int index); static ssize_t nvmem_cell_attr_read(struct file *filp, struct kobject *kobj,				    const struct bin_attribute *attr, char *buf,				    loff_t pos, size_t count){	struct nvmem_cell_entry *entry;	struct nvmem_cell *cell = NULL;	size_t cell_sz, read_len;	void *content; 	entry = attr->private;	cell = nvmem_create_cell(entry, entry->name, 0);	if (IS_ERR(cell))		return PTR_ERR(cell); 	if (!cell)		return -EINVAL; 	content = nvmem_cell_read(cell, &cell_sz);	if (IS_ERR(content)) {		read_len = PTR_ERR(content);		goto destroy_cell;	} 	read_len = min_t(unsigned int, cell_sz - pos, count);	memcpy(buf, content + pos, read_len);	kfree(content); destroy_cell:	kfree_const(cell->id);	kfree(cell); 	return read_len;} /* default read/write permissions */static const struct bin_attribute bin_attr_rw_nvmem = {	.attr	= {		.name	= "nvmem",		.mode	= 0644,	},	.read	= bin_attr_nvmem_read,	.write	= bin_attr_nvmem_write,}; static const struct bin_attribute *const 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,	.bin_size	= nvmem_bin_attr_size,	.is_visible	= nvmem_attr_is_visible,}; static const struct attribute_group *nvmem_dev_groups[] = {	&nvmem_bin_group,	NULL,}; static const 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;	if (config->type == NVMEM_TYPE_FRAM)		nvmem->eeprom.attr.name = "fram";	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){	if (nvmem->flags & FLAG_COMPAT)		device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);} static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem){	struct attribute_group group = {		.name	= "cells",	};	struct nvmem_cell_entry *entry;	const struct bin_attribute **pattrs;	struct bin_attribute *attrs;	unsigned int ncells = 0, i = 0;	int ret; 	guard(mutex)(&nvmem_mutex); 	if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated)		return 0; 	/* Allocate an array of attributes with a sentinel */	ncells = list_count_nodes(&nvmem->cells);	pattrs = devm_kcalloc(&nvmem->dev, ncells + 1,			      sizeof(struct bin_attribute *), GFP_KERNEL);	if (!pattrs)		return -ENOMEM; 	attrs = devm_kcalloc(&nvmem->dev, ncells, sizeof(struct bin_attribute), GFP_KERNEL);	if (!attrs)		return -ENOMEM; 	/* Initialize each attribute to take the name and size of the cell */	list_for_each_entry(entry, &nvmem->cells, node) {		sysfs_bin_attr_init(&attrs[i]);		attrs[i].attr.name = devm_kasprintf(&nvmem->dev, GFP_KERNEL,						    "%s@%x,%x", entry->name,

Lines 1–500 of 2179.