Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Update MBQ regmap to handle more sizes and defers #5087

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 188 additions & 22 deletions drivers/base/regmap/regmap-sdw-mbq.c
Original file line number Diff line number Diff line change
@@ -1,45 +1,180 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright(c) 2020 Intel Corporation.

#include <linux/bits.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 "internal.h"

struct regmap_mbq_context {
struct device *dev;

struct regmap_sdw_mbq_cfg cfg;
plbossart marked this conversation as resolved.
Show resolved Hide resolved

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 > 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,
unsigned long timeout_us, unsigned long retry_us)
{
struct device *dev = &slave->dev;
int val, ret;

dev_dbg(dev, "Deferring transaction for 0x%x\n", reg);

reg = SDW_SDCA_CTL(SDW_SDCA_CTL_FUNC(reg), 0, SDW_SDCA_FUNCTION_STATUS, 0);

ret = read_poll_timeout(sdw_read_no_pm, val,
val < 0 || !(val & SDW_SDCA_FUNCTION_BUSY),
timeout_us, retry_us, false, slave, reg);
plbossart marked this conversation as resolved.
Show resolved Hide resolved
plbossart marked this conversation as resolved.
Show resolved Hide resolved
if (val < 0)
return val;
if (ret)
dev_err(dev, "Timed out polling function busy 0x%x: %d\n", reg, val);

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 device *dev = context;
struct regmap_mbq_context *ctx = context;
struct device *dev = ctx->dev;
struct sdw_slave *slave = dev_to_sdw_dev(dev);
bool deferrable = regmap_sdw_mbq_deferrable(ctx, reg);
int mbq_size = regmap_sdw_mbq_size(ctx, reg);
int ret;

ret = sdw_write_no_pm(slave, SDW_SDCA_MBQ_CTL(reg), (val >> 8) & 0xff);
if (ret < 0)
return 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->cfg.timeout_us, ctx->cfg.retry_us);
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;

return sdw_write_no_pm(slave, reg, val & 0xff);
*val |= read << shift;
shift += BITS_PER_BYTE;
}

return 0;
}

static int regmap_sdw_mbq_read(void *context, unsigned int reg, unsigned int *val)
{
struct device *dev = context;
struct regmap_mbq_context *ctx = context;
struct device *dev = ctx->dev;
struct sdw_slave *slave = dev_to_sdw_dev(dev);
int read0;
int read1;
bool deferrable = regmap_sdw_mbq_deferrable(ctx, reg);
int mbq_size = regmap_sdw_mbq_size(ctx, reg);
int ret;

read0 = sdw_read_no_pm(slave, reg);
if (read0 < 0)
return read0;
if (mbq_size < 0)
return mbq_size;

read1 = sdw_read_no_pm(slave, SDW_SDCA_MBQ_CTL(reg));
if (read1 < 0)
return read1;
/*
* 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->cfg.timeout_us, ctx->cfg.retry_us);
if (ret)
return ret;

*val = (read1 << 8) | read0;
ret = regmap_sdw_mbq_read_impl(slave, reg, val, mbq_size, false);
}

return 0;
return ret;
}

static const struct regmap_bus regmap_sdw_mbq = {
Expand All @@ -51,8 +186,7 @@ static const struct regmap_bus regmap_sdw_mbq = {

static int regmap_sdw_mbq_config_check(const struct regmap_config *config)
{
/* MBQ-based controls are only 16-bits for now */
if (config->val_bits != 16)
if (config->val_bits > (sizeof(unsigned int) * BITS_PER_BYTE))
return -ENOTSUPP;

/* Registers are 32 bits wide */
Expand All @@ -65,35 +199,67 @@ static int regmap_sdw_mbq_config_check(const struct regmap_config *config)
return 0;
}

static struct regmap_mbq_context *
regmap_sdw_mbq_gen_context(struct device *dev,
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->val_size = config->val_bits / BITS_PER_BYTE;

if (mbq_config)
ctx->cfg = *mbq_config;

return ctx;
}

struct regmap *__regmap_init_sdw_mbq(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);

return __regmap_init(&sdw->dev, &regmap_sdw_mbq,
&sdw->dev, config, lock_key, lock_name);
ctx = regmap_sdw_mbq_gen_context(&sdw->dev, config, mbq_config);
if (IS_ERR(ctx))
return ERR_CAST(ctx);

return __regmap_init(&sdw->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 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);

return __devm_regmap_init(&sdw->dev, &regmap_sdw_mbq,
&sdw->dev, config, lock_key, lock_name);
ctx = regmap_sdw_mbq_gen_context(&sdw->dev, config, mbq_config);
if (IS_ERR(ctx))
return ERR_CAST(ctx);

return __devm_regmap_init(&sdw->dev, &regmap_sdw_mbq, ctx,
config, lock_key, lock_name);
}
EXPORT_SYMBOL_GPL(__devm_regmap_init_sdw_mbq);

Expand Down
62 changes: 60 additions & 2 deletions include/linux/regmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,32 @@ struct regmap_range_cfg {
unsigned int window_len;
};

/**
* struct regmap_sdw_mbq_cfg - Configuration for Multi-Byte Quantities
*
* @mbq_size: Callback returning the actual size of the given register.
* @deferrable: Callback returning true if the hardware can defer
* transactions to the given register. Deferral should
* only be used by SDCA parts and typically which controls
* are deferrable will be specified in either as a hard
* coded list or from the DisCo tables in the platform
* firmware.
*
* @timeout_us: The time in microseconds after which waiting for a deferred
* transaction should time out.
* @retry_us: The time in microseconds between polls of the function busy
* status whilst waiting for an opportunity to retry a deferred
* transaction.
*
* Provides additional configuration required for SoundWire MBQ register maps.
*/
struct regmap_sdw_mbq_cfg {
charleskeepax marked this conversation as resolved.
Show resolved Hide resolved
int (*mbq_size)(struct device *dev, unsigned int reg);
bool (*deferrable)(struct device *dev, unsigned int reg);
unsigned long timeout_us;
unsigned long retry_us;
};
plbossart marked this conversation as resolved.
Show resolved Hide resolved

struct regmap_async;

typedef int (*regmap_hw_write)(void *context, const void *data,
Expand Down Expand Up @@ -645,6 +671,7 @@ struct regmap *__regmap_init_sdw(struct sdw_slave *sdw,
const char *lock_name);
struct regmap *__regmap_init_sdw_mbq(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 *__regmap_init_spi_avmm(struct spi_device *spi,
Expand Down Expand Up @@ -706,6 +733,7 @@ struct regmap *__devm_regmap_init_sdw(struct sdw_slave *sdw,
const char *lock_name);
struct regmap *__devm_regmap_init_sdw_mbq(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 *__devm_regmap_init_slimbus(struct slim_device *slimbus,
Expand Down Expand Up @@ -935,7 +963,22 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
*/
#define regmap_init_sdw_mbq(sdw, config) \
__regmap_lockdep_wrapper(__regmap_init_sdw_mbq, #config, \
sdw, config)
sdw, config, NULL)

/**
* regmap_init_sdw_mbq_cfg() - Initialise MBQ SDW register map with config
*
* @sdw: Device that will be interacted with
* @config: Configuration for register map
* @mbq_config: Properties for the MBQ registers
*
* The return value will be an ERR_PTR() on error or a valid pointer
* to a struct regmap. The regmap will be automatically freed by the
* device management code.
*/
#define regmap_init_sdw_mbq_cfg(sdw, config, mbq_config) \
__regmap_lockdep_wrapper(__regmap_init_sdw_mbq, #config, \
sdw, config, mbq_config)

/**
* regmap_init_spi_avmm() - Initialize register map for Intel SPI Slave
Expand Down Expand Up @@ -1148,7 +1191,22 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
*/
#define devm_regmap_init_sdw_mbq(sdw, config) \
__regmap_lockdep_wrapper(__devm_regmap_init_sdw_mbq, #config, \
sdw, config)
sdw, config, NULL)

/**
* devm_regmap_init_sdw_mbq_cfg() - Initialise managed MBQ SDW register map with config
*
* @sdw: Device that will be interacted with
* @config: Configuration for register map
* @mbq_config: Properties for the MBQ registers
*
* The return value will be an ERR_PTR() on error or a valid pointer
* to a struct regmap. The regmap will be automatically freed by the
* device management code.
*/
#define devm_regmap_init_sdw_mbq_cfg(sdw, config, mbq_config) \
__regmap_lockdep_wrapper(__devm_regmap_init_sdw_mbq, \
#config, sdw, config, mbq_config)

/**
* devm_regmap_init_slimbus() - Initialise managed register map
Expand Down
Loading
Loading