| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- // 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(dev, reg)) {
- ret = read_poll_timeout(sdw_read_no_pm, val,
- val < 0 || !(val & SDCA_CTL_ENTITY_0_FUNCTION_BUSY),
- ctx->cfg.timeout_us, ctx->cfg.retry_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, bool deferrable)
- {
- 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;
- }
- ret = sdw_write_no_pm(slave, reg, val & 0xff);
- if (deferrable && ret == -ENODATA)
- return -EAGAIN;
- return ret;
- }
- 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;
- 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, deferrable);
- if (ret == -EAGAIN) {
- ret = regmap_sdw_mbq_poll_busy(slave, reg, ctx);
- if (ret)
- return ret;
- ret = regmap_sdw_mbq_write_impl(slave, reg, val, mbq_size, false);
- }
- return ret;
- }
- static int regmap_sdw_mbq_read_impl(struct sdw_slave *slave,
- unsigned int reg, unsigned int *val,
- int mbq_size, bool deferrable)
- {
- int shift = BITS_PER_BYTE;
- int read;
- read = sdw_read_no_pm(slave, reg);
- if (read < 0) {
- if (deferrable && read == -ENODATA)
- return -EAGAIN;
- 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;
- 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, deferrable);
- if (ret == -EAGAIN) {
- ret = regmap_sdw_mbq_poll_busy(slave, reg, ctx);
- if (ret)
- return ret;
- ret = regmap_sdw_mbq_read_impl(slave, reg, val, mbq_size, false);
- }
- 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");
|