File Index
Files and directories
Sign in
FILE

sdca_ump.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/sdca/sdca_ump.c C
  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 */
 29int 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}
 53EXPORT_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 */
 65int 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}
 82EXPORT_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 */
106int 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}
163EXPORT_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 */
187int 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}
247EXPORT_SYMBOL_NS_GPL(sdca_ump_write_message, "SND_SOC_SDCA");
248
249void sdca_ump_cancel_timeout(struct delayed_work *work)
250{
251	cancel_delayed_work_sync(work);
252}
253EXPORT_SYMBOL_NS_GPL(sdca_ump_cancel_timeout, "SND_SOC_SDCA");
254
255void 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}
262EXPORT_SYMBOL_NS_GPL(sdca_ump_schedule_timeout, "SND_SOC_SDCA");
~/ Read-only file preview.