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