xref: /linux/sound/soc/sdca/sdca_ump.c (revision 84318277d6334c6981ab326d4acc87c6a6ddc9b8)
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