diff options
Diffstat (limited to 'drivers/base/regmap/regmap-multi.c')
-rw-r--r-- | drivers/base/regmap/regmap-multi.c | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/drivers/base/regmap/regmap-multi.c b/drivers/base/regmap/regmap-multi.c new file mode 100644 index 0000000000..74f3648eb4 --- /dev/null +++ b/drivers/base/regmap/regmap-multi.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2022 Ahmad Fatoum <a.fatoum@pengutronix.de> + */ + +#include <common.h> +#include <fcntl.h> +#include <linux/regmap.h> +#include <linux/bitfield.h> +#include <linux/export.h> + +#include "internal.h" + +enum { MULTI_MAP_8, MULTI_MAP_16, MULTI_MAP_32, MULTI_MAP_64, MULTI_MAP_COUNT }; +struct regmap_multi { + struct cdev cdev; + struct regmap *map[MULTI_MAP_COUNT]; +}; + +static struct regmap *regmap_multi_cdev_get_map(struct cdev *cdev, unsigned rwsize) +{ + struct regmap_multi *multi = container_of(cdev, struct regmap_multi, cdev); + + switch (rwsize) { + case 8: + return multi->map[MULTI_MAP_64]; + case 4: + return multi->map[MULTI_MAP_32]; + case 2: + return multi->map[MULTI_MAP_16]; + case 1: + return multi->map[MULTI_MAP_8]; + } + + return NULL; +} + +static ssize_t regmap_multi_cdev_read(struct cdev *cdev, void *buf, size_t count, + loff_t offset, unsigned long flags) +{ + unsigned rwsize = FIELD_GET(O_RWSIZE_MASK, flags); + struct regmap *map; + + map = regmap_multi_cdev_get_map(cdev, rwsize); + if (!map) + return -EINVAL; + + count = ALIGN_DOWN(count, rwsize); + return regmap_bulk_read(map, offset, buf, count / rwsize) ?: count; +} + +static ssize_t regmap_multi_cdev_write(struct cdev *cdev, const void *buf, size_t count, + loff_t offset, unsigned long flags) +{ + unsigned rwsize = FIELD_GET(O_RWSIZE_MASK, flags); + struct regmap *map; + + map = regmap_multi_cdev_get_map(cdev, rwsize); + if (!map) + return -EINVAL; + + count = ALIGN_DOWN(count, rwsize); + return regmap_bulk_write(map, offset, buf, count / rwsize) ?: count; +} + +static struct cdev_operations regmap_multi_fops = { + .read = regmap_multi_cdev_read, + .write = regmap_multi_cdev_write, +}; + +int regmap_multi_register_cdev(struct regmap *map8, + struct regmap *map16, + struct regmap *map32, + struct regmap *map64) +{ + struct regmap *maps[MULTI_MAP_COUNT] = { map8, map16, map32, map64 }; + struct regmap_multi *multi; + struct cdev *cdev; + int i; + + multi = xzalloc(sizeof(*multi)); + cdev = &multi->cdev; + + cdev->ops = ®map_multi_fops; + cdev->size = LLONG_MAX; + + for (i = 0; i < MULTI_MAP_COUNT; i++) { + if (!maps[i]) + continue; + + multi->map[i] = maps[i]; + cdev->size = min_t(loff_t, regmap_size_bytes(maps[i]), cdev->size); + cdev->dev = cdev->dev ?: maps[i]->dev; + } + + if (!cdev->dev) { + free(multi); + return -EINVAL; + } + + cdev->name = xstrdup(dev_name(cdev->dev)); + + return devfs_create(cdev); +} |