| // SPDX-License-Identifier: GPL-2.0 | |
| // Copyright(c) 2020 Intel Corporation. | |
| #include <linux/bits.h> | |
| #include <linux/delay.h> | |
| #include <linux/device.h> | |
| #include <linux/errno.h> | |
| #include <linux/iopoll.h> | |
| #include <linux/module.h> | |
| #include <linux/regmap.h> | |
| #include <linux/soundwire/sdw.h> | |
| #include <linux/soundwire/sdw_registers.h> | |
| #include <sound/sdca_function.h> | |
| #include "internal.h" | |
| struct regmap_mbq_context { | |
| struct device *dev; | |
| struct sdw_slave *sdw; | |
| bool (*readable_reg)(struct device *dev, unsigned int reg); | |
| struct regmap_sdw_mbq_cfg cfg; | |
| int val_size; | |
| }; | |
| static int regmap_sdw_mbq_size(struct regmap_mbq_context *ctx, unsigned int reg) | |
| { | |
| int size = ctx->val_size; | |
| if (ctx->cfg.mbq_size) { | |
| size = ctx->cfg.mbq_size(ctx->dev, reg); | |
| if (!size || size > ctx->val_size) | |
| return -EINVAL; | |
| } | |
| return size; | |
| } | |
| static bool regmap_sdw_mbq_deferrable(struct regmap_mbq_context *ctx, unsigned int reg) | |
| { | |
| if (ctx->cfg.deferrable) | |
| return ctx->cfg.deferrable(ctx->dev, reg); | |
| return false; | |
| } | |
| static int regmap_sdw_mbq_poll_busy(struct sdw_slave *slave, unsigned int reg, | |
| struct regmap_mbq_context *ctx) | |
| { | |
| struct device *dev = ctx->dev; | |
| int val, ret = 0; | |
| dev_dbg(dev, "Deferring transaction for 0x%x\n", reg); | |
| reg = SDW_SDCA_CTL(SDW_SDCA_CTL_FUNC(reg), 0, | |
| SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0); | |
| if (!ctx->readable_reg || ctx->readable_reg(dev, reg)) { | |
| ret = read_poll_timeout(sdw_read_no_pm, val, | |
| val < 0 || !(val & SDCA_CTL_ENTITY_0_FUNCTION_BUSY), | |
| ctx->cfg.retry_us, ctx->cfg.timeout_us, | |
| false, slave, reg); | |
| if (val < 0) | |
| return val; | |
| if (ret) | |
| dev_err(dev, "Function busy timed out 0x%x: %d\n", reg, val); | |
| } else { | |
| fsleep(ctx->cfg.timeout_us); | |
| } | |
| return ret; | |
| } | |
| static int regmap_sdw_mbq_write_impl(struct sdw_slave *slave, | |
| unsigned int reg, unsigned int val, | |
| int mbq_size) | |
| { | |
| int shift = mbq_size * BITS_PER_BYTE; | |
| int ret; | |
| while (--mbq_size > 0) { | |
| shift -= BITS_PER_BYTE; | |
| ret = sdw_write_no_pm(slave, SDW_SDCA_MBQ_CTL(reg), | |
| (val >> shift) & 0xff); | |
| if (ret < 0) | |
| return ret; | |
| } | |
| return sdw_write_no_pm(slave, reg, val & 0xff); | |
| } | |
| static int regmap_sdw_mbq_write(void *context, unsigned int reg, unsigned int val) | |
| { | |
| struct regmap_mbq_context *ctx = context; | |
| struct sdw_slave *slave = ctx->sdw; | |
| struct device *dev = ctx->dev; | |
| bool deferrable = regmap_sdw_mbq_deferrable(ctx, reg); | |
| int mbq_size = regmap_sdw_mbq_size(ctx, reg); | |
| int ret; | |
| if (mbq_size < 0) | |
| return mbq_size; | |
| /* | |
| * Technically the spec does allow a device to set itself to busy for | |
| * internal reasons, but since it doesn't provide any information on | |
| * how to handle timeouts in that case, for now the code will only | |
| * process a single wait/timeout on function busy and a single retry | |
| * of the transaction. | |
| */ | |
| ret = regmap_sdw_mbq_write_impl(slave, reg, val, mbq_size); | |
| if (ret == -ENODATA) { | |
| if (!deferrable) | |
| dev_warn(dev, "Defer on undeferrable control: %x\n", reg); | |
| ret = regmap_sdw_mbq_poll_busy(slave, reg, ctx); | |
| if (ret) | |
| return ret; | |
| ret = regmap_sdw_mbq_write_impl(slave, reg, val, mbq_size); | |
| } | |
| return ret; | |
| } | |
| static int regmap_sdw_mbq_read_impl(struct sdw_slave *slave, | |
| unsigned int reg, unsigned int *val, | |
| int mbq_size) | |
| { | |
| int shift = BITS_PER_BYTE; | |
| int read; | |
| read = sdw_read_no_pm(slave, reg); | |
| if (read < 0) | |
| return read; | |
| *val = read; | |
| while (--mbq_size > 0) { | |
| read = sdw_read_no_pm(slave, SDW_SDCA_MBQ_CTL(reg)); | |
| if (read < 0) | |
| return read; | |
| *val |= read << shift; | |
| shift += BITS_PER_BYTE; | |
| } | |
| return 0; | |
| } | |
| static int regmap_sdw_mbq_read(void *context, unsigned int reg, unsigned int *val) | |
| { | |
| struct regmap_mbq_context *ctx = context; | |
| struct sdw_slave *slave = ctx->sdw; | |
| struct device *dev = ctx->dev; | |
| bool deferrable = regmap_sdw_mbq_deferrable(ctx, reg); | |
| int mbq_size = regmap_sdw_mbq_size(ctx, reg); | |
| int ret; | |
| if (mbq_size < 0) | |
| return mbq_size; | |
| /* | |
| * Technically the spec does allow a device to set itself to busy for | |
| * internal reasons, but since it doesn't provide any information on | |
| * how to handle timeouts in that case, for now the code will only | |
| * process a single wait/timeout on function busy and a single retry | |
| * of the transaction. | |
| */ | |
| ret = regmap_sdw_mbq_read_impl(slave, reg, val, mbq_size); | |
| if (ret == -ENODATA) { | |
| if (!deferrable) | |
| dev_warn(dev, "Defer on undeferrable control: %x\n", reg); | |
| ret = regmap_sdw_mbq_poll_busy(slave, reg, ctx); | |
| if (ret) | |
| return ret; | |
| ret = regmap_sdw_mbq_read_impl(slave, reg, val, mbq_size); | |
| } | |
| return ret; | |
| } | |
| static const struct regmap_bus regmap_sdw_mbq = { | |
| .reg_read = regmap_sdw_mbq_read, | |
| .reg_write = regmap_sdw_mbq_write, | |
| .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, | |
| .val_format_endian_default = REGMAP_ENDIAN_LITTLE, | |
| }; | |
| static int regmap_sdw_mbq_config_check(const struct regmap_config *config) | |
| { | |
| if (config->val_bits > (sizeof(unsigned int) * BITS_PER_BYTE)) | |
| return -ENOTSUPP; | |
| /* Registers are 32 bits wide */ | |
| if (config->reg_bits != 32) | |
| return -ENOTSUPP; | |
| if (config->pad_bits != 0) | |
| return -ENOTSUPP; | |
| return 0; | |
| } | |
| static struct regmap_mbq_context * | |
| regmap_sdw_mbq_gen_context(struct device *dev, | |
| struct sdw_slave *sdw, | |
| const struct regmap_config *config, | |
| const struct regmap_sdw_mbq_cfg *mbq_config) | |
| { | |
| struct regmap_mbq_context *ctx; | |
| ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); | |
| if (!ctx) | |
| return ERR_PTR(-ENOMEM); | |
| ctx->dev = dev; | |
| ctx->sdw = sdw; | |
| if (mbq_config) | |
| ctx->cfg = *mbq_config; | |
| ctx->val_size = config->val_bits / BITS_PER_BYTE; | |
| ctx->readable_reg = config->readable_reg; | |
| return ctx; | |
| } | |
| struct regmap *__regmap_init_sdw_mbq(struct device *dev, struct sdw_slave *sdw, | |
| const struct regmap_config *config, | |
| const struct regmap_sdw_mbq_cfg *mbq_config, | |
| struct lock_class_key *lock_key, | |
| const char *lock_name) | |
| { | |
| struct regmap_mbq_context *ctx; | |
| int ret; | |
| ret = regmap_sdw_mbq_config_check(config); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| ctx = regmap_sdw_mbq_gen_context(dev, sdw, config, mbq_config); | |
| if (IS_ERR(ctx)) | |
| return ERR_CAST(ctx); | |
| return __regmap_init(dev, ®map_sdw_mbq, ctx, | |
| config, lock_key, lock_name); | |
| } | |
| EXPORT_SYMBOL_GPL(__regmap_init_sdw_mbq); | |
| struct regmap *__devm_regmap_init_sdw_mbq(struct device *dev, struct sdw_slave *sdw, | |
| const struct regmap_config *config, | |
| const struct regmap_sdw_mbq_cfg *mbq_config, | |
| struct lock_class_key *lock_key, | |
| const char *lock_name) | |
| { | |
| struct regmap_mbq_context *ctx; | |
| int ret; | |
| ret = regmap_sdw_mbq_config_check(config); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| ctx = regmap_sdw_mbq_gen_context(dev, sdw, config, mbq_config); | |
| if (IS_ERR(ctx)) | |
| return ERR_CAST(ctx); | |
| return __devm_regmap_init(dev, ®map_sdw_mbq, ctx, | |
| config, lock_key, lock_name); | |
| } | |
| EXPORT_SYMBOL_GPL(__devm_regmap_init_sdw_mbq); | |
| MODULE_DESCRIPTION("regmap SoundWire MBQ Module"); | |
| MODULE_LICENSE("GPL"); |