Link: https://lore.kernel.org/r/20260217200002.683975158@linuxfoundation.org Tested-by: Florian Fainelli <florian.fainelli@broadcom.com> Tested-by: Takeshi Ogasawara <takeshi.ogasawara@futuring-girl.com> Tested-by: Peter Schneider <pschneider1968@googlemail.com> Tested-by: Jon Hunter <jonathanh@nvidia.com> Tested-by: Salvatore Bonaccorso <carnil@debian.org> Tested-by: Brett A C Sheffield <bacs@librecast.net> Tested-by: Mark Brown <broonie@kernel.org> Tested-by: Luna Jernberg <droidbittin@gmail.com> Tested-by: Ronald Warsow <rwarsow@gmx.de> Tested-by: Justin M. Forbes <jforbes@fedoraproject.org> Tested-by: Ron Economos <re@w6rz.net> Tested-by: Miguel Ojeda <ojeda@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
263 lines
8.4 KiB
C
263 lines
8.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (C) 2025 Cirrus Logic, Inc. and
|
|
// Cirrus Logic International Semiconductor Ltd.
|
|
|
|
/*
|
|
* The MIPI SDCA specification is available for public downloads at
|
|
* https://www.mipi.org/mipi-sdca-v1-0-download
|
|
*/
|
|
|
|
#include <linux/dev_printk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/regmap.h>
|
|
#include <sound/sdca.h>
|
|
#include <sound/sdca_function.h>
|
|
#include <sound/sdca_ump.h>
|
|
#include <sound/soc-component.h>
|
|
#include <linux/soundwire/sdw_registers.h>
|
|
|
|
/**
|
|
* sdca_ump_get_owner_host - check a UMP buffer is owned by the host
|
|
* @dev: Pointer to the struct device used for error messages.
|
|
* @function_regmap: Pointer to the regmap for the SDCA Function.
|
|
* @function: Pointer to the Function information.
|
|
* @entity: Pointer to the SDCA Entity.
|
|
* @control: Pointer to the SDCA Control for the UMP Owner.
|
|
*
|
|
* Return: Returns zero on success, and a negative error code on failure.
|
|
*/
|
|
int sdca_ump_get_owner_host(struct device *dev,
|
|
struct regmap *function_regmap,
|
|
struct sdca_function_data *function,
|
|
struct sdca_entity *entity,
|
|
struct sdca_control *control)
|
|
{
|
|
unsigned int reg, owner;
|
|
int ret;
|
|
|
|
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0);
|
|
ret = regmap_read(function_regmap, reg, &owner);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: failed to read UMP owner: %d\n",
|
|
entity->label, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (owner != SDCA_UMP_OWNER_HOST) {
|
|
dev_err(dev, "%s: host is not the UMP owner\n", entity->label);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(sdca_ump_get_owner_host, "SND_SOC_SDCA");
|
|
|
|
/**
|
|
* sdca_ump_set_owner_device - set a UMP buffer's ownership back to the device
|
|
* @dev: Pointer to the struct device used for error messages.
|
|
* @function_regmap: Pointer to the regmap for the SDCA Function.
|
|
* @function: Pointer to the Function information.
|
|
* @entity: Pointer to the SDCA Entity.
|
|
* @control: Pointer to the SDCA Control for the UMP Owner.
|
|
*
|
|
* Return: Returns zero on success, and a negative error code on failure.
|
|
*/
|
|
int sdca_ump_set_owner_device(struct device *dev,
|
|
struct regmap *function_regmap,
|
|
struct sdca_function_data *function,
|
|
struct sdca_entity *entity,
|
|
struct sdca_control *control)
|
|
{
|
|
unsigned int reg;
|
|
int ret;
|
|
|
|
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0);
|
|
ret = regmap_write(function_regmap, reg, SDCA_UMP_OWNER_DEVICE);
|
|
if (ret < 0)
|
|
dev_err(dev, "%s: failed to write UMP owner: %d\n",
|
|
entity->label, ret);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(sdca_ump_set_owner_device, "SND_SOC_SDCA");
|
|
|
|
/**
|
|
* sdca_ump_read_message - read a UMP message from the device
|
|
* @dev: Pointer to the struct device used for error messages.
|
|
* @device_regmap: Pointer to the Device register map.
|
|
* @function_regmap: Pointer to the regmap for the SDCA Function.
|
|
* @function: Pointer to the Function information.
|
|
* @entity: Pointer to the SDCA Entity.
|
|
* @offset_sel: Control Selector for the UMP Offset Control.
|
|
* @length_sel: Control Selector for the UMP Length Control.
|
|
* @msg: Pointer that will be populated with an dynamically buffer
|
|
* containing the UMP message. Note this needs to be freed by the
|
|
* caller.
|
|
*
|
|
* The caller should first call sdca_ump_get_owner_host() to ensure the host
|
|
* currently owns the UMP buffer, and then this function can be used to
|
|
* retrieve a message. It is the callers responsibility to free the
|
|
* message once it is finished with it. Finally sdca_ump_set_owner_device()
|
|
* should be called to return the buffer to the device.
|
|
*
|
|
* Return: Returns the message length on success, and a negative error
|
|
* code on failure.
|
|
*/
|
|
int sdca_ump_read_message(struct device *dev,
|
|
struct regmap *device_regmap,
|
|
struct regmap *function_regmap,
|
|
struct sdca_function_data *function,
|
|
struct sdca_entity *entity,
|
|
unsigned int offset_sel, unsigned int length_sel,
|
|
void **msg)
|
|
{
|
|
struct sdca_control_range *range;
|
|
unsigned int msg_offset, msg_len;
|
|
unsigned int buf_addr, buf_len;
|
|
unsigned int reg;
|
|
int ret;
|
|
|
|
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, offset_sel, 0);
|
|
ret = regmap_read(function_regmap, reg, &msg_offset);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: failed to read UMP offset: %d\n",
|
|
entity->label, ret);
|
|
return ret;
|
|
}
|
|
|
|
range = sdca_selector_find_range(dev, entity, offset_sel,
|
|
SDCA_MESSAGEOFFSET_NCOLS, 1);
|
|
if (!range)
|
|
return -ENOENT;
|
|
|
|
buf_addr = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_START_ADDRESS, 0);
|
|
buf_len = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_LENGTH, 0);
|
|
|
|
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, length_sel, 0);
|
|
ret = regmap_read(function_regmap, reg, &msg_len);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: failed to read UMP length: %d\n",
|
|
entity->label, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (msg_len > buf_len - msg_offset) {
|
|
dev_err(dev, "%s: message too big for UMP buffer: %d\n",
|
|
entity->label, msg_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*msg = kmalloc(msg_len, GFP_KERNEL);
|
|
if (!*msg)
|
|
return -ENOMEM;
|
|
|
|
ret = regmap_raw_read(device_regmap, buf_addr + msg_offset, *msg, msg_len);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: failed to read UMP message: %d\n",
|
|
entity->label, ret);
|
|
return ret;
|
|
}
|
|
|
|
return msg_len;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(sdca_ump_read_message, "SND_SOC_SDCA");
|
|
|
|
/**
|
|
* sdca_ump_write_message - write a UMP message to the device
|
|
* @dev: Pointer to the struct device used for error messages.
|
|
* @device_regmap: Pointer to the Device register map.
|
|
* @function_regmap: Pointer to the regmap for the SDCA Function.
|
|
* @function: Pointer to the Function information.
|
|
* @entity: Pointer to the SDCA Entity.
|
|
* @offset_sel: Control Selector for the UMP Offset Control.
|
|
* @msg_offset: Offset within the UMP buffer at which the message should
|
|
* be written.
|
|
* @length_sel: Control Selector for the UMP Length Control.
|
|
* @msg: Pointer to the data that should be written to the UMP buffer.
|
|
* @msg_len: Length of the message data in bytes.
|
|
*
|
|
* The caller should first call sdca_ump_get_owner_host() to ensure the host
|
|
* currently owns the UMP buffer, and then this function can be used to
|
|
* write a message. Finally sdca_ump_set_owner_device() should be called to
|
|
* return the buffer to the device, allowing the device to access the
|
|
* message.
|
|
*
|
|
* Return: Returns zero on success, and a negative error code on failure.
|
|
*/
|
|
int sdca_ump_write_message(struct device *dev,
|
|
struct regmap *device_regmap,
|
|
struct regmap *function_regmap,
|
|
struct sdca_function_data *function,
|
|
struct sdca_entity *entity,
|
|
unsigned int offset_sel, unsigned int msg_offset,
|
|
unsigned int length_sel,
|
|
void *msg, int msg_len)
|
|
{
|
|
struct sdca_control_range *range;
|
|
unsigned int buf_addr, buf_len, ump_mode;
|
|
unsigned int reg;
|
|
int ret;
|
|
|
|
range = sdca_selector_find_range(dev, entity, offset_sel,
|
|
SDCA_MESSAGEOFFSET_NCOLS, 1);
|
|
if (!range)
|
|
return -ENOENT;
|
|
|
|
buf_addr = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_START_ADDRESS, 0);
|
|
buf_len = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_LENGTH, 0);
|
|
ump_mode = sdca_range(range, SDCA_MESSAGEOFFSET_UMP_MODE, 0);
|
|
|
|
if (msg_len > buf_len - msg_offset) {
|
|
dev_err(dev, "%s: message too big for UMP buffer: %d\n",
|
|
entity->label, msg_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ump_mode != SDCA_UMP_MODE_DIRECT) {
|
|
dev_err(dev, "%s: only direct mode currently supported\n",
|
|
entity->label);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = regmap_raw_write(device_regmap, buf_addr + msg_offset, msg, msg_len);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to write UMP message: %d\n",
|
|
entity->label, ret);
|
|
return ret;
|
|
}
|
|
|
|
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, offset_sel, 0);
|
|
ret = regmap_write(function_regmap, reg, msg_offset);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: failed to write UMP offset: %d\n",
|
|
entity->label, ret);
|
|
return ret;
|
|
}
|
|
|
|
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, length_sel, 0);
|
|
ret = regmap_write(function_regmap, reg, msg_len);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: failed to write UMP length: %d\n",
|
|
entity->label, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(sdca_ump_write_message, "SND_SOC_SDCA");
|
|
|
|
void sdca_ump_cancel_timeout(struct delayed_work *work)
|
|
{
|
|
cancel_delayed_work_sync(work);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(sdca_ump_cancel_timeout, "SND_SOC_SDCA");
|
|
|
|
void sdca_ump_schedule_timeout(struct delayed_work *work, unsigned int timeout_us)
|
|
{
|
|
if (!timeout_us)
|
|
return;
|
|
|
|
queue_delayed_work(system_wq, work, usecs_to_jiffies(timeout_us));
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(sdca_ump_schedule_timeout, "SND_SOC_SDCA");
|