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 55 return cs_dsp_coeff_lock_and_write_ctrl(cs_ctl, 0, p, cs_ctl->len); 56 } 57 58 static int hda_cs_dsp_coeff_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) 59 { 60 struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); 61 struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; 62 char *p = ucontrol->value.bytes.data; 63 64 return cs_dsp_coeff_lock_and_read_ctrl(cs_ctl, 0, p, cs_ctl->len); 65 } 66 67 static unsigned int wmfw_convert_flags(unsigned int in) 68 { 69 unsigned int out, rd, wr, vol; 70 71 rd = SNDRV_CTL_ELEM_ACCESS_READ; 72 wr = SNDRV_CTL_ELEM_ACCESS_WRITE; 73 vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; 74 75 out = 0; 76 77 if (in) { 78 out |= rd; 79 if (in & WMFW_CTL_FLAG_WRITEABLE) 80 out |= wr; 81 if (in & WMFW_CTL_FLAG_VOLATILE) 82 out |= vol; 83 } else { 84 out |= rd | wr | vol; 85 } 86 87 return out; 88 } 89 90 static void hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char *name) 91 { 92 struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; 93 struct snd_kcontrol_new kcontrol = {0}; 94 struct snd_kcontrol *kctl; 95 int ret = 0; 96 97 if (cs_ctl->len > ADSP_MAX_STD_CTRL_SIZE) { 98 dev_err(cs_ctl->dsp->dev, "KControl %s: length %zu exceeds maximum %d\n", name, 99 cs_ctl->len, ADSP_MAX_STD_CTRL_SIZE); 100 return; 101 } 102 103 kcontrol.name = name; 104 kcontrol.info = hda_cs_dsp_coeff_info; 105 kcontrol.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 106 kcontrol.access = wmfw_convert_flags(cs_ctl->flags); 107 kcontrol.get = hda_cs_dsp_coeff_get; 108 kcontrol.put = hda_cs_dsp_coeff_put; 109 110 /* Save ctl inside private_data, ctl is owned by cs_dsp, 111 * and will be freed when cs_dsp removes the control */ 112 kctl = snd_ctl_new1(&kcontrol, (void *)ctl); 113 if (!kctl) 114 return; 115 116 ret = snd_ctl_add(ctl->card, kctl); 117 if (ret) { 118 dev_err(cs_ctl->dsp->dev, "Failed to add KControl %s = %d\n", kcontrol.name, ret); 119 return; 120 } 121 122 dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol.name); 123 ctl->kctl = kctl; 124 } 125 126 static void hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, 127 const struct hda_cs_dsp_ctl_info *info) 128 { 129 struct cs_dsp *cs_dsp = cs_ctl->dsp; 130 char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; 131 struct hda_cs_dsp_coeff_ctl *ctl; 132 const char *region_name; 133 int ret; 134 135 region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type); 136 if (!region_name) { 137 dev_warn(cs_dsp->dev, "Unknown region type: %d\n", cs_ctl->alg_region.type); 138 return; 139 } 140 141 ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %.12s %x", info->device_name, 142 cs_dsp->name, hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg); 143 144 if (cs_ctl->subname) { 145 int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; 146 int skip = 0; 147 148 /* Truncate the subname from the start if it is too long */ 149 if (cs_ctl->subname_len > avail) 150 skip = cs_ctl->subname_len - avail; 151 152 snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, 153 " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); 154 } 155 156 ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); 157 if (!ctl) 158 return; 159 160 ctl->cs_ctl = cs_ctl; 161 ctl->card = info->card; 162 cs_ctl->priv = ctl; 163 164 hda_cs_dsp_add_kcontrol(ctl, name); 165 } 166 167 void hda_cs_dsp_add_controls(struct cs_dsp *dsp, const struct hda_cs_dsp_ctl_info *info) 168 { 169 struct cs_dsp_coeff_ctl *cs_ctl; 170 171 /* 172 * pwr_lock would cause mutex inversion with ALSA control lock compared 173 * to the get/put functions. 174 * It is safe to walk the list without holding a mutex because entries 175 * are persistent and only cs_dsp_power_up() or cs_dsp_remove() can 176 * change the list. 177 */ 178 lockdep_assert_not_held(&dsp->pwr_lock); 179 180 list_for_each_entry(cs_ctl, &dsp->ctl_list, list) { 181 if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) 182 continue; 183 184 if (cs_ctl->priv) 185 continue; 186 187 hda_cs_dsp_control_add(cs_ctl, info); 188 } 189 } 190 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_add_controls, SND_HDA_CS_DSP_CONTROLS); 191 192 void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) 193 { 194 struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; 195 196 kfree(ctl); 197 } 198 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); 199 200 int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, 201 unsigned int alg, const void *buf, size_t len) 202 { 203 struct cs_dsp_coeff_ctl *cs_ctl; 204 int ret; 205 206 mutex_lock(&dsp->pwr_lock); 207 cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); 208 ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); 209 mutex_unlock(&dsp->pwr_lock); 210 if (ret < 0) 211 return ret; 212 213 return 0; 214 } 215 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS); 216 217 int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, 218 unsigned int alg, void *buf, size_t len) 219 { 220 int ret; 221 222 mutex_lock(&dsp->pwr_lock); 223 ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(dsp, name, type, alg), 0, buf, len); 224 mutex_unlock(&dsp->pwr_lock); 225 226 return ret; 227 228 } 229 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl, SND_HDA_CS_DSP_CONTROLS); 230 231 MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library"); 232 MODULE_AUTHOR("Stefan Binding, <sbinding@opensource.cirrus.com>"); 233 MODULE_LICENSE("GPL"); 234 MODULE_IMPORT_NS(FW_CS_DSP); 235