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 */
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)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 */
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)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 */
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)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_offset + msg_len > buf_len) {
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 */
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)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_offset + msg_len > buf_len) {
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
sdca_ump_cancel_timeout(struct delayed_work * work)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
sdca_ump_schedule_timeout(struct delayed_work * work,unsigned int timeout_us)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_dfl_wq, work, usecs_to_jiffies(timeout_us));
261 }
262 EXPORT_SYMBOL_NS_GPL(sdca_ump_schedule_timeout, "SND_SOC_SDCA");
263