1910 lines · 43.2 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/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.