Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/soc/sdca/sdca_ump.c
38189 views
1
// SPDX-License-Identifier: GPL-2.0
2
// Copyright (C) 2025 Cirrus Logic, Inc. and
3
// Cirrus Logic International Semiconductor Ltd.
4
5
/*
6
* The MIPI SDCA specification is available for public downloads at
7
* https://www.mipi.org/mipi-sdca-v1-0-download
8
*/
9
10
#include <linux/dev_printk.h>
11
#include <linux/device.h>
12
#include <linux/regmap.h>
13
#include <sound/sdca.h>
14
#include <sound/sdca_function.h>
15
#include <sound/sdca_ump.h>
16
#include <sound/soc-component.h>
17
#include <linux/soundwire/sdw_registers.h>
18
19
/**
20
* sdca_ump_get_owner_host - check a UMP buffer is owned by the host
21
* @dev: Pointer to the struct device used for error messages.
22
* @function_regmap: Pointer to the regmap for the SDCA Function.
23
* @function: Pointer to the Function information.
24
* @entity: Pointer to the SDCA Entity.
25
* @control: Pointer to the SDCA Control for the UMP Owner.
26
*
27
* Return: Returns zero on success, and a negative error code on failure.
28
*/
29
int sdca_ump_get_owner_host(struct device *dev,
30
struct regmap *function_regmap,
31
struct sdca_function_data *function,
32
struct sdca_entity *entity,
33
struct sdca_control *control)
34
{
35
unsigned int reg, owner;
36
int ret;
37
38
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0);
39
ret = regmap_read(function_regmap, reg, &owner);
40
if (ret < 0) {
41
dev_err(dev, "%s: failed to read UMP owner: %d\n",
42
entity->label, ret);
43
return ret;
44
}
45
46
if (owner != SDCA_UMP_OWNER_HOST) {
47
dev_err(dev, "%s: host is not the UMP owner\n", entity->label);
48
return -EINVAL;
49
}
50
51
return 0;
52
}
53
EXPORT_SYMBOL_NS_GPL(sdca_ump_get_owner_host, "SND_SOC_SDCA");
54
55
/**
56
* sdca_ump_set_owner_device - set a UMP buffer's ownership back to the device
57
* @dev: Pointer to the struct device used for error messages.
58
* @function_regmap: Pointer to the regmap for the SDCA Function.
59
* @function: Pointer to the Function information.
60
* @entity: Pointer to the SDCA Entity.
61
* @control: Pointer to the SDCA Control for the UMP Owner.
62
*
63
* Return: Returns zero on success, and a negative error code on failure.
64
*/
65
int sdca_ump_set_owner_device(struct device *dev,
66
struct regmap *function_regmap,
67
struct sdca_function_data *function,
68
struct sdca_entity *entity,
69
struct sdca_control *control)
70
{
71
unsigned int reg;
72
int ret;
73
74
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0);
75
ret = regmap_write(function_regmap, reg, SDCA_UMP_OWNER_DEVICE);
76
if (ret < 0)
77
dev_err(dev, "%s: failed to write UMP owner: %d\n",
78
entity->label, ret);
79
80
return ret;
81
}
82
EXPORT_SYMBOL_NS_GPL(sdca_ump_set_owner_device, "SND_SOC_SDCA");
83
84
/**
85
* sdca_ump_read_message - read a UMP message from the device
86
* @dev: Pointer to the struct device used for error messages.
87
* @device_regmap: Pointer to the Device register map.
88
* @function_regmap: Pointer to the regmap for the SDCA Function.
89
* @function: Pointer to the Function information.
90
* @entity: Pointer to the SDCA Entity.
91
* @offset_sel: Control Selector for the UMP Offset Control.
92
* @length_sel: Control Selector for the UMP Length Control.
93
* @msg: Pointer that will be populated with an dynamically buffer
94
* containing the UMP message. Note this needs to be freed by the
95
* caller.
96
*
97
* The caller should first call sdca_ump_get_owner_host() to ensure the host
98
* currently owns the UMP buffer, and then this function can be used to
99
* retrieve a message. It is the callers responsibility to free the
100
* message once it is finished with it. Finally sdca_ump_set_owner_device()
101
* should be called to return the buffer to the device.
102
*
103
* Return: Returns the message length on success, and a negative error
104
* code on failure.
105
*/
106
int sdca_ump_read_message(struct device *dev,
107
struct regmap *device_regmap,
108
struct regmap *function_regmap,
109
struct sdca_function_data *function,
110
struct sdca_entity *entity,
111
unsigned int offset_sel, unsigned int length_sel,
112
void **msg)
113
{
114
struct sdca_control_range *range;
115
unsigned int msg_offset, msg_len;
116
unsigned int buf_addr, buf_len;
117
unsigned int reg;
118
int ret;
119
120
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, offset_sel, 0);
121
ret = regmap_read(function_regmap, reg, &msg_offset);
122
if (ret < 0) {
123
dev_err(dev, "%s: failed to read UMP offset: %d\n",
124
entity->label, ret);
125
return ret;
126
}
127
128
range = sdca_selector_find_range(dev, entity, offset_sel,
129
SDCA_MESSAGEOFFSET_NCOLS, 1);
130
if (!range)
131
return -ENOENT;
132
133
buf_addr = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_START_ADDRESS, 0);
134
buf_len = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_LENGTH, 0);
135
136
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, length_sel, 0);
137
ret = regmap_read(function_regmap, reg, &msg_len);
138
if (ret < 0) {
139
dev_err(dev, "%s: failed to read UMP length: %d\n",
140
entity->label, ret);
141
return ret;
142
}
143
144
if (msg_len > buf_len - msg_offset) {
145
dev_err(dev, "%s: message too big for UMP buffer: %d\n",
146
entity->label, msg_len);
147
return -EINVAL;
148
}
149
150
*msg = kmalloc(msg_len, GFP_KERNEL);
151
if (!*msg)
152
return -ENOMEM;
153
154
ret = regmap_raw_read(device_regmap, buf_addr + msg_offset, *msg, msg_len);
155
if (ret < 0) {
156
dev_err(dev, "%s: failed to read UMP message: %d\n",
157
entity->label, ret);
158
return ret;
159
}
160
161
return msg_len;
162
}
163
EXPORT_SYMBOL_NS_GPL(sdca_ump_read_message, "SND_SOC_SDCA");
164
165
/**
166
* sdca_ump_write_message - write a UMP message to the device
167
* @dev: Pointer to the struct device used for error messages.
168
* @device_regmap: Pointer to the Device register map.
169
* @function_regmap: Pointer to the regmap for the SDCA Function.
170
* @function: Pointer to the Function information.
171
* @entity: Pointer to the SDCA Entity.
172
* @offset_sel: Control Selector for the UMP Offset Control.
173
* @msg_offset: Offset within the UMP buffer at which the message should
174
* be written.
175
* @length_sel: Control Selector for the UMP Length Control.
176
* @msg: Pointer to the data that should be written to the UMP buffer.
177
* @msg_len: Length of the message data in bytes.
178
*
179
* The caller should first call sdca_ump_get_owner_host() to ensure the host
180
* currently owns the UMP buffer, and then this function can be used to
181
* write a message. Finally sdca_ump_set_owner_device() should be called to
182
* return the buffer to the device, allowing the device to access the
183
* message.
184
*
185
* Return: Returns zero on success, and a negative error code on failure.
186
*/
187
int sdca_ump_write_message(struct device *dev,
188
struct regmap *device_regmap,
189
struct regmap *function_regmap,
190
struct sdca_function_data *function,
191
struct sdca_entity *entity,
192
unsigned int offset_sel, unsigned int msg_offset,
193
unsigned int length_sel,
194
void *msg, int msg_len)
195
{
196
struct sdca_control_range *range;
197
unsigned int buf_addr, buf_len, ump_mode;
198
unsigned int reg;
199
int ret;
200
201
range = sdca_selector_find_range(dev, entity, offset_sel,
202
SDCA_MESSAGEOFFSET_NCOLS, 1);
203
if (!range)
204
return -ENOENT;
205
206
buf_addr = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_START_ADDRESS, 0);
207
buf_len = sdca_range(range, SDCA_MESSAGEOFFSET_BUFFER_LENGTH, 0);
208
ump_mode = sdca_range(range, SDCA_MESSAGEOFFSET_UMP_MODE, 0);
209
210
if (msg_len > buf_len - msg_offset) {
211
dev_err(dev, "%s: message too big for UMP buffer: %d\n",
212
entity->label, msg_len);
213
return -EINVAL;
214
}
215
216
if (ump_mode != SDCA_UMP_MODE_DIRECT) {
217
dev_err(dev, "%s: only direct mode currently supported\n",
218
entity->label);
219
return -EINVAL;
220
}
221
222
ret = regmap_raw_write(device_regmap, buf_addr + msg_offset, msg, msg_len);
223
if (ret) {
224
dev_err(dev, "%s: failed to write UMP message: %d\n",
225
entity->label, ret);
226
return ret;
227
}
228
229
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, offset_sel, 0);
230
ret = regmap_write(function_regmap, reg, msg_offset);
231
if (ret < 0) {
232
dev_err(dev, "%s: failed to write UMP offset: %d\n",
233
entity->label, ret);
234
return ret;
235
}
236
237
reg = SDW_SDCA_CTL(function->desc->adr, entity->id, length_sel, 0);
238
ret = regmap_write(function_regmap, reg, msg_len);
239
if (ret < 0) {
240
dev_err(dev, "%s: failed to write UMP length: %d\n",
241
entity->label, ret);
242
return ret;
243
}
244
245
return 0;
246
}
247
EXPORT_SYMBOL_NS_GPL(sdca_ump_write_message, "SND_SOC_SDCA");
248
249
void sdca_ump_cancel_timeout(struct delayed_work *work)
250
{
251
cancel_delayed_work_sync(work);
252
}
253
EXPORT_SYMBOL_NS_GPL(sdca_ump_cancel_timeout, "SND_SOC_SDCA");
254
255
void sdca_ump_schedule_timeout(struct delayed_work *work, unsigned int timeout_us)
256
{
257
if (!timeout_us)
258
return;
259
260
queue_delayed_work(system_wq, work, usecs_to_jiffies(timeout_us));
261
}
262
EXPORT_SYMBOL_NS_GPL(sdca_ump_schedule_timeout, "SND_SOC_SDCA");
263
264