#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>
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");
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");
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");
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");