summaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/Kconfig21
-rw-r--r--drivers/base/Makefile1
-rw-r--r--drivers/base/attribute_container.c14
-rw-r--r--drivers/base/core.c9
-rw-r--r--drivers/base/dd.c2
-rw-r--r--drivers/base/devcoredump.c265
-rw-r--r--drivers/base/devres.c15
-rw-r--r--drivers/base/firmware_class.c3
-rw-r--r--drivers/base/node.c1
-rw-r--r--drivers/base/regmap/Kconfig3
-rw-r--r--drivers/base/regmap/internal.h9
-rw-r--r--drivers/base/regmap/regcache-rbtree.c9
-rw-r--r--drivers/base/regmap/regcache.c15
-rw-r--r--drivers/base/regmap/regmap-debugfs.c20
-rw-r--r--drivers/base/regmap/regmap-i2c.c2
-rw-r--r--drivers/base/regmap/regmap-spi.c2
-rw-r--r--drivers/base/regmap/regmap.c88
17 files changed, 435 insertions, 44 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 4e7f0ff83ae..134f763d90f 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -165,6 +165,27 @@ config FW_LOADER_USER_HELPER_FALLBACK
If you are unsure about this, say N here.
+config WANT_DEV_COREDUMP
+ bool
+ help
+ Drivers should "select" this option if they desire to use the
+ device coredump mechanism.
+
+config DISABLE_DEV_COREDUMP
+ bool "Disable device coredump" if EXPERT
+ help
+ Disable the device coredump mechanism despite drivers wanting to
+ use it; this allows for more sensitive systems or systems that
+ don't want to ever access the information to not have the code,
+ nor keep any data.
+
+ If unsure, say N.
+
+config DEV_COREDUMP
+ bool
+ default y if WANT_DEV_COREDUMP
+ depends on !DISABLE_DEV_COREDUMP
+
config DEBUG_DRIVER
bool "Driver Core verbose debug messages"
depends on DEBUG_KERNEL
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 4aab26ec029..6922cd6850a 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o
obj-$(CONFIG_REGMAP) += regmap/
obj-$(CONFIG_SOC_BUS) += soc.o
obj-$(CONFIG_PINCTRL) += pinctrl.o
+obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o
ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
diff --git a/drivers/base/attribute_container.c b/drivers/base/attribute_container.c
index b84ca8f13f9..3ead3af4be6 100644
--- a/drivers/base/attribute_container.c
+++ b/drivers/base/attribute_container.c
@@ -74,9 +74,9 @@ int
attribute_container_register(struct attribute_container *cont)
{
INIT_LIST_HEAD(&cont->node);
- klist_init(&cont->containers,internal_container_klist_get,
+ klist_init(&cont->containers, internal_container_klist_get,
internal_container_klist_put);
-
+
mutex_lock(&attribute_container_mutex);
list_add_tail(&cont->node, &attribute_container_list);
mutex_unlock(&attribute_container_mutex);
@@ -104,14 +104,14 @@ attribute_container_unregister(struct attribute_container *cont)
spin_unlock(&cont->containers.k_lock);
mutex_unlock(&attribute_container_mutex);
return retval;
-
+
}
EXPORT_SYMBOL_GPL(attribute_container_unregister);
/* private function used as class release */
static void attribute_container_release(struct device *classdev)
{
- struct internal_container *ic
+ struct internal_container *ic
= container_of(classdev, struct internal_container, classdev);
struct device *dev = classdev->parent;
@@ -184,8 +184,8 @@ attribute_container_add_device(struct device *dev,
struct klist_node *n = klist_next(iter); \
n ? container_of(n, typeof(*pos), member) : \
({ klist_iter_exit(iter) ; NULL; }); \
- }) ) != NULL; )
-
+ })) != NULL;)
+
/**
* attribute_container_remove_device - make device eligible for removal.
@@ -247,7 +247,7 @@ attribute_container_remove_device(struct device *dev,
* container, then use attribute_container_trigger() instead.
*/
void
-attribute_container_device_trigger(struct device *dev,
+attribute_container_device_trigger(struct device *dev,
int (*fn)(struct attribute_container *,
struct device *,
struct device *))
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 20da3ad1696..28b808c73e8 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2007,6 +2007,8 @@ create_syslog_header(const struct device *dev, char *hdr, size_t hdrlen)
return 0;
pos += snprintf(hdr + pos, hdrlen - pos, "SUBSYSTEM=%s", subsys);
+ if (pos >= hdrlen)
+ goto overflow;
/*
* Add device identifier DEVICE=:
@@ -2038,7 +2040,14 @@ create_syslog_header(const struct device *dev, char *hdr, size_t hdrlen)
"DEVICE=+%s:%s", subsys, dev_name(dev));
}
+ if (pos >= hdrlen)
+ goto overflow;
+
return pos;
+
+overflow:
+ dev_WARN(dev, "device/subsystem name too long");
+ return 0;
}
int dev_vprintk_emit(int level, const struct device *dev,
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index e4ffbcf2f51..cdc779cf79a 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -54,7 +54,7 @@ static LIST_HEAD(deferred_probe_active_list);
static struct workqueue_struct *deferred_wq;
static atomic_t deferred_trigger_count = ATOMIC_INIT(0);
-/**
+/*
* deferred_probe_work_func() - Retry probing devices in the active list.
*/
static void deferred_probe_work_func(struct work_struct *work)
diff --git a/drivers/base/devcoredump.c b/drivers/base/devcoredump.c
new file mode 100644
index 00000000000..96614b04544
--- /dev/null
+++ b/drivers/base/devcoredump.c
@@ -0,0 +1,265 @@
+/*
+ * This file is provided under the GPLv2 license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2014 Intel Mobile Communications GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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.
+ *
+ * The full GNU General Public License is included in this distribution
+ * in the file called COPYING.
+ *
+ * Contact Information:
+ * Intel Linux Wireless <ilw@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ * Author: Johannes Berg <johannes@sipsolutions.net>
+ */
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/devcoredump.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/workqueue.h>
+
+/* if data isn't read by userspace after 5 minutes then delete it */
+#define DEVCD_TIMEOUT (HZ * 60 * 5)
+
+struct devcd_entry {
+ struct device devcd_dev;
+ const void *data;
+ size_t datalen;
+ struct module *owner;
+ ssize_t (*read)(char *buffer, loff_t offset, size_t count,
+ const void *data, size_t datalen);
+ void (*free)(const void *data);
+ struct delayed_work del_wk;
+ struct device *failing_dev;
+};
+
+static struct devcd_entry *dev_to_devcd(struct device *dev)
+{
+ return container_of(dev, struct devcd_entry, devcd_dev);
+}
+
+static void devcd_dev_release(struct device *dev)
+{
+ struct devcd_entry *devcd = dev_to_devcd(dev);
+
+ devcd->free(devcd->data);
+ module_put(devcd->owner);
+
+ /*
+ * this seems racy, but I don't see a notifier or such on
+ * a struct device to know when it goes away?
+ */
+ if (devcd->failing_dev->kobj.sd)
+ sysfs_delete_link(&devcd->failing_dev->kobj, &dev->kobj,
+ "devcoredump");
+
+ put_device(devcd->failing_dev);
+ kfree(devcd);
+}
+
+static void devcd_del(struct work_struct *wk)
+{
+ struct devcd_entry *devcd;
+
+ devcd = container_of(wk, struct devcd_entry, del_wk.work);
+
+ device_del(&devcd->devcd_dev);
+ put_device(&devcd->devcd_dev);
+}
+
+static ssize_t devcd_data_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buffer, loff_t offset, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct devcd_entry *devcd = dev_to_devcd(dev);
+
+ return devcd->read(buffer, offset, count, devcd->data, devcd->datalen);
+}
+
+static ssize_t devcd_data_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buffer, loff_t offset, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct devcd_entry *devcd = dev_to_devcd(dev);
+
+ mod_delayed_work(system_wq, &devcd->del_wk, 0);
+
+ return count;
+}
+
+static struct bin_attribute devcd_attr_data = {
+ .attr = { .name = "data", .mode = S_IRUSR | S_IWUSR, },
+ .size = 0,
+ .read = devcd_data_read,
+ .write = devcd_data_write,
+};
+
+static struct bin_attribute *devcd_dev_bin_attrs[] = {
+ &devcd_attr_data, NULL,
+};
+
+static const struct attribute_group devcd_dev_group = {
+ .bin_attrs = devcd_dev_bin_attrs,
+};
+
+static const struct attribute_group *devcd_dev_groups[] = {
+ &devcd_dev_group, NULL,
+};
+
+static struct class devcd_class = {
+ .name = "devcoredump",
+ .owner = THIS_MODULE,
+ .dev_release = devcd_dev_release,
+ .dev_groups = devcd_dev_groups,
+};
+
+static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count,
+ const void *data, size_t datalen)
+{
+ if (offset > datalen)
+ return -EINVAL;
+
+ if (offset + count > datalen)
+ count = datalen - offset;
+
+ if (count)
+ memcpy(buffer, ((u8 *)data) + offset, count);
+
+ return count;
+}
+
+/**
+ * dev_coredumpv - create device coredump with vmalloc data
+ * @dev: the struct device for the crashed device
+ * @data: vmalloc data containing the device coredump
+ * @datalen: length of the data
+ * @gfp: allocation flags
+ *
+ * This function takes ownership of the vmalloc'ed data and will free
+ * it when it is no longer used. See dev_coredumpm() for more information.
+ */
+void dev_coredumpv(struct device *dev, const void *data, size_t datalen,
+ gfp_t gfp)
+{
+ dev_coredumpm(dev, NULL, data, datalen, gfp, devcd_readv, vfree);
+}
+EXPORT_SYMBOL_GPL(dev_coredumpv);
+
+static int devcd_match_failing(struct device *dev, const void *failing)
+{
+ struct devcd_entry *devcd = dev_to_devcd(dev);
+
+ return devcd->failing_dev == failing;
+}
+
+/**
+ * dev_coredumpm - create device coredump with read/free methods
+ * @dev: the struct device for the crashed device
+ * @owner: the module that contains the read/free functions, use %THIS_MODULE
+ * @data: data cookie for the @read/@free functions
+ * @datalen: length of the data
+ * @gfp: allocation flags
+ * @read: function to read from the given buffer
+ * @free: function to free the given buffer
+ *
+ * Creates a new device coredump for the given device. If a previous one hasn't
+ * been read yet, the new coredump is discarded. The data lifetime is determined
+ * by the device coredump framework and when it is no longer needed the @free
+ * function will be called to free the data.
+ */
+void dev_coredumpm(struct device *dev, struct module *owner,
+ const void *data, size_t datalen, gfp_t gfp,
+ ssize_t (*read)(char *buffer, loff_t offset, size_t count,
+ const void *data, size_t datalen),
+ void (*free)(const void *data))
+{
+ static atomic_t devcd_count = ATOMIC_INIT(0);
+ struct devcd_entry *devcd;
+ struct device *existing;
+
+ existing = class_find_device(&devcd_class, NULL, dev,
+ devcd_match_failing);
+ if (existing) {
+ put_device(existing);
+ goto free;
+ }
+
+ if (!try_module_get(owner))
+ goto free;
+
+ devcd = kzalloc(sizeof(*devcd), gfp);
+ if (!devcd)
+ goto put_module;
+
+ devcd->owner = owner;
+ devcd->data = data;
+ devcd->datalen = datalen;
+ devcd->read = read;
+ devcd->free = free;
+ devcd->failing_dev = get_device(dev);
+
+ device_initialize(&devcd->devcd_dev);
+
+ dev_set_name(&devcd->devcd_dev, "devcd%d",
+ atomic_inc_return(&devcd_count));
+ devcd->devcd_dev.class = &devcd_class;
+
+ if (device_add(&devcd->devcd_dev))
+ goto put_device;
+
+ if (sysfs_create_link(&devcd->devcd_dev.kobj, &dev->kobj,
+ "failing_device"))
+ /* nothing - symlink will be missing */;
+
+ if (sysfs_create_link(&dev->kobj, &devcd->devcd_dev.kobj,
+ "devcoredump"))
+ /* nothing - symlink will be missing */;
+
+ INIT_DELAYED_WORK(&devcd->del_wk, devcd_del);
+ schedule_delayed_work(&devcd->del_wk, DEVCD_TIMEOUT);
+
+ return;
+ put_device:
+ put_device(&devcd->devcd_dev);
+ put_module:
+ module_put(owner);
+ free:
+ free(data);
+}
+EXPORT_SYMBOL_GPL(dev_coredumpm);
+
+static int __init devcoredump_init(void)
+{
+ return class_register(&devcd_class);
+}
+__initcall(devcoredump_init);
+
+static int devcd_free(struct device *dev, void *data)
+{
+ struct devcd_entry *devcd = dev_to_devcd(dev);
+
+ flush_delayed_work(&devcd->del_wk);
+ return 0;
+}
+
+static void __exit devcoredump_exit(void)
+{
+ class_for_each_device(&devcd_class, NULL, NULL, devcd_free);
+ class_unregister(&devcd_class);
+}
+__exitcall(devcoredump_exit);
diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index 69d9b0c89a0..c8a53d1e019 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -817,13 +817,13 @@ char *devm_kstrdup(struct device *dev, const char *s, gfp_t gfp)
EXPORT_SYMBOL_GPL(devm_kstrdup);
/**
- * devm_kvasprintf - Allocate resource managed space
- * for the formatted string.
+ * devm_kvasprintf - Allocate resource managed space and format a string
+ * into that.
* @dev: Device to allocate memory for
* @gfp: the GFP mask used in the devm_kmalloc() call when
* allocating memory
- * @fmt: the formatted string to duplicate
- * @ap: the list of tokens to be placed in the formatted string
+ * @fmt: The printf()-style format string
+ * @ap: Arguments for the format string
* RETURNS:
* Pointer to allocated string on success, NULL on failure.
*/
@@ -849,12 +849,13 @@ char *devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt,
EXPORT_SYMBOL(devm_kvasprintf);
/**
- * devm_kasprintf - Allocate resource managed space
- * and copy an existing formatted string into that
+ * devm_kasprintf - Allocate resource managed space and format a string
+ * into that.
* @dev: Device to allocate memory for
* @gfp: the GFP mask used in the devm_kmalloc() call when
* allocating memory
- * @fmt: the string to duplicate
+ * @fmt: The printf()-style format string
+ * @...: Arguments for the format string
* RETURNS:
* Pointer to allocated string on success, NULL on failure.
*/
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index bf424305f3d..3d785ebb48d 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -1105,6 +1105,9 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
if (!firmware_p)
return -EINVAL;
+ if (!name || name[0] == '\0')
+ return -EINVAL;
+
ret = _request_firmware_prepare(&fw, name, device);
if (ret <= 0) /* error or already assigned */
goto out;
diff --git a/drivers/base/node.c b/drivers/base/node.c
index c6d3ae05f1c..d51c49c9baf 100644
--- a/drivers/base/node.c
+++ b/drivers/base/node.c
@@ -603,7 +603,6 @@ void unregister_one_node(int nid)
return;
unregister_node(node_devices[nid]);
- kfree(node_devices[nid]);
node_devices[nid] = NULL;
}
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index 4251570610c..8a3f51f7b1b 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -11,12 +11,15 @@ config REGMAP
config REGMAP_I2C
tristate
+ depends on I2C
config REGMAP_SPI
tristate
+ depends on SPI
config REGMAP_SPMI
tristate
+ depends on SPMI
config REGMAP_MMIO
tristate
diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h
index 7d1326985be..0da5865df5b 100644
--- a/drivers/base/regmap/internal.h
+++ b/drivers/base/regmap/internal.h
@@ -49,8 +49,10 @@ struct regmap_async {
};
struct regmap {
- struct mutex mutex;
- spinlock_t spinlock;
+ union {
+ struct mutex mutex;
+ spinlock_t spinlock;
+ };
unsigned long spinlock_flags;
regmap_lock lock;
regmap_unlock unlock;
@@ -146,6 +148,9 @@ struct regcache_ops {
enum regcache_type type;
int (*init)(struct regmap *map);
int (*exit)(struct regmap *map);
+#ifdef CONFIG_DEBUG_FS
+ void (*debugfs_init)(struct regmap *map);
+#endif
int (*read)(struct regmap *map, unsigned int reg, unsigned int *value);
int (*write)(struct regmap *map, unsigned int reg, unsigned int value);
int (*sync)(struct regmap *map, unsigned int min, unsigned int max);
diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c
index 6a7e4fa1285..f3e8fe0cc65 100644
--- a/drivers/base/regmap/regcache-rbtree.c
+++ b/drivers/base/regmap/regcache-rbtree.c
@@ -194,10 +194,6 @@ static void rbtree_debugfs_init(struct regmap *map)
{
debugfs_create_file("rbtree", 0400, map->debugfs, map, &rbtree_fops);
}
-#else
-static void rbtree_debugfs_init(struct regmap *map)
-{
-}
#endif
static int regcache_rbtree_init(struct regmap *map)
@@ -222,8 +218,6 @@ static int regcache_rbtree_init(struct regmap *map)
goto err;
}
- rbtree_debugfs_init(map);
-
return 0;
err:
@@ -532,6 +526,9 @@ struct regcache_ops regcache_rbtree_ops = {
.name = "rbtree",
.init = regcache_rbtree_init,
.exit = regcache_rbtree_exit,
+#ifdef CONFIG_DEBUG_FS
+ .debugfs_init = rbtree_debugfs_init,
+#endif
.read = regcache_rbtree_read,
.write = regcache_rbtree_write,
.sync = regcache_rbtree_sync,
diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c
index 29b4128da0b..f1280dc356d 100644
--- a/drivers/base/regmap/regcache.c
+++ b/drivers/base/regmap/regcache.c
@@ -269,8 +269,11 @@ static int regcache_default_sync(struct regmap *map, unsigned int min,
map->cache_bypass = 1;
ret = _regmap_write(map, reg, val);
map->cache_bypass = 0;
- if (ret)
+ if (ret) {
+ dev_err(map->dev, "Unable to sync register %#x. %d\n",
+ reg, ret);
return ret;
+ }
dev_dbg(map->dev, "Synced register %#x, value %#x\n", reg, val);
}
@@ -615,8 +618,11 @@ static int regcache_sync_block_single(struct regmap *map, void *block,
ret = _regmap_write(map, regtmp, val);
map->cache_bypass = 0;
- if (ret != 0)
+ if (ret != 0) {
+ dev_err(map->dev, "Unable to sync register %#x. %d\n",
+ regtmp, ret);
return ret;
+ }
dev_dbg(map->dev, "Synced register %#x, value %#x\n",
regtmp, val);
}
@@ -641,6 +647,9 @@ static int regcache_sync_block_raw_flush(struct regmap *map, const void **data,
map->cache_bypass = 1;
ret = _regmap_raw_write(map, base, *data, count * val_bytes);
+ if (ret)
+ dev_err(map->dev, "Unable to sync registers %#x-%#x. %d\n",
+ base, cur - map->reg_stride, ret);
map->cache_bypass = 0;
@@ -698,7 +707,7 @@ int regcache_sync_block(struct regmap *map, void *block,
unsigned int block_base, unsigned int start,
unsigned int end)
{
- if (regmap_can_raw_write(map))
+ if (regmap_can_raw_write(map) && !map->use_single_rw)
return regcache_sync_block_raw(map, block, cache_present,
block_base, start, end);
else
diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c
index 45d812c0ea7..5799a0b9e6c 100644
--- a/drivers/base/regmap/regmap-debugfs.c
+++ b/drivers/base/regmap/regmap-debugfs.c
@@ -473,6 +473,7 @@ void regmap_debugfs_init(struct regmap *map, const char *name)
{
struct rb_node *next;
struct regmap_range_node *range_node;
+ const char *devname = "dummy";
/* If we don't have the debugfs root yet, postpone init */
if (!regmap_debugfs_root) {
@@ -491,12 +492,15 @@ void regmap_debugfs_init(struct regmap *map, const char *name)
INIT_LIST_HEAD(&map->debugfs_off_cache);
mutex_init(&map->cache_lock);
+ if (map->dev)
+ devname = dev_name(map->dev);
+
if (name) {
map->debugfs_name = kasprintf(GFP_KERNEL, "%s-%s",
- dev_name(map->dev), name);
+ devname, name);
name = map->debugfs_name;
} else {
- name = dev_name(map->dev);
+ name = devname;
}
map->debugfs = debugfs_create_dir(name, regmap_debugfs_root);
@@ -512,7 +516,14 @@ void regmap_debugfs_init(struct regmap *map, const char *name)
map, &regmap_reg_ranges_fops);
if (map->max_register || regmap_readable(map, 0)) {
- debugfs_create_file("registers", 0400, map->debugfs,
+ umode_t registers_mode;
+
+ if (IS_ENABLED(REGMAP_ALLOW_WRITE_DEBUGFS))
+ registers_mode = 0600;
+ else
+ registers_mode = 0400;
+
+ debugfs_create_file("registers", registers_mode, map->debugfs,
map, &regmap_map_fops);
debugfs_create_file("access", 0400, map->debugfs,
map, &regmap_access_fops);
@@ -538,6 +549,9 @@ void regmap_debugfs_init(struct regmap *map, const char *name)
next = rb_next(&range_node->node);
}
+
+ if (map->cache_ops && map->cache_ops->debugfs_init)
+ map->cache_ops->debugfs_init(map);
}
void regmap_debugfs_exit(struct regmap *map)
diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c
index ca193d1ef47..053150a7f9f 100644
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -168,6 +168,8 @@ static struct regmap_bus regmap_i2c = {
.write = regmap_i2c_write,
.gather_write = regmap_i2c_gather_write,
.read = regmap_i2c_read,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
};
static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c
index 0eb3097c0d7..53d1148e80a 100644
--- a/drivers/base/regmap/regmap-spi.c
+++ b/drivers/base/regmap/regmap-spi.c
@@ -109,6 +109,8 @@ static struct regmap_bus regmap_spi = {
.async_alloc = regmap_spi_async_alloc,
.read = regmap_spi_read,
.read_flag_mask = 0x80,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
};
/**
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index 78f43fb2fe8..d2f8a818d20 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -15,6 +15,7 @@
#include <linux/export.h>
#include <linux/mutex.h>
#include <linux/err.h>
+#include <linux/of.h>
#include <linux/rbtree.h>
#include <linux/sched.h>
@@ -109,7 +110,7 @@ bool regmap_readable(struct regmap *map, unsigned int reg)
bool regmap_volatile(struct regmap *map, unsigned int reg)
{
- if (!regmap_readable(map, reg))
+ if (!map->format.format_write && !regmap_readable(map, reg))
return false;
if (map->volatile_reg)
@@ -448,6 +449,71 @@ int regmap_attach_dev(struct device *dev, struct regmap *map,
}
EXPORT_SYMBOL_GPL(regmap_attach_dev);
+static enum regmap_endian regmap_get_reg_endian(const struct regmap_bus *bus,
+ const struct regmap_config *config)
+{
+ enum regmap_endian endian;
+
+ /* Retrieve the endianness specification from the regmap config */
+ endian = config->reg_format_endian;
+
+ /* If the regmap config specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Retrieve the endianness specification from the bus config */
+ if (bus && bus->reg_format_endian_default)
+ endian = bus->reg_format_endian_default;
+
+ /* If the bus specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Use this if no other value was found */
+ return REGMAP_ENDIAN_BIG;
+}
+
+static enum regmap_endian regmap_get_val_endian(struct device *dev,
+ const struct regmap_bus *bus,
+ const struct regmap_config *config)
+{
+ struct device_node *np;
+ enum regmap_endian endian;
+
+ /* Retrieve the endianness specification from the regmap config */
+ endian = config->val_format_endian;
+
+ /* If the regmap config specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* If the dev and dev->of_node exist try to get endianness from DT */
+ if (dev && dev->of_node) {
+ np = dev->of_node;
+
+ /* Parse the device's DT node for an endianness specification */
+ if (of_property_read_bool(np, "big-endian"))
+ endian = REGMAP_ENDIAN_BIG;
+ else if (of_property_read_bool(np, "little-endian"))
+ endian = REGMAP_ENDIAN_LITTLE;
+
+ /* If the endianness was specified in DT, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+ }
+
+ /* Retrieve the endianness specification from the bus config */
+ if (bus && bus->val_format_endian_default)
+ endian = bus->val_format_endian_default;
+
+ /* If the bus specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Use this if no other value was found */
+ return REGMAP_ENDIAN_BIG;
+}
+
/**
* regmap_init(): Initialise register map
*
@@ -551,17 +617,8 @@ struct regmap *regmap_init(struct device *dev,
map->reg_read = _regmap_bus_read;
}
- reg_endian = config->reg_format_endian;
- if (reg_endian == REGMAP_ENDIAN_DEFAULT)
- reg_endian = bus->reg_format_endian_default;
- if (reg_endian == REGMAP_ENDIAN_DEFAULT)
- reg_endian = REGMAP_ENDIAN_BIG;
-
- val_endian = config->val_format_endian;
- if (val_endian == REGMAP_ENDIAN_DEFAULT)
- val_endian = bus->val_format_endian_default;
- if (val_endian == REGMAP_ENDIAN_DEFAULT)
- val_endian = REGMAP_ENDIAN_BIG;
+ reg_endian = regmap_get_reg_endian(bus, config);
+ val_endian = regmap_get_val_endian(dev, bus, config);
switch (config->reg_bits + map->reg_shift) {
case 2:
@@ -1408,7 +1465,7 @@ int _regmap_write(struct regmap *map, unsigned int reg,
}
#ifdef LOG_DEVICE
- if (strcmp(dev_name(map->dev), LOG_DEVICE) == 0)
+ if (map->dev && strcmp(dev_name(map->dev), LOG_DEVICE) == 0)
dev_info(map->dev, "%x <= %x\n", reg, val);
#endif
@@ -1659,6 +1716,9 @@ out:
} else {
void *wval;
+ if (!val_count)
+ return -EINVAL;
+
wval = kmemdup(val, val_count * val_bytes, GFP_KERNEL);
if (!wval) {
dev_err(map->dev, "Error in memory allocation\n");
@@ -2058,7 +2118,7 @@ static int _regmap_read(struct regmap *map, unsigned int reg,
ret = map->reg_read(context, reg, val);
if (ret == 0) {
#ifdef LOG_DEVICE
- if (strcmp(dev_name(map->dev), LOG_DEVICE) == 0)
+ if (map->dev && strcmp(dev_name(map->dev), LOG_DEVICE) == 0)
dev_info(map->dev, "%x => %x\n", reg, *val);
#endif