Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 //
0003 // HDA DSP ALSA Control Driver
0004 //
0005 // Copyright 2022 Cirrus Logic, Inc.
0006 //
0007 // Author: Stefan Binding <sbinding@opensource.cirrus.com>
0008 
0009 #include <linux/module.h>
0010 #include <sound/soc.h>
0011 #include <linux/firmware/cirrus/cs_dsp.h>
0012 #include <linux/firmware/cirrus/wmfw.h>
0013 #include "hda_cs_dsp_ctl.h"
0014 
0015 #define ADSP_MAX_STD_CTRL_SIZE               512
0016 
0017 struct hda_cs_dsp_coeff_ctl {
0018     struct cs_dsp_coeff_ctl *cs_ctl;
0019     struct snd_card *card;
0020     struct snd_kcontrol *kctl;
0021 };
0022 
0023 static const char * const hda_cs_dsp_fw_text[HDA_CS_DSP_NUM_FW] = {
0024     [HDA_CS_DSP_FW_SPK_PROT] = "Prot",
0025     [HDA_CS_DSP_FW_SPK_CALI] = "Cali",
0026     [HDA_CS_DSP_FW_SPK_DIAG] = "Diag",
0027     [HDA_CS_DSP_FW_MISC] =     "Misc",
0028 };
0029 
0030 const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW] = {
0031     [HDA_CS_DSP_FW_SPK_PROT] = "spk-prot",
0032     [HDA_CS_DSP_FW_SPK_CALI] = "spk-cali",
0033     [HDA_CS_DSP_FW_SPK_DIAG] = "spk-diag",
0034     [HDA_CS_DSP_FW_MISC] =     "misc",
0035 };
0036 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_fw_ids, SND_HDA_CS_DSP_CONTROLS);
0037 
0038 static int hda_cs_dsp_coeff_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
0039 {
0040     struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl);
0041     struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl;
0042 
0043     uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
0044     uinfo->count = cs_ctl->len;
0045 
0046     return 0;
0047 }
0048 
0049 static int hda_cs_dsp_coeff_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
0050 {
0051     struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl);
0052     struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl;
0053     char *p = ucontrol->value.bytes.data;
0054     int ret = 0;
0055 
0056     mutex_lock(&cs_ctl->dsp->pwr_lock);
0057     ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, p, cs_ctl->len);
0058     mutex_unlock(&cs_ctl->dsp->pwr_lock);
0059 
0060     return ret;
0061 }
0062 
0063 static int hda_cs_dsp_coeff_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
0064 {
0065     struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl);
0066     struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl;
0067     char *p = ucontrol->value.bytes.data;
0068     int ret;
0069 
0070     mutex_lock(&cs_ctl->dsp->pwr_lock);
0071     ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, p, cs_ctl->len);
0072     mutex_unlock(&cs_ctl->dsp->pwr_lock);
0073 
0074     return ret;
0075 }
0076 
0077 static unsigned int wmfw_convert_flags(unsigned int in)
0078 {
0079     unsigned int out, rd, wr, vol;
0080 
0081     rd = SNDRV_CTL_ELEM_ACCESS_READ;
0082     wr = SNDRV_CTL_ELEM_ACCESS_WRITE;
0083     vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE;
0084 
0085     out = 0;
0086 
0087     if (in) {
0088         out |= rd;
0089         if (in & WMFW_CTL_FLAG_WRITEABLE)
0090             out |= wr;
0091         if (in & WMFW_CTL_FLAG_VOLATILE)
0092             out |= vol;
0093     } else {
0094         out |= rd | wr | vol;
0095     }
0096 
0097     return out;
0098 }
0099 
0100 static int hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char *name)
0101 {
0102     struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl;
0103     struct snd_kcontrol_new kcontrol = {0};
0104     struct snd_kcontrol *kctl;
0105     int ret = 0;
0106 
0107     if (cs_ctl->len > ADSP_MAX_STD_CTRL_SIZE) {
0108         dev_err(cs_ctl->dsp->dev, "KControl %s: length %zu exceeds maximum %d\n", name,
0109             cs_ctl->len, ADSP_MAX_STD_CTRL_SIZE);
0110         return -EINVAL;
0111     }
0112 
0113     kcontrol.name = name;
0114     kcontrol.info = hda_cs_dsp_coeff_info;
0115     kcontrol.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
0116     kcontrol.access = wmfw_convert_flags(cs_ctl->flags);
0117     kcontrol.get = hda_cs_dsp_coeff_get;
0118     kcontrol.put = hda_cs_dsp_coeff_put;
0119 
0120     /* Save ctl inside private_data, ctl is owned by cs_dsp,
0121      * and will be freed when cs_dsp removes the control */
0122     kctl = snd_ctl_new1(&kcontrol, (void *)ctl);
0123     if (!kctl) {
0124         ret = -ENOMEM;
0125         return ret;
0126     }
0127 
0128     ret = snd_ctl_add(ctl->card, kctl);
0129     if (ret) {
0130         dev_err(cs_ctl->dsp->dev, "Failed to add KControl %s = %d\n", kcontrol.name, ret);
0131         return ret;
0132     }
0133 
0134     dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol.name);
0135     ctl->kctl = kctl;
0136 
0137     return 0;
0138 }
0139 
0140 int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info)
0141 {
0142     struct cs_dsp *cs_dsp = cs_ctl->dsp;
0143     char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
0144     struct hda_cs_dsp_coeff_ctl *ctl;
0145     const char *region_name;
0146     int ret;
0147 
0148     if (cs_ctl->flags & WMFW_CTL_FLAG_SYS)
0149         return 0;
0150 
0151     region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type);
0152     if (!region_name) {
0153         dev_err(cs_dsp->dev, "Unknown region type: %d\n", cs_ctl->alg_region.type);
0154         return -EINVAL;
0155     }
0156 
0157     ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %.12s %x", info->device_name,
0158             cs_dsp->name, hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg);
0159 
0160     if (cs_ctl->subname) {
0161         int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2;
0162         int skip = 0;
0163 
0164         /* Truncate the subname from the start if it is too long */
0165         if (cs_ctl->subname_len > avail)
0166             skip = cs_ctl->subname_len - avail;
0167 
0168         snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret,
0169              " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip);
0170     }
0171 
0172     ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
0173     if (!ctl)
0174         return -ENOMEM;
0175 
0176     ctl->cs_ctl = cs_ctl;
0177     ctl->card = info->card;
0178     cs_ctl->priv = ctl;
0179 
0180     ret = hda_cs_dsp_add_kcontrol(ctl, name);
0181     if (ret) {
0182         dev_err(cs_dsp->dev, "Error (%d) adding control %s\n", ret, name);
0183         kfree(ctl);
0184         return ret;
0185     }
0186 
0187     return 0;
0188 }
0189 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_add, SND_HDA_CS_DSP_CONTROLS);
0190 
0191 void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl)
0192 {
0193     struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv;
0194 
0195     kfree(ctl);
0196 }
0197 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS);
0198 
0199 int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type,
0200              unsigned int alg, const void *buf, size_t len)
0201 {
0202     struct cs_dsp_coeff_ctl *cs_ctl;
0203     struct hda_cs_dsp_coeff_ctl *ctl;
0204     int ret;
0205 
0206     cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg);
0207     if (!cs_ctl)
0208         return -EINVAL;
0209 
0210     ctl = cs_ctl->priv;
0211 
0212     ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len);
0213     if (ret)
0214         return ret;
0215 
0216     if (cs_ctl->flags & WMFW_CTL_FLAG_SYS)
0217         return 0;
0218 
0219     snd_ctl_notify(ctl->card, SNDRV_CTL_EVENT_MASK_VALUE, &ctl->kctl->id);
0220 
0221     return 0;
0222 }
0223 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS);
0224 
0225 int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type,
0226             unsigned int alg, void *buf, size_t len)
0227 {
0228     struct cs_dsp_coeff_ctl *cs_ctl;
0229 
0230     cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg);
0231     if (!cs_ctl)
0232         return -EINVAL;
0233 
0234     return cs_dsp_coeff_read_ctrl(cs_ctl, 0, buf, len);
0235 }
0236 EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl, SND_HDA_CS_DSP_CONTROLS);
0237 
0238 MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library");
0239 MODULE_AUTHOR("Stefan Binding, <sbinding@opensource.cirrus.com>");
0240 MODULE_LICENSE("GPL");