root/drivers/base/regmap/regmap-sdw-mbq.c
// 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, &regmap_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, &regmap_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");