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 int 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 -EINVAL; 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 ret = -ENOMEM; 125 return ret; 126 } 127 128 ret = snd_ctl_add(ctl->card, kctl); 129 if (ret) { 130 dev_err(cs_ctl->dsp->dev, "Failed to add KControl %s = %d\n", kcontrol.name, ret); 131 return ret; 132 } 133 134 dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol.name); 135 ctl->kctl = kctl; 136 137 return 0; 138 } 139 140 int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info) 141 { 142 struct cs_dsp *cs_dsp = cs_ctl->dsp; 143 char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; 144 struct hda_cs_dsp_coeff_ctl *ctl; 145 const char *region_name; 146 int ret; 147 148 if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) 149 return 0; 150 151 region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type); 152 if (!region_name) { 153 dev_err(cs_dsp->dev, "Unknown region type: %d\n", cs_ctl->alg_region.type); 154 return -EINVAL; 155 } 156 157 ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %.12s %x", info->device_name, 158 cs_dsp->name, hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg); 159 160 if (cs_ctl->subname) { 161 int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; 162 int skip = 0; 163 164 /* Truncate the subname from the start if it is too long */ 165 if (cs_ctl->subname_len > avail) 166 skip = cs_ctl->subname_len - avail; 167 168 snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, 169 " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); 170 } 171 172 ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); 173 if (!ctl) 174 return -ENOMEM; 175 176 ctl->cs_ctl = cs_ctl; 177 ctl->card = info->card; 178 cs_ctl->priv = ctl; 179 180 ret = hda_cs_dsp_add_kcontrol(ctl, name); 181 if (ret) { 182 dev_err(cs_dsp->dev, "Error (%d) adding control %s\n", ret, name); 183 kfree(ctl); 184 return ret; 185 } 186 187 return 0; 188 } 189 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_add, SND_HDA_CS_DSP_CONTROLS); 190 191 void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) 192 { 193 struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; 194 195 kfree(ctl); 196 } 197 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); 198 199 int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, 200 unsigned int alg, const void *buf, size_t len) 201 { 202 struct cs_dsp_coeff_ctl *cs_ctl; 203 struct hda_cs_dsp_coeff_ctl *ctl; 204 int ret; 205 206 cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); 207 if (!cs_ctl) 208 return -EINVAL; 209 210 ctl = cs_ctl->priv; 211 212 ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); 213 if (ret) 214 return ret; 215 216 if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) 217 return 0; 218 219 snd_ctl_notify(ctl->card, SNDRV_CTL_EVENT_MASK_VALUE, &ctl->kctl->id); 220 221 return 0; 222 } 223 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS); 224 225 int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, 226 unsigned int alg, void *buf, size_t len) 227 { 228 struct cs_dsp_coeff_ctl *cs_ctl; 229 230 cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); 231 if (!cs_ctl) 232 return -EINVAL; 233 234 return cs_dsp_coeff_read_ctrl(cs_ctl, 0, buf, len); 235 } 236 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl, SND_HDA_CS_DSP_CONTROLS); 237 238 MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library"); 239 MODULE_AUTHOR("Stefan Binding, <sbinding@opensource.cirrus.com>"); 240 MODULE_LICENSE("GPL"); 241