diff options
Diffstat (limited to 'drivers/base/regmap')
-rw-r--r-- | drivers/base/regmap/Kconfig | 14 | ||||
-rw-r--r-- | drivers/base/regmap/Makefile | 8 | ||||
-rw-r--r-- | drivers/base/regmap/internal.h | 51 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-fmt.c | 574 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-i2c.c | 101 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-mmio.c | 320 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-multi.c | 104 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-spi.c | 42 | ||||
-rw-r--r-- | drivers/base/regmap/regmap.c | 204 |
9 files changed, 1351 insertions, 67 deletions
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig new file mode 100644 index 0000000000..c76908952a --- /dev/null +++ b/drivers/base/regmap/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config REGMAP_FORMATTED + bool + +config REGMAP_I2C + bool "I2C regmaps" if COMPILE_TEST + depends on I2C + select REGMAP_FORMATTED + +config REGMAP_SPI + bool "SPI regmaps" if COMPILE_TEST + depends on SPI + select REGMAP_FORMATTED diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 4dc3d8c510..6911e07f0e 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1 +1,7 @@ -obj-y += regmap.o
\ No newline at end of file +# SPDX-License-Identifier: GPL-2.0-only +obj-y += regmap.o +obj-y += regmap-multi.o +obj-y += regmap-mmio.o +obj-$(CONFIG_REGMAP_FORMATTED) += regmap-fmt.o +obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o +obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 52df5290a1..ac3f0d3c0f 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -1,18 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef REGMAP_INTERNAL_H_ +#define REGMAP_INTERNAL_H_ #include <linux/list.h> +#include <driver.h> + +struct regmap_bus; + +struct regmap_format { + size_t buf_size; + size_t reg_bytes; + size_t pad_bytes; + size_t val_bytes; + void (*format_write)(struct regmap *map, + unsigned int reg, unsigned int val); + void (*format_reg)(void *buf, unsigned int reg, unsigned int shift); + void (*format_val)(void *buf, unsigned int val, unsigned int shift); + unsigned int (*parse_val)(const void *buf); +}; struct regmap { - struct device_d *dev; + struct device *dev; const struct regmap_bus *bus; const char *name; void *bus_context; struct list_head list; - int reg_bits; int reg_stride; - int pad_bits; - int val_bits; - int val_bytes; + void *work_buf; /* Scratch buffer used to format I/O */ + struct regmap_format format; + unsigned int read_flag_mask; + unsigned int write_flag_mask; + int reg_shift; unsigned int max_register; struct cdev cdev; -};
\ No newline at end of file + + int (*reg_read)(void *context, unsigned int reg, + unsigned int *val); + int (*reg_write)(void *context, unsigned int reg, + unsigned int val); +}; + +enum regmap_endian regmap_get_val_endian(struct device *dev, + const struct regmap_bus *bus, + const struct regmap_config *config); + +#ifdef CONFIG_REGMAP_FORMATTED +int regmap_formatted_init(struct regmap *map, const struct regmap_config *); +#else +static inline int regmap_formatted_init(struct regmap *map, const struct regmap_config *cfg) +{ + return -ENOSYS; +} +#endif + +#endif diff --git a/drivers/base/regmap/regmap-fmt.c b/drivers/base/regmap/regmap-fmt.c new file mode 100644 index 0000000000..e7f6a8da80 --- /dev/null +++ b/drivers/base/regmap/regmap-fmt.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Formatted register map access API + * + * Copyright 2022 Ahmad Fatoum <a.fatoum@pengutronix.de> + * + * based on Kernel code: + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + */ + +#include <common.h> +#include <linux/regmap.h> +#include <linux/log2.h> +#include <asm/unaligned.h> + +#include "internal.h" + +static void regmap_format_12_20_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + out[0] = reg >> 4; + out[1] = (reg << 4) | (val >> 16); + out[2] = val >> 8; + out[3] = val; +} + + +static void regmap_format_2_6_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + *out = (reg << 6) | val; +} + +static void regmap_format_4_12_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 12) | val); +} + +static void regmap_format_7_9_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 9) | val); +} + +static void regmap_format_7_17_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + out[2] = val; + out[1] = val >> 8; + out[0] = (val >> 16) | (reg << 1); +} + +static void regmap_format_10_14_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + out[2] = val; + out[1] = (val >> 8) | (reg << 6); + out[0] = reg >> 2; +} + +static void regmap_format_8(void *buf, unsigned int val, unsigned int shift) +{ + u8 *b = buf; + + b[0] = val << shift; +} + +static void regmap_format_16_be(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_be16(val << shift, buf); +} + +static void regmap_format_16_le(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_le16(val << shift, buf); +} + +static void regmap_format_16_native(void *buf, unsigned int val, + unsigned int shift) +{ + u16 v = val << shift; + + memcpy(buf, &v, sizeof(v)); +} + +static void regmap_format_24(void *buf, unsigned int val, unsigned int shift) +{ + u8 *b = buf; + + val <<= shift; + + b[0] = val >> 16; + b[1] = val >> 8; + b[2] = val; +} + +static void regmap_format_32_be(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_be32(val << shift, buf); +} + +static void regmap_format_32_le(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_le32(val << shift, buf); +} + +static void regmap_format_32_native(void *buf, unsigned int val, + unsigned int shift) +{ + u32 v = val << shift; + + memcpy(buf, &v, sizeof(v)); +} + +#ifdef CONFIG_64BIT +static void regmap_format_64_be(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_be64((u64) val << shift, buf); +} + +static void regmap_format_64_le(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_le64((u64) val << shift, buf); +} + +static void regmap_format_64_native(void *buf, unsigned int val, + unsigned int shift) +{ + u64 v = (u64) val << shift; + + memcpy(buf, &v, sizeof(v)); +} +#endif + +static unsigned int regmap_parse_8(const void *buf) +{ + const u8 *b = buf; + + return b[0]; +} + +static unsigned int regmap_parse_16_be(const void *buf) +{ + return get_unaligned_be16(buf); +} + +static unsigned int regmap_parse_16_le(const void *buf) +{ + return get_unaligned_le16(buf); +} + +static unsigned int regmap_parse_16_native(const void *buf) +{ + u16 v; + + memcpy(&v, buf, sizeof(v)); + return v; +} + +static unsigned int regmap_parse_24(const void *buf) +{ + const u8 *b = buf; + unsigned int ret = b[2]; + ret |= ((unsigned int)b[1]) << 8; + ret |= ((unsigned int)b[0]) << 16; + + return ret; +} + +static unsigned int regmap_parse_32_be(const void *buf) +{ + return get_unaligned_be32(buf); +} + +static unsigned int regmap_parse_32_le(const void *buf) +{ + return get_unaligned_le32(buf); +} + +static unsigned int regmap_parse_32_native(const void *buf) +{ + u32 v; + + memcpy(&v, buf, sizeof(v)); + return v; +} + +#ifdef CONFIG_64BIT +static unsigned int regmap_parse_64_be(const void *buf) +{ + return get_unaligned_be64(buf); +} + +static unsigned int regmap_parse_64_le(const void *buf) +{ + return get_unaligned_le64(buf); +} + +static unsigned int regmap_parse_64_native(const void *buf) +{ + u64 v; + + memcpy(&v, buf, sizeof(v)); + return v; +} +#endif + + +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 void regmap_set_work_buf_flag_mask(struct regmap *map, int max_bytes, + unsigned long mask) +{ + u8 *buf; + int i; + + if (!mask || !map->work_buf) + return; + + buf = map->work_buf; + + for (i = 0; i < max_bytes; i++) + buf[i] |= (mask >> (8 * i)) & 0xff; +} + +static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, + unsigned int val_len, bool noinc) +{ + const struct regmap_bus *bus = map->bus; + + if (!bus->read) + return -EINVAL; + + map->format.format_reg(map->work_buf, reg, map->reg_shift); + regmap_set_work_buf_flag_mask(map, map->format.reg_bytes, + map->read_flag_mask); + + return bus->read(map->bus_context, map->work_buf, + map->format.reg_bytes + map->format.pad_bytes, + val, val_len); + +} + +static int _regmap_bus_read(void *context, unsigned int reg, + unsigned int *val) +{ + int ret; + struct regmap *map = context; + void *work_val = map->work_buf + map->format.reg_bytes + + map->format.pad_bytes; + + if (!map->format.parse_val) + return -EINVAL; + + ret = _regmap_raw_read(map, reg, work_val, map->format.val_bytes, false); + if (ret == 0) + *val = map->format.parse_val(work_val); + + return ret; +} + +static int _regmap_bus_formatted_write(void *context, unsigned int reg, + unsigned int val) +{ + struct regmap *map = context; + + map->format.format_write(map, reg, val); + + return map->bus->write(map->bus_context, map->work_buf, + map->format.buf_size); +} + +static int _regmap_raw_write_impl(struct regmap *map, unsigned int reg, + const void *val, size_t val_len, bool noinc) +{ + void *work_val = map->work_buf + map->format.reg_bytes + + map->format.pad_bytes; + + map->format.format_reg(map->work_buf, reg, map->reg_shift); + regmap_set_work_buf_flag_mask(map, map->format.reg_bytes, + map->write_flag_mask); + + /* + * Essentially all I/O mechanisms will be faster with a single + * buffer to write. Since register syncs often generate raw + * writes of single registers optimise that case. + */ + if (val != work_val && val_len == map->format.val_bytes) { + memcpy(work_val, val, map->format.val_bytes); + val = work_val; + } + + /* If we're doing a single register write we can probably just + * send the work_buf directly, otherwise try to do a gather + * write. + */ + return map->bus->write(map->bus_context, map->work_buf, + map->format.reg_bytes + + map->format.pad_bytes + + val_len); + +} + +static int _regmap_bus_raw_write(void *context, unsigned int reg, + unsigned int val) +{ + struct regmap *map = context; + + WARN_ON(!map->format.format_val); + + map->format.format_val(map->work_buf + map->format.reg_bytes + + map->format.pad_bytes, val, 0); + return _regmap_raw_write_impl(map, reg, + map->work_buf + + map->format.reg_bytes + + map->format.pad_bytes, + map->format.val_bytes, + false); +} + +int regmap_formatted_init(struct regmap *map, const struct regmap_config *config) +{ + enum regmap_endian reg_endian, val_endian; + const struct regmap_bus *bus = map->bus; + + map->format.buf_size = DIV_ROUND_UP(config->reg_bits + + config->val_bits + config->pad_bits, 8); + + map->work_buf = xzalloc(map->format.buf_size); + + if (config->read_flag_mask || config->write_flag_mask) { + map->read_flag_mask = config->read_flag_mask; + map->write_flag_mask = config->write_flag_mask; + } else { + map->read_flag_mask = bus->read_flag_mask; + } + + map->reg_read = _regmap_bus_read; + + reg_endian = regmap_get_reg_endian(bus, config); + val_endian = regmap_get_val_endian(map->dev, bus, config); + + switch (config->reg_bits + config->pad_bits % 8) { + case 2: + switch (config->val_bits) { + case 6: + map->format.format_write = regmap_format_2_6_write; + break; + default: + return -EINVAL; + } + break; + + case 4: + switch (config->val_bits) { + case 12: + map->format.format_write = regmap_format_4_12_write; + break; + default: + return -EINVAL; + } + break; + + case 7: + switch (config->val_bits) { + case 9: + map->format.format_write = regmap_format_7_9_write; + break; + case 17: + map->format.format_write = regmap_format_7_17_write; + break; + default: + return -EINVAL; + } + break; + + case 10: + switch (config->val_bits) { + case 14: + map->format.format_write = regmap_format_10_14_write; + break; + default: + return -EINVAL; + } + break; + + case 12: + switch (config->val_bits) { + case 20: + map->format.format_write = regmap_format_12_20_write; + break; + default: + return -EINVAL; + } + break; + + case 8: + map->format.format_reg = regmap_format_8; + break; + + case 16: + switch (reg_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_reg = regmap_format_16_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_reg = regmap_format_16_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_reg = regmap_format_16_native; + break; + default: + return -EINVAL; + } + break; + + case 24: + if (reg_endian != REGMAP_ENDIAN_BIG) + return -EINVAL; + map->format.format_reg = regmap_format_24; + break; + + case 32: + switch (reg_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_reg = regmap_format_32_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_reg = regmap_format_32_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_reg = regmap_format_32_native; + break; + default: + return -EINVAL; + } + break; + +#ifdef CONFIG_64BIT + case 64: + switch (reg_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_reg = regmap_format_64_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_reg = regmap_format_64_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_reg = regmap_format_64_native; + break; + default: + return -EINVAL; + } + break; +#endif + + default: + return -EINVAL; + } + + switch (config->val_bits) { + case 8: + map->format.format_val = regmap_format_8; + map->format.parse_val = regmap_parse_8; + break; + case 16: + switch (val_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_val = regmap_format_16_be; + map->format.parse_val = regmap_parse_16_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_val = regmap_format_16_le; + map->format.parse_val = regmap_parse_16_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_val = regmap_format_16_native; + map->format.parse_val = regmap_parse_16_native; + break; + default: + return -EINVAL; + } + break; + case 24: + if (val_endian != REGMAP_ENDIAN_BIG) + return -EINVAL; + map->format.format_val = regmap_format_24; + map->format.parse_val = regmap_parse_24; + break; + case 32: + switch (val_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_val = regmap_format_32_be; + map->format.parse_val = regmap_parse_32_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_val = regmap_format_32_le; + map->format.parse_val = regmap_parse_32_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_val = regmap_format_32_native; + map->format.parse_val = regmap_parse_32_native; + break; + default: + return -EINVAL; + } + break; +#ifdef CONFIG_64BIT + case 64: + switch (val_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_val = regmap_format_64_be; + map->format.parse_val = regmap_parse_64_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_val = regmap_format_64_le; + map->format.parse_val = regmap_parse_64_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_val = regmap_format_64_native; + map->format.parse_val = regmap_parse_64_native; + break; + default: + return -EINVAL; + } + break; +#endif + } + + if (map->format.format_write) + map->reg_write = _regmap_bus_formatted_write; + else if (map->format.format_val) + map->reg_write = _regmap_bus_raw_write; + else + return -EOPNOTSUPP; + + return 0; +} diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c new file mode 100644 index 0000000000..13ba5866c0 --- /dev/null +++ b/drivers/base/regmap/regmap-i2c.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, Ahmad Fatoum, Pengutronix + */ + +#include <i2c/i2c.h> +#include <linux/regmap.h> + + +static int regmap_i2c_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + struct i2c_msg xfer[2]; + int ret; + + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = reg_size; + xfer[0].buf = (void *)reg; + + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = val_size; + xfer[1].buf = val; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static int regmap_i2c_write(void *context, const void *data, size_t count) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + int ret; + + ret = i2c_master_send(i2c, data, count); + if (ret == count) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static const struct regmap_bus regmap_regmap_i2c_bus = { + .write = regmap_i2c_write, + .read = regmap_i2c_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +struct regmap *regmap_init_i2c(struct i2c_client *client, + const struct regmap_config *config) +{ + return regmap_init(&client->dev, ®map_regmap_i2c_bus, client, config); +} + +static int regmap_smbus_byte_reg_read(void *client, unsigned int reg, unsigned int *val) +{ + int ret; + + if (reg > 0xff) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int regmap_smbus_byte_reg_write(void *client, unsigned int reg, unsigned int val) +{ + if (val > 0xff || reg > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static const struct regmap_bus regmap_smbus_byte = { + .reg_write = regmap_smbus_byte_reg_write, + .reg_read = regmap_smbus_byte_reg_read, +}; + +struct regmap *regmap_init_i2c_smbus(struct i2c_client *client, + const struct regmap_config *config) +{ + if (config->val_bits != 8 || config->reg_bits != 8) + return ERR_PTR(-ENOSYS); + return regmap_init(&client->dev, ®map_smbus_byte, client, config); +} diff --git a/drivers/base/regmap/regmap-mmio.c b/drivers/base/regmap/regmap-mmio.c new file mode 100644 index 0000000000..01b0a99631 --- /dev/null +++ b/drivers/base/regmap/regmap-mmio.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Register map access API - MMIO support +// +// Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/err.h> +#include <io.h> +#include <linux/regmap.h> + +#include "internal.h" + +struct regmap_mmio_context { + void __iomem *regs; + unsigned val_bytes; + + struct clk *clk; + + void (*reg_write)(struct regmap_mmio_context *ctx, + unsigned int reg, unsigned int val); + unsigned int (*reg_read)(struct regmap_mmio_context *ctx, + unsigned int reg); +}; + +static int regmap_mmio_regbits_check(size_t reg_bits) +{ + switch (reg_bits) { + case 8: + case 16: + case 32: +#ifdef CONFIG_64BIT + case 64: +#endif + return 0; + default: + return -EINVAL; + } +} + +static int regmap_mmio_get_min_stride(size_t val_bits) +{ + int min_stride; + + switch (val_bits) { + case 8: + /* The core treats 0 as 1 */ + min_stride = 0; + break; + case 16: + min_stride = 2; + break; + case 32: + min_stride = 4; + break; +#ifdef CONFIG_64BIT + case 64: + min_stride = 8; + break; +#endif + default: + return -EINVAL; + } + + return min_stride; +} + +static void regmap_mmio_write8(struct regmap_mmio_context *ctx, + unsigned int reg, + unsigned int val) +{ + writeb(val, ctx->regs + reg); +} + +static void regmap_mmio_write16le(struct regmap_mmio_context *ctx, + unsigned int reg, + unsigned int val) +{ + writew(val, ctx->regs + reg); +} + +static void regmap_mmio_write32le(struct regmap_mmio_context *ctx, + unsigned int reg, + unsigned int val) +{ + writel(val, ctx->regs + reg); +} + +static void regmap_mmio_write16be(struct regmap_mmio_context *ctx, + unsigned int reg, + unsigned int val) +{ + iowrite16be(val, ctx->regs + reg); +} + +static void regmap_mmio_write32be(struct regmap_mmio_context *ctx, + unsigned int reg, + unsigned int val) +{ + iowrite32be(val, ctx->regs + reg); +} + +#ifdef CONFIG_64BIT +static void regmap_mmio_write64le(struct regmap_mmio_context *ctx, + unsigned int reg, + unsigned int val) +{ + writeq(val, ctx->regs + reg); +} +#endif + +static int regmap_mmio_write(void *context, unsigned int reg, unsigned int val) +{ + struct regmap_mmio_context *ctx = context; + int ret; + + ret = clk_enable(ctx->clk); + if (ret < 0) + return ret; + + ctx->reg_write(ctx, reg, val); + + clk_disable(ctx->clk); + + return 0; +} + +static unsigned int regmap_mmio_read8(struct regmap_mmio_context *ctx, + unsigned int reg) +{ + return readb(ctx->regs + reg); +} + +static unsigned int regmap_mmio_read16le(struct regmap_mmio_context *ctx, + unsigned int reg) +{ + return readw(ctx->regs + reg); +} + +static unsigned int regmap_mmio_read32le(struct regmap_mmio_context *ctx, + unsigned int reg) +{ + return readl(ctx->regs + reg); +} + +#ifdef CONFIG_64BIT +static unsigned int regmap_mmio_read64le(struct regmap_mmio_context *ctx, + unsigned int reg) +{ + return readq(ctx->regs + reg); +} +#endif + +static unsigned int regmap_mmio_read16be(struct regmap_mmio_context *ctx, + unsigned int reg) +{ + return ioread16be(ctx->regs + reg); +} + +static unsigned int regmap_mmio_read32be(struct regmap_mmio_context *ctx, + unsigned int reg) +{ + return ioread32be(ctx->regs + reg); +} + +static int regmap_mmio_read(void *context, unsigned int reg, unsigned int *val) +{ + struct regmap_mmio_context *ctx = context; + int ret; + + ret = clk_enable(ctx->clk); + if (ret < 0) + return ret; + + *val = ctx->reg_read(ctx, reg); + + clk_disable(ctx->clk); + + return 0; +} + +static const struct regmap_bus regmap_mmio = { + .reg_write = regmap_mmio_write, + .reg_read = regmap_mmio_read, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, +}; + +static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev, + void __iomem *regs, + const struct regmap_config *config) +{ + struct regmap_mmio_context *ctx; + int min_stride; + int ret; + + ret = regmap_mmio_regbits_check(config->reg_bits); + if (ret) + return ERR_PTR(ret); + + if (config->pad_bits) + return ERR_PTR(-EINVAL); + + min_stride = regmap_mmio_get_min_stride(config->val_bits); + if (min_stride < 0) + return ERR_PTR(min_stride); + + if (config->reg_stride < min_stride) + return ERR_PTR(-EINVAL); + + ctx = xzalloc(sizeof(*ctx)); + + ctx->regs = regs; + ctx->val_bytes = config->val_bits / 8; + + switch (regmap_get_val_endian(dev, ®map_mmio, config)) { + case REGMAP_ENDIAN_DEFAULT: + case REGMAP_ENDIAN_LITTLE: +#ifdef __LITTLE_ENDIAN + case REGMAP_ENDIAN_NATIVE: +#endif + switch (config->val_bits) { + case 8: + ctx->reg_read = regmap_mmio_read8; + ctx->reg_write = regmap_mmio_write8; + break; + case 16: + ctx->reg_read = regmap_mmio_read16le; + ctx->reg_write = regmap_mmio_write16le; + break; + case 32: + ctx->reg_read = regmap_mmio_read32le; + ctx->reg_write = regmap_mmio_write32le; + break; +#ifdef CONFIG_64BIT + case 64: + ctx->reg_read = regmap_mmio_read64le; + ctx->reg_write = regmap_mmio_write64le; + break; +#endif + default: + ret = -EINVAL; + goto err_free; + } + break; + case REGMAP_ENDIAN_BIG: +#ifdef __BIG_ENDIAN + case REGMAP_ENDIAN_NATIVE: +#endif + switch (config->val_bits) { + case 8: + ctx->reg_read = regmap_mmio_read8; + ctx->reg_write = regmap_mmio_write8; + break; + case 16: + ctx->reg_read = regmap_mmio_read16be; + ctx->reg_write = regmap_mmio_write16be; + break; + case 32: + ctx->reg_read = regmap_mmio_read32be; + ctx->reg_write = regmap_mmio_write32be; + break; + default: + ret = -EINVAL; + goto err_free; + } + break; + default: + ret = -EINVAL; + goto err_free; + } + + return ctx; + +err_free: + kfree(ctx); + + return ERR_PTR(ret); +} + +struct regmap *regmap_init_mmio_clk(struct device *dev, + const char *clk_id, + void __iomem *regs, + const struct regmap_config *config) +{ + struct regmap_mmio_context *ctx; + + ctx = regmap_mmio_gen_context(dev, regs, config); + if (IS_ERR(ctx)) + return ERR_CAST(ctx); + + if (clk_id) { + struct clk *clk; + + clk = clk_get(dev, clk_id); + if (IS_ERR(clk)) { + kfree(ctx); + return ERR_CAST(clk); + } + + ctx->clk = clk; + } + + return regmap_init(dev, ®map_mmio, ctx, config); +} + +int regmap_mmio_attach_clk(struct regmap *map, struct clk *clk) +{ + struct regmap_mmio_context *ctx = map->bus_context; + + ctx->clk = clk; + + return 0; +} + +void regmap_mmio_detach_clk(struct regmap *map) +{ + struct regmap_mmio_context *ctx = map->bus_context; + + ctx->clk = NULL; +} 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); +} diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c new file mode 100644 index 0000000000..d15d59f635 --- /dev/null +++ b/drivers/base/regmap/regmap-spi.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Register map access API - SPI support +// +// Copyright 2011 Wolfson Microelectronics plc +// +// Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + +#include <linux/regmap.h> +#include <spi/spi.h> + +static int regmap_spi_write(void *context, const void *data, size_t count) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + + return spi_write(spi, data, count); +} + +static int regmap_spi_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + + return spi_write_then_read(spi, reg, reg_size, val, val_size); +} + +static const struct regmap_bus regmap_spi = { + .write = regmap_spi_write, + .read = regmap_spi_read, + .read_flag_mask = 0x80, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +struct regmap *regmap_init_spi(struct spi_device *spi, + const struct regmap_config *config) +{ + return regmap_init(&spi->dev, ®map_spi, &spi->dev, config); +} diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index dc7b4f276f..7ad527954c 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Register map access API * @@ -8,19 +9,10 @@ * Copyright 2011 Wolfson Microelectronics plc * * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; version 2. - * - * 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 <common.h> -#include <regmap.h> +#include <linux/regmap.h> #include <malloc.h> #include <linux/log2.h> @@ -28,6 +20,67 @@ static LIST_HEAD(regmaps); +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->device_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; + else if (of_property_read_bool(np, "native-endian")) + endian = REGMAP_ENDIAN_NATIVE; + + /* 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; +} +EXPORT_SYMBOL_GPL(regmap_get_val_endian); + +static int _regmap_bus_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct regmap *map = context; + + return map->bus->reg_read(map->bus_context, reg, val); +} + + +static int _regmap_bus_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct regmap *map = context; + + return map->bus->reg_write(map->bus_context, reg, val); +} + /* * regmap_init - initialize and register a regmap * @@ -38,27 +91,39 @@ static LIST_HEAD(regmaps); * * Returns a pointer to the new map or a ERR_PTR value on failure */ -struct regmap *regmap_init(struct device_d *dev, +struct regmap *regmap_init(struct device *dev, const struct regmap_bus *bus, void *bus_context, const struct regmap_config *config) { struct regmap *map; + int ret; map = xzalloc(sizeof(*map)); map->dev = dev; map->bus = bus; map->name = config->name; map->bus_context = bus_context; - map->reg_bits = config->reg_bits; + map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8); map->reg_stride = config->reg_stride; if (!map->reg_stride) map->reg_stride = 1; - map->pad_bits = config->pad_bits; - map->val_bits = config->val_bits; - map->val_bytes = DIV_ROUND_UP(config->val_bits, 8); + map->format.pad_bytes = config->pad_bits / 8; + map->format.val_bytes = DIV_ROUND_UP(config->val_bits, 8); + map->reg_shift = config->pad_bits % 8; map->max_register = config->max_register; + if (!bus->read || !bus->write) { + map->reg_read = _regmap_bus_reg_read; + map->reg_write = _regmap_bus_reg_write; + } else { + ret = regmap_formatted_init(map, config); + if (ret) { + free(map); + return ERR_PTR(ret); + } + } + list_add_tail(&map->list, ®maps); return map; @@ -72,7 +137,7 @@ struct regmap *regmap_init(struct device_d *dev, * * Returns a pointer to the regmap or a ERR_PTR value on failure */ -struct regmap *dev_get_regmap(struct device_d *dev, const char *name) +struct regmap *dev_get_regmap(struct device *dev, const char *name) { struct regmap *map; @@ -88,24 +153,9 @@ struct regmap *dev_get_regmap(struct device_d *dev, const char *name) return ERR_PTR(-ENOENT); } -/* - * of_node_to_regmap - get a regmap from a device node - * - * node: The device node - * - * Returns a pointer to the regmap or a ERR_PTR if the node has no - * regmap attached. - */ -struct regmap *of_node_to_regmap(struct device_node *node) +struct device *regmap_get_device(struct regmap *map) { - struct regmap *map; - - list_for_each_entry(map, ®maps, list) { - if (map->dev->device_node == node) - return map; - } - - return ERR_PTR(-ENOENT); + return map->dev; } /* @@ -119,7 +169,7 @@ struct regmap *of_node_to_regmap(struct device_node *node) */ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) { - return map->bus->reg_write(map->bus_context, reg, val); + return map->reg_write(map, reg, val); } /* @@ -133,7 +183,7 @@ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) */ int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) { - return map->bus->reg_read(map->bus_context, reg, val); + return map->reg_read(map, reg, val); } /** @@ -198,27 +248,27 @@ int regmap_write_bits(struct regmap *map, unsigned int reg, * @map: Register map to read from * @reg: First register to be read from * @val: Pointer to store read value - * @val_len: Size of data to read + * @val_count: Number of registers to read * * A value of zero will be returned on success, a negative errno will * be returned in error cases. */ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, - size_t val_len) + size_t val_count) { - size_t val_bytes = map->val_bytes; - size_t val_count = val_len / val_bytes; unsigned int v; int ret, i; - if (val_len % val_bytes) - return -EINVAL; if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; if (val_count == 0) return -EINVAL; for (i = 0; i < val_count; i++) { + +#ifdef CONFIG_64BIT + u64 *u64 = val; +#endif u32 *u32 = val; u16 *u16 = val; u8 *u8 = val; @@ -227,7 +277,12 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, if (ret != 0) goto out; - switch (map->val_bytes) { + switch (map->format.val_bytes) { +#ifdef CONFIG_64BIT + case 8: + u64[i] = v; + break; +#endif case 4: u32[i] = v; break; @@ -253,20 +308,17 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, * @reg: Initial register to write to * @val: Block of data to be written, laid out for direct transmission to the * device - * @val_len: Length of data pointed to by val. + * @val_len: Number of registers to write * * A value of zero will be returned on success, a negative errno will * be returned in error cases. */ int regmap_bulk_write(struct regmap *map, unsigned int reg, - const void *val, size_t val_len) + const void *val, size_t val_count) { - size_t val_bytes = map->val_bytes; - size_t val_count = val_len / val_bytes; + size_t val_bytes = map->format.val_bytes; int ret, i; - if (val_len % val_bytes) - return -EINVAL; if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; if (val_count == 0) @@ -285,6 +337,11 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, case 4: ival = *(u32 *)(val + (i * val_bytes)); break; +#ifdef CONFIG_64BIT + case 8: + ival = *(u64 *)(val + (i * val_bytes)); + break; +#endif default: ret = -EINVAL; goto out; @@ -302,7 +359,7 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, int regmap_get_val_bytes(struct regmap *map) { - return map->val_bytes; + return map->format.val_bytes; } int regmap_get_max_register(struct regmap *map) @@ -317,22 +374,18 @@ int regmap_get_reg_stride(struct regmap *map) static int regmap_round_val_bytes(struct regmap *map) { - int val_bytes; - - val_bytes = roundup_pow_of_two(map->val_bits) >> 3; - if (!val_bytes) - val_bytes = 1; - - return val_bytes; + return map->format.val_bytes ?: 1; } static ssize_t regmap_cdev_read(struct cdev *cdev, void *buf, size_t count, loff_t offset, unsigned long flags) { struct regmap *map = container_of(cdev, struct regmap, cdev); + size_t val_bytes = map->format.val_bytes; int ret; - ret = regmap_bulk_read(map, offset, buf, count); + count = ALIGN_DOWN(count, val_bytes); + ret = regmap_bulk_read(map, offset, buf, count / val_bytes); if (ret) return ret; @@ -343,9 +396,11 @@ static ssize_t regmap_cdev_write(struct cdev *cdev, const void *buf, size_t coun unsigned long flags) { struct regmap *map = container_of(cdev, struct regmap, cdev); + size_t val_bytes = map->format.val_bytes; int ret; - ret = regmap_bulk_write(map, offset, buf, count); + count = ALIGN_DOWN(count, val_bytes); + ret = regmap_bulk_write(map, offset, buf, count / val_bytes); if (ret) return ret; @@ -358,6 +413,36 @@ static struct cdev_operations regmap_fops = { }; /* + * regmap_count_registers - returns the total number of registers + * + * @map: The map + * + * Returns the total number of registers in a regmap + */ +static size_t regmap_count_registers(struct regmap *map) +{ + /* + * max_register is in units of reg_stride, so we need to divide + * by the register stride before adding one to arrive at the + * total number of registers. + */ + return (map->max_register / map->reg_stride) + 1; +} + +/* + * regmap_size_bytes - computes the size of the regmap in bytes + * + * @map: The map + * + * Returns the number of bytes needed to hold all values in the + * regmap. + */ +size_t regmap_size_bytes(struct regmap *map) +{ + return regmap_round_val_bytes(map) * regmap_count_registers(map); +} + +/* * regmap_register_cdev - register a devfs file for a regmap * * @map: The map @@ -384,8 +469,7 @@ int regmap_register_cdev(struct regmap *map, const char *name) map->cdev.name = xstrdup(dev_name(map->dev)); } - map->cdev.size = regmap_round_val_bytes(map) * (map->max_register + 1) / - map->reg_stride; + map->cdev.size = regmap_size_bytes(map); map->cdev.dev = map->dev; map->cdev.ops = ®map_fops; |