ASoC: wm_adsp: Expose coefficient blocks as ALSA binary controls

Add initial support for runtime tuning for the ADSP cores.  This
is achieved by exposing the coefficient configuration blocks as
ALSA binary controls.
The current code assumes that no controls on the DSP are volatile.

Signed-off-by: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
This commit is contained in:
Dimitris Papastamos 2013-05-08 14:15:35 +01:00 коммит произвёл Mark Brown
Родитель f722406faa
Коммит 6ab2b7b415
2 изменённых файлов: 454 добавлений и 7 удалений

Просмотреть файл

@ -21,6 +21,7 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@ -215,6 +216,36 @@ static struct {
[WM_ADSP_FW_RX_ANC] = { .file = "rx-anc" },
};
struct wm_coeff_ctl_ops {
int (*xget)(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int (*xput)(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int (*xinfo)(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
};
struct wm_coeff {
struct device *dev;
struct list_head ctl_list;
struct regmap *regmap;
};
struct wm_coeff_ctl {
const char *name;
struct snd_card *card;
struct wm_adsp_alg_region region;
struct wm_coeff_ctl_ops ops;
struct wm_adsp *adsp;
void *private;
unsigned int enabled:1;
struct list_head list;
void *cache;
size_t len;
unsigned int dirty:1;
struct snd_kcontrol *kcontrol;
};
static int wm_adsp_fw_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
@ -334,6 +365,181 @@ static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *region,
}
}
static int wm_coeff_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct wm_coeff_ctl *ctl = (struct wm_coeff_ctl *)kcontrol->private_value;
uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
uinfo->count = ctl->len;
return 0;
}
static int wm_coeff_write_control(struct snd_kcontrol *kcontrol,
const void *buf, size_t len)
{
struct wm_coeff *wm_coeff= snd_kcontrol_chip(kcontrol);
struct wm_coeff_ctl *ctl = (struct wm_coeff_ctl *)kcontrol->private_value;
struct wm_adsp_alg_region *region = &ctl->region;
const struct wm_adsp_region *mem;
struct wm_adsp *adsp = ctl->adsp;
void *scratch;
int ret;
unsigned int reg;
mem = wm_adsp_find_region(adsp, region->type);
if (!mem) {
adsp_err(adsp, "No base for region %x\n",
region->type);
return -EINVAL;
}
reg = ctl->region.base;
reg = wm_adsp_region_to_reg(mem, reg);
scratch = kmemdup(buf, ctl->len, GFP_KERNEL | GFP_DMA);
if (!scratch)
return -ENOMEM;
ret = regmap_raw_write(wm_coeff->regmap, reg, scratch,
ctl->len);
if (ret) {
adsp_err(adsp, "Failed to write %zu bytes to %x\n",
ctl->len, reg);
kfree(scratch);
return ret;
}
kfree(scratch);
return 0;
}
static int wm_coeff_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct wm_coeff_ctl *ctl = (struct wm_coeff_ctl *)kcontrol->private_value;
char *p = ucontrol->value.bytes.data;
memcpy(ctl->cache, p, ctl->len);
if (!ctl->enabled) {
ctl->dirty = 1;
return 0;
}
return wm_coeff_write_control(kcontrol, p, ctl->len);
}
static int wm_coeff_read_control(struct snd_kcontrol *kcontrol,
void *buf, size_t len)
{
struct wm_coeff *wm_coeff= snd_kcontrol_chip(kcontrol);
struct wm_coeff_ctl *ctl = (struct wm_coeff_ctl *)kcontrol->private_value;
struct wm_adsp_alg_region *region = &ctl->region;
const struct wm_adsp_region *mem;
struct wm_adsp *adsp = ctl->adsp;
void *scratch;
int ret;
unsigned int reg;
mem = wm_adsp_find_region(adsp, region->type);
if (!mem) {
adsp_err(adsp, "No base for region %x\n",
region->type);
return -EINVAL;
}
reg = ctl->region.base;
reg = wm_adsp_region_to_reg(mem, reg);
scratch = kmalloc(ctl->len, GFP_KERNEL | GFP_DMA);
if (!scratch)
return -ENOMEM;
ret = regmap_raw_read(wm_coeff->regmap, reg, scratch, ctl->len);
if (ret) {
adsp_err(adsp, "Failed to read %zu bytes from %x\n",
ctl->len, reg);
kfree(scratch);
return ret;
}
memcpy(buf, scratch, ctl->len);
kfree(scratch);
return 0;
}
static int wm_coeff_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct wm_coeff_ctl *ctl = (struct wm_coeff_ctl *)kcontrol->private_value;
char *p = ucontrol->value.bytes.data;
memcpy(p, ctl->cache, ctl->len);
return 0;
}
static int wm_coeff_add_kcontrol(struct wm_coeff *wm_coeff,
struct wm_coeff_ctl *ctl,
const struct snd_kcontrol_new *kctl)
{
int ret;
struct snd_kcontrol *kcontrol;
kcontrol = snd_ctl_new1(kctl, wm_coeff);
ret = snd_ctl_add(ctl->card, kcontrol);
if (ret < 0) {
dev_err(wm_coeff->dev, "Failed to add %s: %d\n",
kctl->name, ret);
return ret;
}
ctl->kcontrol = kcontrol;
return 0;
}
struct wmfw_ctl_work {
struct wm_coeff *wm_coeff;
struct wm_coeff_ctl *ctl;
struct work_struct work;
};
static int wmfw_add_ctl(struct wm_coeff *wm_coeff,
struct wm_coeff_ctl *ctl)
{
struct snd_kcontrol_new *kcontrol;
int ret;
if (!wm_coeff || !ctl || !ctl->name || !ctl->card)
return -EINVAL;
kcontrol = kzalloc(sizeof(*kcontrol), GFP_KERNEL);
if (!kcontrol)
return -ENOMEM;
kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
kcontrol->name = ctl->name;
kcontrol->info = wm_coeff_info;
kcontrol->get = wm_coeff_get;
kcontrol->put = wm_coeff_put;
kcontrol->private_value = (unsigned long)ctl;
ret = wm_coeff_add_kcontrol(wm_coeff,
ctl, kcontrol);
if (ret < 0)
goto err_kcontrol;
kfree(kcontrol);
list_add(&ctl->list, &wm_coeff->ctl_list);
return 0;
err_kcontrol:
kfree(kcontrol);
return ret;
}
static int wm_adsp_load(struct wm_adsp *dsp)
{
LIST_HEAD(buf_list);
@ -547,7 +753,156 @@ out:
return ret;
}
static int wm_adsp_setup_algs(struct wm_adsp *dsp)
static int wm_coeff_init_control_caches(struct wm_coeff *wm_coeff)
{
struct wm_coeff_ctl *ctl;
int ret;
list_for_each_entry(ctl, &wm_coeff->ctl_list,
list) {
if (!ctl->enabled || ctl->dirty)
continue;
ret = wm_coeff_read_control(ctl->kcontrol,
ctl->cache,
ctl->len);
if (ret < 0)
return ret;
}
return 0;
}
static int wm_coeff_sync_controls(struct wm_coeff *wm_coeff)
{
struct wm_coeff_ctl *ctl;
int ret;
list_for_each_entry(ctl, &wm_coeff->ctl_list,
list) {
if (!ctl->enabled)
continue;
if (ctl->dirty) {
ret = wm_coeff_write_control(ctl->kcontrol,
ctl->cache,
ctl->len);
if (ret < 0)
return ret;
ctl->dirty = 0;
}
}
return 0;
}
static void wm_adsp_ctl_work(struct work_struct *work)
{
struct wmfw_ctl_work *ctl_work = container_of(work,
struct wmfw_ctl_work,
work);
wmfw_add_ctl(ctl_work->wm_coeff, ctl_work->ctl);
kfree(ctl_work);
}
static int wm_adsp_create_control(struct snd_soc_codec *codec,
const struct wm_adsp_alg_region *region)
{
struct wm_adsp *dsp = snd_soc_codec_get_drvdata(codec);
struct wm_coeff_ctl *ctl;
struct wmfw_ctl_work *ctl_work;
char *name;
char *region_name;
int ret;
name = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!name)
return -ENOMEM;
switch (region->type) {
case WMFW_ADSP1_PM:
region_name = "PM";
break;
case WMFW_ADSP1_DM:
region_name = "DM";
break;
case WMFW_ADSP2_XM:
region_name = "XM";
break;
case WMFW_ADSP2_YM:
region_name = "YM";
break;
case WMFW_ADSP1_ZM:
region_name = "ZM";
break;
default:
return -EINVAL;
}
snprintf(name, PAGE_SIZE, "DSP%d %s %x",
dsp->num, region_name, region->alg);
list_for_each_entry(ctl, &dsp->wm_coeff->ctl_list,
list) {
if (!strcmp(ctl->name, name)) {
if (!ctl->enabled)
ctl->enabled = 1;
return 0;
}
}
ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
if (!ctl) {
ret = -ENOMEM;
goto err_name;
}
ctl->region = *region;
ctl->name = kmemdup(name, strlen(name) + 1, GFP_KERNEL);
if (!ctl->name) {
ret = -ENOMEM;
goto err_ctl;
}
ctl->enabled = 1;
ctl->dirty = 0;
ctl->ops.xget = wm_coeff_get;
ctl->ops.xput = wm_coeff_put;
ctl->card = codec->card->snd_card;
ctl->adsp = dsp;
ctl->len = region->len;
ctl->cache = kzalloc(ctl->len, GFP_KERNEL);
if (!ctl->cache) {
ret = -ENOMEM;
goto err_ctl_name;
}
ctl_work = kzalloc(sizeof(*ctl_work), GFP_KERNEL);
if (!ctl_work) {
ret = -ENOMEM;
goto err_ctl_cache;
}
ctl_work->wm_coeff = dsp->wm_coeff;
ctl_work->ctl = ctl;
INIT_WORK(&ctl_work->work, wm_adsp_ctl_work);
schedule_work(&ctl_work->work);
kfree(name);
return 0;
err_ctl_cache:
kfree(ctl->cache);
err_ctl_name:
kfree(ctl->name);
err_ctl:
kfree(ctl);
err_name:
kfree(name);
return ret;
}
static int wm_adsp_setup_algs(struct wm_adsp *dsp, struct snd_soc_codec *codec)
{
struct regmap *regmap = dsp->regmap;
struct wmfw_adsp1_id_hdr adsp1_id;
@ -730,7 +1085,16 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp)
region->type = WMFW_ADSP1_DM;
region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
region->base = be32_to_cpu(adsp1_alg[i].dm);
region->len = 0;
list_add_tail(&region->list, &dsp->alg_regions);
if (i + 1 < algs) {
region->len = be32_to_cpu(adsp1_alg[i + 1].dm);
region->len -= be32_to_cpu(adsp1_alg[i].dm);
wm_adsp_create_control(codec, region);
} else {
adsp_warn(dsp, "Missing length info for region DM with ID %x\n",
be32_to_cpu(adsp1_alg[i].alg.id));
}
region = kzalloc(sizeof(*region), GFP_KERNEL);
if (!region)
@ -738,7 +1102,16 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp)
region->type = WMFW_ADSP1_ZM;
region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
region->base = be32_to_cpu(adsp1_alg[i].zm);
region->len = 0;
list_add_tail(&region->list, &dsp->alg_regions);
if (i + 1 < algs) {
region->len = be32_to_cpu(adsp1_alg[i + 1].zm);
region->len -= be32_to_cpu(adsp1_alg[i].zm);
wm_adsp_create_control(codec, region);
} else {
adsp_warn(dsp, "Missing length info for region ZM with ID %x\n",
be32_to_cpu(adsp1_alg[i].alg.id));
}
break;
case WMFW_ADSP2:
@ -758,7 +1131,16 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp)
region->type = WMFW_ADSP2_XM;
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
region->base = be32_to_cpu(adsp2_alg[i].xm);
region->len = 0;
list_add_tail(&region->list, &dsp->alg_regions);
if (i + 1 < algs) {
region->len = be32_to_cpu(adsp2_alg[i + 1].xm);
region->len -= be32_to_cpu(adsp2_alg[i].xm);
wm_adsp_create_control(codec, region);
} else {
adsp_warn(dsp, "Missing length info for region XM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
}
region = kzalloc(sizeof(*region), GFP_KERNEL);
if (!region)
@ -766,7 +1148,16 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp)
region->type = WMFW_ADSP2_YM;
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
region->base = be32_to_cpu(adsp2_alg[i].ym);
region->len = 0;
list_add_tail(&region->list, &dsp->alg_regions);
if (i + 1 < algs) {
region->len = be32_to_cpu(adsp2_alg[i + 1].ym);
region->len -= be32_to_cpu(adsp2_alg[i].ym);
wm_adsp_create_control(codec, region);
} else {
adsp_warn(dsp, "Missing length info for region YM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
}
region = kzalloc(sizeof(*region), GFP_KERNEL);
if (!region)
@ -774,7 +1165,16 @@ static int wm_adsp_setup_algs(struct wm_adsp *dsp)
region->type = WMFW_ADSP2_ZM;
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
region->base = be32_to_cpu(adsp2_alg[i].zm);
region->len = 0;
list_add_tail(&region->list, &dsp->alg_regions);
if (i + 1 < algs) {
region->len = be32_to_cpu(adsp2_alg[i + 1].zm);
region->len -= be32_to_cpu(adsp2_alg[i].zm);
wm_adsp_create_control(codec, region);
} else {
adsp_warn(dsp, "Missing length info for region ZM with ID %x\n",
be32_to_cpu(adsp2_alg[i].alg.id));
}
break;
}
}
@ -986,6 +1386,7 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
struct snd_soc_codec *codec = w->codec;
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
struct wm_adsp *dsp = &dsps[w->shift];
struct wm_coeff_ctl *ctl;
int ret;
int val;
@ -1023,7 +1424,7 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
if (ret != 0)
goto err;
ret = wm_adsp_setup_algs(dsp);
ret = wm_adsp_setup_algs(dsp, codec);
if (ret != 0)
goto err;
@ -1031,6 +1432,16 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
if (ret != 0)
goto err;
/* Initialize caches for enabled and non-dirty controls */
ret = wm_coeff_init_control_caches(dsp->wm_coeff);
if (ret != 0)
goto err;
/* Sync dirty controls */
ret = wm_coeff_sync_controls(dsp->wm_coeff);
if (ret != 0)
goto err;
/* Start the core running */
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
ADSP1_CORE_ENA | ADSP1_START,
@ -1047,6 +1458,11 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
ADSP1_SYS_ENA, 0);
list_for_each_entry(ctl, &dsp->wm_coeff->ctl_list,
list) {
ctl->enabled = 0;
}
break;
default:
@ -1099,6 +1515,7 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
struct wm_adsp *dsp = &dsps[w->shift];
struct wm_adsp_alg_region *alg_region;
struct wm_coeff_ctl *ctl;
unsigned int val;
int ret;
@ -1164,7 +1581,7 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
if (ret != 0)
goto err;
ret = wm_adsp_setup_algs(dsp);
ret = wm_adsp_setup_algs(dsp, codec);
if (ret != 0)
goto err;
@ -1172,6 +1589,16 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
if (ret != 0)
goto err;
/* Initialize caches for enabled and non-dirty controls */
ret = wm_coeff_init_control_caches(dsp->wm_coeff);
if (ret != 0)
goto err;
/* Sync dirty controls */
ret = wm_coeff_sync_controls(dsp->wm_coeff);
if (ret != 0)
goto err;
ret = regmap_update_bits(dsp->regmap,
dsp->base + ADSP2_CONTROL,
ADSP2_CORE_ENA | ADSP2_START,
@ -1209,6 +1636,11 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
ret);
}
list_for_each_entry(ctl, &dsp->wm_coeff->ctl_list,
list) {
ctl->enabled = 0;
}
while (!list_empty(&dsp->alg_regions)) {
alg_region = list_first_entry(&dsp->alg_regions,
struct wm_adsp_alg_region,
@ -1247,36 +1679,48 @@ int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs)
INIT_LIST_HEAD(&adsp->alg_regions);
adsp->wm_coeff = kzalloc(sizeof(*adsp->wm_coeff),
GFP_KERNEL);
if (!adsp->wm_coeff)
return -ENOMEM;
adsp->wm_coeff->regmap = adsp->regmap;
adsp->wm_coeff->dev = adsp->dev;
INIT_LIST_HEAD(&adsp->wm_coeff->ctl_list);
if (dvfs) {
adsp->dvfs = devm_regulator_get(adsp->dev, "DCVDD");
if (IS_ERR(adsp->dvfs)) {
ret = PTR_ERR(adsp->dvfs);
dev_err(adsp->dev, "Failed to get DCVDD: %d\n", ret);
return ret;
goto out_coeff;
}
ret = regulator_enable(adsp->dvfs);
if (ret != 0) {
dev_err(adsp->dev, "Failed to enable DCVDD: %d\n",
ret);
return ret;
goto out_coeff;
}
ret = regulator_set_voltage(adsp->dvfs, 1200000, 1800000);
if (ret != 0) {
dev_err(adsp->dev, "Failed to initialise DVFS: %d\n",
ret);
return ret;
goto out_coeff;
}
ret = regulator_disable(adsp->dvfs);
if (ret != 0) {
dev_err(adsp->dev, "Failed to disable DCVDD: %d\n",
ret);
return ret;
goto out_coeff;
}
}
return 0;
out_coeff:
kfree(adsp->wm_coeff);
return ret;
}
EXPORT_SYMBOL_GPL(wm_adsp2_init);

Просмотреть файл

@ -30,6 +30,7 @@ struct wm_adsp_alg_region {
unsigned int alg;
int type;
unsigned int base;
size_t len;
};
struct wm_adsp {
@ -55,6 +56,8 @@ struct wm_adsp {
bool running;
struct regulator *dvfs;
struct wm_coeff *wm_coeff;
};
#define WM_ADSP1(wname, num) \