1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // HDA DSP ALSA Control Driver 4 // 5 // Copyright 2022 Cirrus Logic, Inc. 6 // 7 // Author: Stefan Binding <sbinding@opensource.cirrus.com> 8 9 #include <linux/module.h> 10 #include <sound/soc.h> 11 #include <linux/firmware/cirrus/cs_dsp.h> 12 #include <linux/firmware/cirrus/wmfw.h> 13 #include "hda_cs_dsp_ctl.h" 14 15 #define ADSP_MAX_STD_CTRL_SIZE 512 16 17 struct hda_cs_dsp_coeff_ctl { 18 struct cs_dsp_coeff_ctl *cs_ctl; 19 struct snd_card *card; 20 struct snd_kcontrol *kctl; 21 }; 22 23 static const char * const hda_cs_dsp_fw_text[HDA_CS_DSP_NUM_FW] = { 24 [HDA_CS_DSP_FW_SPK_PROT] = "Prot", 25 [HDA_CS_DSP_FW_SPK_CALI] = "Cali", 26 [HDA_CS_DSP_FW_SPK_DIAG] = "Diag", 27 [HDA_CS_DSP_FW_MISC] = "Misc", 28 }; 29 30 const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW] = { 31 [HDA_CS_DSP_FW_SPK_PROT] = "spk-prot", 32 [HDA_CS_DSP_FW_SPK_CALI] = "spk-cali", 33 [HDA_CS_DSP_FW_SPK_DIAG] = "spk-diag", 34 [HDA_CS_DSP_FW_MISC] = "misc", 35 }; 36 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_fw_ids, SND_HDA_CS_DSP_CONTROLS); 37 38 static int hda_cs_dsp_coeff_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) 39 { 40 struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); 41 struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; 42 43 uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; 44 uinfo->count = cs_ctl->len; 45 46 return 0; 47 } 48 49 static int hda_cs_dsp_coeff_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) 50 { 51 struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); 52 struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; 53 char *p = ucontrol->value.bytes.data; 54 int ret = 0; 55 56 mutex_lock(&cs_ctl->dsp->pwr_lock); 57 ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, p, cs_ctl->len); 58 mutex_unlock(&cs_ctl->dsp->pwr_lock); 59 60 return ret; 61 } 62 63 static int hda_cs_dsp_coeff_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) 64 { 65 struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); 66 struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; 67 char *p = ucontrol->value.bytes.data; 68 int ret; 69 70 mutex_lock(&cs_ctl->dsp->pwr_lock); 71 ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, p, cs_ctl->len); 72 mutex_unlock(&cs_ctl->dsp->pwr_lock); 73 74 return ret; 75 } 76 77 static unsigned int wmfw_convert_flags(unsigned int in) 78 { 79 unsigned int out, rd, wr, vol; 80 81 rd = SNDRV_CTL_ELEM_ACCESS_READ; 82 wr = SNDRV_CTL_ELEM_ACCESS_WRITE; 83 vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; 84 85 out = 0; 86 87 if (in) { 88 out |= rd; 89 if (in & WMFW_CTL_FLAG_WRITEABLE) 90 out |= wr; 91 if (in & WMFW_CTL_FLAG_VOLATILE) 92 out |= vol; 93 } else { 94 out |= rd | wr | vol; 95 } 96 97 return out; 98 } 99 100 static void hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char *name) 101 { 102 struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; 103 struct snd_kcontrol_new kcontrol = {0}; 104 struct snd_kcontrol *kctl; 105 int ret = 0; 106 107 if (cs_ctl->len > ADSP_MAX_STD_CTRL_SIZE) { 108 dev_err(cs_ctl->dsp->dev, "KControl %s: length %zu exceeds maximum %d\n", name, 109 cs_ctl->len, ADSP_MAX_STD_CTRL_SIZE); 110 return; 111 } 112 113 kcontrol.name = name; 114 kcontrol.info = hda_cs_dsp_coeff_info; 115 kcontrol.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 116 kcontrol.access = wmfw_convert_flags(cs_ctl->flags); 117 kcontrol.get = hda_cs_dsp_coeff_get; 118 kcontrol.put = hda_cs_dsp_coeff_put; 119 120 /* Save ctl inside private_data, ctl is owned by cs_dsp, 121 * and will be freed when cs_dsp removes the control */ 122 kctl = snd_ctl_new1(&kcontrol, (void *)ctl); 123 if (!kctl) 124 return; 125 126 ret = snd_ctl_add(ctl->card, kctl); 127 if (ret) { 128 dev_err(cs_ctl->dsp->dev, "Failed to add KControl %s = %d\n", kcontrol.name, ret); 129 return; 130 } 131 132 dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol.name); 133 ctl->kctl = kctl; 134 } 135 136 static void hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, 137 const struct hda_cs_dsp_ctl_info *info) 138 { 139 struct cs_dsp *cs_dsp = cs_ctl->dsp; 140 char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; 141 struct hda_cs_dsp_coeff_ctl *ctl; 142 const char *region_name; 143 int ret; 144 145 region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type); 146 if (!region_name) { 147 dev_warn(cs_dsp->dev, "Unknown region type: %d\n", cs_ctl->alg_region.type); 148 return; 149 } 150 151 ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %.12s %x", info->device_name, 152 cs_dsp->name, hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg); 153 154 if (cs_ctl->subname) { 155 int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; 156 int skip = 0; 157 158 /* Truncate the subname from the start if it is too long */ 159 if (cs_ctl->subname_len > avail) 160 skip = cs_ctl->subname_len - avail; 161 162 snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, 163 " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); 164 } 165 166 ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); 167 if (!ctl) 168 return; 169 170 ctl->cs_ctl = cs_ctl; 171 ctl->card = info->card; 172 cs_ctl->priv = ctl; 173 174 hda_cs_dsp_add_kcontrol(ctl, name); 175 } 176 177 void hda_cs_dsp_add_controls(struct cs_dsp *dsp, const struct hda_cs_dsp_ctl_info *info) 178 { 179 struct cs_dsp_coeff_ctl *cs_ctl; 180 181 /* 182 * pwr_lock would cause mutex inversion with ALSA control lock compared 183 * to the get/put functions. 184 * It is safe to walk the list without holding a mutex because entries 185 * are persistent and only cs_dsp_power_up() or cs_dsp_remove() can 186 * change the list. 187 */ 188 lockdep_assert_not_held(&dsp->pwr_lock); 189 190 list_for_each_entry(cs_ctl, &dsp->ctl_list, list) { 191 if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) 192 continue; 193 194 if (cs_ctl->priv) 195 continue; 196 197 hda_cs_dsp_control_add(cs_ctl, info); 198 } 199 } 200 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_add_controls, SND_HDA_CS_DSP_CONTROLS); 201 202 void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) 203 { 204 struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; 205 206 kfree(ctl); 207 } 208 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); 209 210 int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, 211 unsigned int alg, const void *buf, size_t len) 212 { 213 struct cs_dsp_coeff_ctl *cs_ctl; 214 struct hda_cs_dsp_coeff_ctl *ctl; 215 int ret; 216 217 mutex_lock(&dsp->pwr_lock); 218 cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); 219 ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); 220 mutex_unlock(&dsp->pwr_lock); 221 if (ret) 222 return ret; 223 224 if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) 225 return 0; 226 227 ctl = cs_ctl->priv; 228 229 snd_ctl_notify(ctl->card, SNDRV_CTL_EVENT_MASK_VALUE, &ctl->kctl->id); 230 231 return 0; 232 } 233 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS); 234 235 int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, 236 unsigned int alg, void *buf, size_t len) 237 { 238 int ret; 239 240 mutex_lock(&dsp->pwr_lock); 241 ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(dsp, name, type, alg), 0, buf, len); 242 mutex_unlock(&dsp->pwr_lock); 243 244 return ret; 245 246 } 247 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl, SND_HDA_CS_DSP_CONTROLS); 248 249 MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library"); 250 MODULE_AUTHOR("Stefan Binding, <sbinding@opensource.cirrus.com>"); 251 MODULE_LICENSE("GPL"); 252 MODULE_IMPORT_NS(FW_CS_DSP); 253