Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fbc6c4ee authored by Mark Brown's avatar Mark Brown
Browse files

Merge remote-tracking branch 'asoc/topic/adsp' into asoc-next

parents 44947837 0c2e3f34
Loading
Loading
Loading
Loading
+452 −7
Original line number Diff line number Diff line
@@ -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 set: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->set = 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,157 @@ static int wm_adsp_load(struct wm_adsp *dsp)
	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->set)
			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->set) {
			ret = wm_coeff_write_control(ctl->kcontrol,
						     ctl->cache,
						     ctl->len);
			if (ret < 0)
				return ret;
		}
	}

	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:
		ret = -EINVAL;
		goto err_name;
	}

	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;
			goto found;
		}
	}

	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->set = 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);

found:
	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 +1086,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 +1103,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 +1132,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 +1149,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 +1166,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 +1387,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 +1425,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 +1433,16 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
		if (ret != 0)
			goto err;

		/* Initialize caches for enabled and unset controls */
		ret = wm_coeff_init_control_caches(dsp->wm_coeff);
		if (ret != 0)
			goto err;

		/* Sync set 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 +1459,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 +1516,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 +1582,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 +1590,16 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
		if (ret != 0)
			goto err;

		/* Initialize caches for enabled and unset controls */
		ret = wm_coeff_init_control_caches(dsp->wm_coeff);
		if (ret != 0)
			goto err;

		/* Sync set 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 +1637,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 +1680,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);
+3 −0
Original line number Diff line number Diff line
@@ -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) \