1245 lines · 27.4 KB · C
RawDownload
/* * nvmem framework core. * * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. */ #include <linux/device.h>#include <linux/export.h>#include <linux/fs.h>#include <linux/idr.h>#include <linux/init.h>#include <linux/module.h>#include <linux/nvmem-consumer.h>#include <linux/nvmem-provider.h>#include <linux/of.h>#include <linux/slab.h> struct nvmem_device {	const char		*name;	struct module		*owner;	struct device		dev;	int			stride;	int			word_size;	int			ncells;	int			id;	int			users;	size_t			size;	bool			read_only;	int			flags;	struct bin_attribute	eeprom;	struct device		*base_dev;	nvmem_reg_read_t	reg_read;	nvmem_reg_write_t	reg_write;	void *priv;}; #define FLAG_COMPAT		BIT(0) struct nvmem_cell {	const char		*name;	int			offset;	int			bytes;	int			bit_offset;	int			nbits;	struct nvmem_device	*nvmem;	struct list_head	node;}; static DEFINE_MUTEX(nvmem_mutex);static DEFINE_IDA(nvmem_ida); static LIST_HEAD(nvmem_cells);static DEFINE_MUTEX(nvmem_cells_mutex); #ifdef CONFIG_DEBUG_LOCK_ALLOCstatic struct lock_class_key eeprom_lock_key;#endif #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev)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){	if (nvmem->reg_write)		return nvmem->reg_write(nvmem->priv, offset, val, bytes); 	return -EINVAL;} 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); 	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 = container_of(kobj, struct device, kobj);	nvmem = to_nvmem_device(dev); 	/* Stop the user from writing */	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); 	rc = nvmem_reg_write(nvmem, pos, buf, count); 	if (rc)		return rc; 	return count;} /* default read/write permissions */static struct bin_attribute bin_attr_rw_nvmem = {	.attr	= {		.name	= "nvmem",		.mode	= S_IWUSR | S_IRUGO,	},	.read	= bin_attr_nvmem_read,	.write	= bin_attr_nvmem_write,}; static struct bin_attribute *nvmem_bin_rw_attributes[] = {	&bin_attr_rw_nvmem,	NULL,}; static const struct attribute_group nvmem_bin_rw_group = {	.bin_attrs	= nvmem_bin_rw_attributes,}; static const struct attribute_group *nvmem_rw_dev_groups[] = {	&nvmem_bin_rw_group,	NULL,}; /* read only permission */static struct bin_attribute bin_attr_ro_nvmem = {	.attr	= {		.name	= "nvmem",		.mode	= S_IRUGO,	},	.read	= bin_attr_nvmem_read,}; static struct bin_attribute *nvmem_bin_ro_attributes[] = {	&bin_attr_ro_nvmem,	NULL,}; static const struct attribute_group nvmem_bin_ro_group = {	.bin_attrs	= nvmem_bin_ro_attributes,}; static const struct attribute_group *nvmem_ro_dev_groups[] = {	&nvmem_bin_ro_group,	NULL,}; /* default read/write permissions, root only */static struct bin_attribute bin_attr_rw_root_nvmem = {	.attr	= {		.name	= "nvmem",		.mode	= S_IWUSR | S_IRUSR,	},	.read	= bin_attr_nvmem_read,	.write	= bin_attr_nvmem_write,}; static struct bin_attribute *nvmem_bin_rw_root_attributes[] = {	&bin_attr_rw_root_nvmem,	NULL,}; static const struct attribute_group nvmem_bin_rw_root_group = {	.bin_attrs	= nvmem_bin_rw_root_attributes,}; static const struct attribute_group *nvmem_rw_root_dev_groups[] = {	&nvmem_bin_rw_root_group,	NULL,}; /* read only permission, root only */static struct bin_attribute bin_attr_ro_root_nvmem = {	.attr	= {		.name	= "nvmem",		.mode	= S_IRUSR,	},	.read	= bin_attr_nvmem_read,}; static struct bin_attribute *nvmem_bin_ro_root_attributes[] = {	&bin_attr_ro_root_nvmem,	NULL,}; static const struct attribute_group nvmem_bin_ro_root_group = {	.bin_attrs	= nvmem_bin_ro_root_attributes,}; static const struct attribute_group *nvmem_ro_root_dev_groups[] = {	&nvmem_bin_ro_root_group,	NULL,}; static void nvmem_release(struct device *dev){	struct nvmem_device *nvmem = to_nvmem_device(dev); 	ida_simple_remove(&nvmem_ida, nvmem->id);	kfree(nvmem);} static const struct device_type nvmem_provider_type = {	.release	= nvmem_release,}; static struct bus_type nvmem_bus_type = {	.name		= "nvmem",}; static int of_nvmem_match(struct device *dev, void *nvmem_np){	return dev->of_node == nvmem_np;} static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np){	struct device *d; 	if (!nvmem_np)		return NULL; 	d = bus_find_device(&nvmem_bus_type, NULL, nvmem_np, of_nvmem_match); 	if (!d)		return NULL; 	return to_nvmem_device(d);} static struct nvmem_cell *nvmem_find_cell(const char *cell_id){	struct nvmem_cell *p; 	mutex_lock(&nvmem_cells_mutex); 	list_for_each_entry(p, &nvmem_cells, node)		if (p && !strcmp(p->name, cell_id)) {			mutex_unlock(&nvmem_cells_mutex);			return p;		} 	mutex_unlock(&nvmem_cells_mutex); 	return NULL;} static void nvmem_cell_drop(struct nvmem_cell *cell){	mutex_lock(&nvmem_cells_mutex);	list_del(&cell->node);	mutex_unlock(&nvmem_cells_mutex);	kfree(cell);} static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem){	struct nvmem_cell *cell;	struct list_head *p, *n; 	list_for_each_safe(p, n, &nvmem_cells) {		cell = list_entry(p, struct nvmem_cell, node);		if (cell->nvmem == nvmem)			nvmem_cell_drop(cell);	}} static void nvmem_cell_add(struct nvmem_cell *cell){	mutex_lock(&nvmem_cells_mutex);	list_add_tail(&cell->node, &nvmem_cells);	mutex_unlock(&nvmem_cells_mutex);} static int nvmem_cell_info_to_nvmem_cell(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, nvmem->stride);		return -EINVAL;	} 	return 0;} static int nvmem_add_cells(struct nvmem_device *nvmem,			   const struct nvmem_config *cfg){	struct nvmem_cell **cells;	const struct nvmem_cell_info *info = cfg->cells;	int i, rval; 	cells = kcalloc(cfg->ncells, sizeof(*cells), GFP_KERNEL);	if (!cells)		return -ENOMEM; 	for (i = 0; i < cfg->ncells; i++) {		cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);		if (!cells[i]) {			rval = -ENOMEM;			goto err;		} 		rval = nvmem_cell_info_to_nvmem_cell(nvmem, &info[i], cells[i]);		if (rval) {			kfree(cells[i]);			goto err;		} 		nvmem_cell_add(cells[i]);	} 	nvmem->ncells = cfg->ncells;	/* remove tmp array */	kfree(cells); 	return 0;err:	while (i--)		nvmem_cell_drop(cells[i]); 	kfree(cells); 	return rval;} /* * 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_setup_compat(struct nvmem_device *nvmem,			      const struct nvmem_config *config){	int rval; 	if (!config->base_dev)		return -EINVAL; 	if (nvmem->read_only)		nvmem->eeprom = bin_attr_ro_root_nvmem;	else		nvmem->eeprom = bin_attr_rw_root_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;} /** * nvmem_register() - Register a nvmem device for given nvmem_config. * Also creates an binary entry in /sys/bus/nvmem/devices/dev-name/nvmem * * @config: nvmem device configuration with which nvmem device is created. * * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device * on success. */ struct nvmem_device *nvmem_register(const struct nvmem_config *config){	struct nvmem_device *nvmem;	struct device_node *np;	int rval; 	if (!config->dev)		return ERR_PTR(-EINVAL); 	nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL);	if (!nvmem)		return ERR_PTR(-ENOMEM); 	rval  = ida_simple_get(&nvmem_ida, 0, 0, GFP_KERNEL);	if (rval < 0) {		kfree(nvmem);		return ERR_PTR(rval);	} 	nvmem->id = rval;	nvmem->owner = config->owner;	nvmem->stride = config->stride;	nvmem->word_size = config->word_size;	nvmem->size = config->size;	nvmem->dev.type = &nvmem_provider_type;	nvmem->dev.bus = &nvmem_bus_type;	nvmem->dev.parent = config->dev;	nvmem->priv = config->priv;	nvmem->reg_read = config->reg_read;	nvmem->reg_write = config->reg_write;	np = config->dev->of_node;	nvmem->dev.of_node = np;	dev_set_name(&nvmem->dev, "%s%d",		     config->name ? : "nvmem",		     config->name ? config->id : nvmem->id); 	nvmem->read_only = of_property_read_bool(np, "read-only") |			   config->read_only; 	if (config->root_only)		nvmem->dev.groups = nvmem->read_only ?			nvmem_ro_root_dev_groups :			nvmem_rw_root_dev_groups;	else		nvmem->dev.groups = nvmem->read_only ?			nvmem_ro_dev_groups :			nvmem_rw_dev_groups; 	device_initialize(&nvmem->dev); 	dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); 	rval = device_add(&nvmem->dev);	if (rval)		goto err_put_device; 	if (config->compat) {

Lines 1–500 of 1245.