regmap: sdw-mbq: Add support for further MBQ register sizes

SoundWire MBQ register maps typically contain a variety of register
sizes, which doesn't map ideally to the regmap abstraction which
expects register maps to have a consistent size. Currently the MBQ
register map only allows 16-bit registers to be defined, however
this leads to complex CODEC driver implementations with an 8-bit
register map and a 16-bit MBQ, every control will then have a custom
get and put handler that allows them to access different register
maps. Further more 32-bit MBQ quantities are not currently supported.

Add support for additional MBQ sizes and to avoid the complexity
of multiple register maps treat the val_size as a maximum size for
the register map. Within the regmap use an ancillary callback to
determine how many bytes to actually read/write to the hardware for
a specific register. In the case that no callback is defined the
behaviour defaults back to the existing behaviour of a fixed size
register map.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Link: https://patch.msgid.link/20250107154408.814455-4-ckeepax@opensource.cirrus.com
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.dev>
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Charles Keepax
2025-01-07 15:44:05 +00:00
committed by Mark Brown
parent b21468e83b
commit fdd9ef3dce
2 changed files with 139 additions and 22 deletions

View File

@@ -1,6 +1,7 @@
// 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/module.h>
@@ -9,35 +10,77 @@
#include <linux/soundwire/sdw_registers.h>
#include "internal.h"
struct regmap_mbq_context {
struct device *dev;
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 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);
int mbq_size = regmap_sdw_mbq_size(ctx, reg);
int shift = mbq_size * BITS_PER_BYTE;
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;
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_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;
int mbq_size = regmap_sdw_mbq_size(ctx, reg);
int shift = BITS_PER_BYTE;
int read;
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;
read = sdw_read_no_pm(slave, reg);
if (read < 0)
return read;
*val = (read1 << 8) | read0;
*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;
}
@@ -51,8 +94,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 */
@@ -65,35 +107,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);

View File

@@ -506,6 +506,17 @@ 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.
*
* Provides additional configuration required for SoundWire MBQ register maps.
*/
struct regmap_sdw_mbq_cfg {
int (*mbq_size)(struct device *dev, unsigned int reg);
};
struct regmap_async;
typedef int (*regmap_hw_write)(void *context, const void *data,
@@ -652,6 +663,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,
@@ -713,6 +725,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,
@@ -942,7 +955,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
@@ -1155,7 +1183,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