1 // SPDX-License-Identifier: GPL-2.0
3 // HDA DSP ALSA Control Driver
5 // Copyright 2022 Cirrus Logic, Inc.
7 // Author: Stefan Binding <sbinding@opensource.cirrus.com>
9 #include <linux/module.h>
10 #include <sound/soc.h>
11 #include <linux/cleanup.h>
12 #include <linux/firmware/cirrus/cs_dsp.h>
13 #include <linux/firmware/cirrus/wmfw.h>
14 #include "hda_cs_dsp_ctl.h"
16 #define ADSP_MAX_STD_CTRL_SIZE 512
18 struct hda_cs_dsp_coeff_ctl
{
19 struct cs_dsp_coeff_ctl
*cs_ctl
;
20 struct snd_card
*card
;
21 struct snd_kcontrol
*kctl
;
24 static const char * const hda_cs_dsp_fw_text
[HDA_CS_DSP_NUM_FW
] = {
25 [HDA_CS_DSP_FW_SPK_PROT
] = "Prot",
26 [HDA_CS_DSP_FW_SPK_CALI
] = "Cali",
27 [HDA_CS_DSP_FW_SPK_DIAG
] = "Diag",
28 [HDA_CS_DSP_FW_MISC
] = "Misc",
31 const char * const hda_cs_dsp_fw_ids
[HDA_CS_DSP_NUM_FW
] = {
32 [HDA_CS_DSP_FW_SPK_PROT
] = "spk-prot",
33 [HDA_CS_DSP_FW_SPK_CALI
] = "spk-cali",
34 [HDA_CS_DSP_FW_SPK_DIAG
] = "spk-diag",
35 [HDA_CS_DSP_FW_MISC
] = "misc",
37 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_fw_ids
, "SND_HDA_CS_DSP_CONTROLS");
39 static int hda_cs_dsp_coeff_info(struct snd_kcontrol
*kctl
, struct snd_ctl_elem_info
*uinfo
)
41 struct hda_cs_dsp_coeff_ctl
*ctl
= (struct hda_cs_dsp_coeff_ctl
*)snd_kcontrol_chip(kctl
);
42 struct cs_dsp_coeff_ctl
*cs_ctl
= ctl
->cs_ctl
;
44 uinfo
->type
= SNDRV_CTL_ELEM_TYPE_BYTES
;
45 uinfo
->count
= cs_ctl
->len
;
50 static int hda_cs_dsp_coeff_put(struct snd_kcontrol
*kctl
, struct snd_ctl_elem_value
*ucontrol
)
52 struct hda_cs_dsp_coeff_ctl
*ctl
= (struct hda_cs_dsp_coeff_ctl
*)snd_kcontrol_chip(kctl
);
53 struct cs_dsp_coeff_ctl
*cs_ctl
= ctl
->cs_ctl
;
54 char *p
= ucontrol
->value
.bytes
.data
;
56 return cs_dsp_coeff_lock_and_write_ctrl(cs_ctl
, 0, p
, cs_ctl
->len
);
59 static int hda_cs_dsp_coeff_get(struct snd_kcontrol
*kctl
, struct snd_ctl_elem_value
*ucontrol
)
61 struct hda_cs_dsp_coeff_ctl
*ctl
= (struct hda_cs_dsp_coeff_ctl
*)snd_kcontrol_chip(kctl
);
62 struct cs_dsp_coeff_ctl
*cs_ctl
= ctl
->cs_ctl
;
63 char *p
= ucontrol
->value
.bytes
.data
;
65 return cs_dsp_coeff_lock_and_read_ctrl(cs_ctl
, 0, p
, cs_ctl
->len
);
68 static unsigned int wmfw_convert_flags(unsigned int in
)
70 unsigned int out
, rd
, wr
, vol
;
72 rd
= SNDRV_CTL_ELEM_ACCESS_READ
;
73 wr
= SNDRV_CTL_ELEM_ACCESS_WRITE
;
74 vol
= SNDRV_CTL_ELEM_ACCESS_VOLATILE
;
80 if (in
& WMFW_CTL_FLAG_WRITEABLE
)
82 if (in
& WMFW_CTL_FLAG_VOLATILE
)
91 static void hda_cs_dsp_free_kcontrol(struct snd_kcontrol
*kctl
)
93 struct hda_cs_dsp_coeff_ctl
*ctl
= (struct hda_cs_dsp_coeff_ctl
*)snd_kcontrol_chip(kctl
);
94 struct cs_dsp_coeff_ctl
*cs_ctl
= ctl
->cs_ctl
;
96 /* NULL priv to prevent a double-free in hda_cs_dsp_control_remove() */
101 static void hda_cs_dsp_add_kcontrol(struct cs_dsp_coeff_ctl
*cs_ctl
,
102 const struct hda_cs_dsp_ctl_info
*info
,
105 struct snd_kcontrol_new kcontrol
= {0};
106 struct snd_kcontrol
*kctl
;
107 struct hda_cs_dsp_coeff_ctl
*ctl
__free(kfree
) = NULL
;
110 if (cs_ctl
->len
> ADSP_MAX_STD_CTRL_SIZE
) {
111 dev_err(cs_ctl
->dsp
->dev
, "KControl %s: length %zu exceeds maximum %d\n", name
,
112 cs_ctl
->len
, ADSP_MAX_STD_CTRL_SIZE
);
116 ctl
= kzalloc(sizeof(*ctl
), GFP_KERNEL
);
120 ctl
->cs_ctl
= cs_ctl
;
121 ctl
->card
= info
->card
;
123 kcontrol
.name
= name
;
124 kcontrol
.info
= hda_cs_dsp_coeff_info
;
125 kcontrol
.iface
= SNDRV_CTL_ELEM_IFACE_MIXER
;
126 kcontrol
.access
= wmfw_convert_flags(cs_ctl
->flags
);
127 kcontrol
.get
= hda_cs_dsp_coeff_get
;
128 kcontrol
.put
= hda_cs_dsp_coeff_put
;
130 kctl
= snd_ctl_new1(&kcontrol
, (void *)ctl
);
134 kctl
->private_free
= hda_cs_dsp_free_kcontrol
;
137 /* snd_ctl_add() calls our private_free on error, which will kfree(ctl) */
138 cs_ctl
->priv
= no_free_ptr(ctl
);
139 ret
= snd_ctl_add(info
->card
, kctl
);
141 dev_err(cs_ctl
->dsp
->dev
, "Failed to add KControl %s = %d\n", kcontrol
.name
, ret
);
145 dev_dbg(cs_ctl
->dsp
->dev
, "Added KControl: %s\n", kcontrol
.name
);
148 static void hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl
*cs_ctl
,
149 const struct hda_cs_dsp_ctl_info
*info
)
151 struct cs_dsp
*cs_dsp
= cs_ctl
->dsp
;
152 char name
[SNDRV_CTL_ELEM_ID_NAME_MAXLEN
];
153 const char *region_name
;
156 region_name
= cs_dsp_mem_region_name(cs_ctl
->alg_region
.type
);
158 dev_warn(cs_dsp
->dev
, "Unknown region type: %d\n", cs_ctl
->alg_region
.type
);
162 ret
= scnprintf(name
, SNDRV_CTL_ELEM_ID_NAME_MAXLEN
, "%s %s %.12s %x", info
->device_name
,
163 cs_dsp
->name
, hda_cs_dsp_fw_text
[info
->fw_type
], cs_ctl
->alg_region
.alg
);
165 if (cs_ctl
->subname
) {
166 int avail
= SNDRV_CTL_ELEM_ID_NAME_MAXLEN
- ret
- 2;
169 /* Truncate the subname from the start if it is too long */
170 if (cs_ctl
->subname_len
> avail
)
171 skip
= cs_ctl
->subname_len
- avail
;
173 snprintf(name
+ ret
, SNDRV_CTL_ELEM_ID_NAME_MAXLEN
- ret
,
174 " %.*s", cs_ctl
->subname_len
- skip
, cs_ctl
->subname
+ skip
);
177 hda_cs_dsp_add_kcontrol(cs_ctl
, info
, name
);
180 void hda_cs_dsp_add_controls(struct cs_dsp
*dsp
, const struct hda_cs_dsp_ctl_info
*info
)
182 struct cs_dsp_coeff_ctl
*cs_ctl
;
185 * pwr_lock would cause mutex inversion with ALSA control lock compared
186 * to the get/put functions.
187 * It is safe to walk the list without holding a mutex because entries
188 * are persistent and only cs_dsp_power_up() or cs_dsp_remove() can
191 lockdep_assert_not_held(&dsp
->pwr_lock
);
193 list_for_each_entry(cs_ctl
, &dsp
->ctl_list
, list
) {
194 if (cs_ctl
->flags
& WMFW_CTL_FLAG_SYS
)
200 hda_cs_dsp_control_add(cs_ctl
, info
);
203 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_add_controls
, "SND_HDA_CS_DSP_CONTROLS");
205 void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl
*cs_ctl
)
207 struct hda_cs_dsp_coeff_ctl
*ctl
= cs_ctl
->priv
;
209 /* ctl and kctl may already have been removed by ALSA private_free */
211 snd_ctl_remove(ctl
->card
, ctl
->kctl
);
213 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove
, "SND_HDA_CS_DSP_CONTROLS");
215 int hda_cs_dsp_write_ctl(struct cs_dsp
*dsp
, const char *name
, int type
,
216 unsigned int alg
, const void *buf
, size_t len
)
218 struct cs_dsp_coeff_ctl
*cs_ctl
;
221 mutex_lock(&dsp
->pwr_lock
);
222 cs_ctl
= cs_dsp_get_ctl(dsp
, name
, type
, alg
);
223 ret
= cs_dsp_coeff_write_ctrl(cs_ctl
, 0, buf
, len
);
224 mutex_unlock(&dsp
->pwr_lock
);
230 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl
, "SND_HDA_CS_DSP_CONTROLS");
232 int hda_cs_dsp_read_ctl(struct cs_dsp
*dsp
, const char *name
, int type
,
233 unsigned int alg
, void *buf
, size_t len
)
237 mutex_lock(&dsp
->pwr_lock
);
238 ret
= cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(dsp
, name
, type
, alg
), 0, buf
, len
);
239 mutex_unlock(&dsp
->pwr_lock
);
244 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl
, "SND_HDA_CS_DSP_CONTROLS");
246 MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library");
247 MODULE_AUTHOR("Stefan Binding, <sbinding@opensource.cirrus.com>");
248 MODULE_LICENSE("GPL");
249 MODULE_IMPORT_NS("FW_CS_DSP");