Skip to content

Commit

Permalink
regmap: sdw-mbq: Add support for SDCA deferred controls
Browse files Browse the repository at this point in the history
The SDCA specification allows for controls to be deferred. In the case
of a deferred control the device will return COMMAND_IGNORED to the
8-bit operation that would cause the value to commit. Which is the
final 8-bits on a write, or the first 8-bits on a read. In the case of
receiving a defer, the regmap will poll the SDCA function busy bit,
after which the transaction will be retried, returning an error if the
function busy does not clear within a chip specific timeout. Since
this is common SDCA functionality which is the 99% use-case for MBQs
it makes sense to incorporate this functionality into the register
map. If no MBQ configuration is specified, the behaviour will default
to the existing behaviour.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
  • Loading branch information
charleskeepax committed Jul 23, 2024
1 parent af61fe0 commit 3acc68e
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 8 deletions.
96 changes: 88 additions & 8 deletions drivers/base/regmap/regmap-sdw-mbq.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#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>
Expand Down Expand Up @@ -31,12 +32,40 @@ static int regmap_sdw_mbq_size(struct regmap_mbq_context *ctx, unsigned int reg)
return size;
}

static int regmap_sdw_mbq_write(void *context, unsigned int reg, unsigned int val)
static int regmap_sdw_mbq_deferrable(struct regmap_mbq_context *ctx, unsigned int reg)
{
struct regmap_mbq_context *ctx = context;
struct device *dev = ctx->dev;
struct sdw_slave *slave = dev_to_sdw_dev(dev);
int mbq_size = regmap_sdw_mbq_size(ctx, reg);
if (ctx->cfg.deferrable)
return ctx->cfg.deferrable(ctx->dev, reg);

return 0;
}

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);
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, int deferrable)
{
struct device *dev = &slave->dev;
int ret;

switch (mbq_size) {
Expand All @@ -58,24 +87,53 @@ static int regmap_sdw_mbq_write(void *context, unsigned int reg, unsigned int va
return ret;
fallthrough;
case 1:
return sdw_write_no_pm(slave, reg, val & 0xff);
ret = sdw_write_no_pm(slave, reg, val & 0xff);
if (deferrable && ret == -ENODATA)
return -EAGAIN;

return ret;
default:
dev_err(dev, "Invalid MBQ size for 0x%x: %d\n", reg, mbq_size);
return -EINVAL;
}
}

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

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, int deferrable)
{
struct device *dev = &slave->dev;
int read0, read1 = 0, read2 = 0, read3 = 0;

read0 = sdw_read_no_pm(slave, reg);
if (read0 < 0)
if (read0 < 0) {
if (deferrable && read0 == -ENODATA)
return -EAGAIN;

return read0;
}

if (mbq_size != 1) {
read1 = sdw_read_no_pm(slave, SDW_SDCA_MBQ_CTL(reg));
Expand Down Expand Up @@ -103,6 +161,28 @@ static int regmap_sdw_mbq_read(void *context, unsigned int reg, unsigned int *va
return 0;
}

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

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;

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,
Expand Down
3 changes: 3 additions & 0 deletions include/linux/regmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,9 @@ struct regmap_range_cfg {

struct regmap_sdw_mbq_cfg {
int (*mbq_size)(struct device *dev, unsigned int reg);
int (*deferrable)(struct device *dev, unsigned int reg);
unsigned long timeout_us;
unsigned long retry_us;
};

struct regmap_async;
Expand Down

0 comments on commit 3acc68e

Please sign in to comment.