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

Commit 6e87badd authored by Mark Brown's avatar Mark Brown
Browse files

ASoC: wm2200: Provide initial coefficient loading



Allow a coefficient set provided using the Wolfson callibration tools to
be provided along with the firmware files. Currently only coefficient
files which configure absolute register addresses are supported.

Signed-off-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
parent e10f8711
Loading
Loading
Loading
Loading
+194 −0
Original line number Diff line number Diff line
@@ -1150,6 +1150,192 @@ out:
	return ret;
}

static int wm2200_setup_algs(struct snd_soc_codec *codec, int base)
{
	struct regmap *regmap = codec->control_data;
	struct wmfw_adsp1_id_hdr id;
	struct wmfw_adsp1_alg_hdr *alg;
	size_t algs;
	int zm, dm, pm, ret, i;
	__be32 val;

	switch (base) {
	case WM2200_DSP1_CONTROL_1:
		dm = WM2200_DSP1_DM_BASE;
		pm = WM2200_DSP1_PM_BASE;
		zm = WM2200_DSP1_ZM_BASE;
		break;
	case WM2200_DSP2_CONTROL_1:
		dm = WM2200_DSP2_DM_BASE;
		pm = WM2200_DSP2_PM_BASE;
		zm = WM2200_DSP2_ZM_BASE;
		break;
	default:
		dev_err(codec->dev, "BASE %x\n", base);
		BUG_ON(1);
		return -EINVAL;
	}

	ret = regmap_raw_read(regmap, dm, &id, sizeof(id));
	if (ret != 0) {
		dev_err(codec->dev, "Failed to read algorithm info: %d\n",
			ret);
		return ret;
	}

	algs = be32_to_cpu(id.algs);
	dev_info(codec->dev, "Firmware: %x v%d.%d.%d, %d algorithms\n",
		 be32_to_cpu(id.fw.id),
		 (be32_to_cpu(id.fw.ver) & 0xff000) >> 16,
		 (be32_to_cpu(id.fw.ver) & 0xff00) >> 8,
		 be32_to_cpu(id.fw.ver) & 0xff,
		 algs);

	/* Read the terminator first to validate the length */
	ret = regmap_raw_read(regmap, dm +
			      (sizeof(id) + (algs * sizeof(*alg))) / 2,
			      &val, sizeof(val));
	if (ret != 0) {
		dev_err(codec->dev, "Failed to read algorithm list end: %d\n",
			ret);
		return ret;
	}

	if (be32_to_cpu(val) != 0xbedead)
		dev_warn(codec->dev, "Algorithm list end %x 0x%x != 0xbeadead\n",
			 (sizeof(id) + (algs * sizeof(*alg))) / 2,
			 be32_to_cpu(val));

	alg = kzalloc(sizeof(*alg) * algs, GFP_KERNEL);
	if (!alg)
		return -ENOMEM;

	ret = regmap_raw_read(regmap, dm + (sizeof(id) / 2),
			      alg, algs * sizeof(*alg));
	if (ret != 0) {
		dev_err(codec->dev, "Failed to read algorithm list: %d\n",
			ret);
		goto out;
	}

	for (i = 0; i < algs; i++) {
		dev_info(codec->dev, "%d: ID %x v%d.%d.%d\n",
			 i, be32_to_cpu(alg[i].alg.id),
			 (be32_to_cpu(alg[i].alg.ver) & 0xff000) >> 16,
			 (be32_to_cpu(alg[i].alg.ver) & 0xff00) >> 8,
			 be32_to_cpu(alg[i].alg.ver) & 0xff);
	}

out:
	kfree(alg);
	return ret;
}

static int wm2200_load_coeff(struct snd_soc_codec *codec, int base)
{
	struct regmap *regmap = codec->control_data;
	struct wmfw_coeff_hdr *hdr;
	struct wmfw_coeff_item *blk;
	const struct firmware *firmware;
	const char *file, *region_name;
	int ret, dm, pm, zm, pos, blocks, type, offset, reg;

	switch (base) {
	case WM2200_DSP1_CONTROL_1:
		file = "wm2200-dsp1.bin";
		dm = WM2200_DSP1_DM_BASE;
		pm = WM2200_DSP1_PM_BASE;
		zm = WM2200_DSP1_ZM_BASE;
		break;
	case WM2200_DSP2_CONTROL_1:
		file = "wm2200-dsp2.bin";
		dm = WM2200_DSP2_DM_BASE;
		pm = WM2200_DSP2_PM_BASE;
		zm = WM2200_DSP2_ZM_BASE;
		break;
	default:
		dev_err(codec->dev, "BASE %x\n", base);
		BUG_ON(1);
		return -EINVAL;
	}

	ret = request_firmware(&firmware, file, codec->dev);
	if (ret != 0) {
		dev_err(codec->dev, "Failed to request '%s'\n", file);
		return ret;
	}

	if (sizeof(*hdr) >= firmware->size) {
		dev_err(codec->dev, "%s: file too short, %d bytes\n",
			file, firmware->size);
		return -EINVAL;
	}

	hdr = (void*)&firmware->data[0];
	if (memcmp(hdr->magic, "WMDR", 4) != 0) {
		dev_err(codec->dev, "%s: invalid magic\n", file);
		return -EINVAL;
	}

	dev_dbg(codec->dev, "%s: v%d.%d.%d\n", file,
		(le32_to_cpu(hdr->ver) >> 16) & 0xff,
		(le32_to_cpu(hdr->ver) >>  8) & 0xff,
		le32_to_cpu(hdr->ver) & 0xff);

	pos = le32_to_cpu(hdr->len);

	blocks = 0;
	while (pos < firmware->size &&
	       pos - firmware->size > sizeof(*blk)) {
		blk = (void*)(&firmware->data[pos]);

		type = be32_to_cpu(blk->type) & 0xff;
		offset = le32_to_cpu(blk->offset) & 0xffffff;

		dev_dbg(codec->dev, "%s.%d: %x v%d.%d.%d\n",
			file, blocks, le32_to_cpu(blk->id),
			(le32_to_cpu(blk->ver) >> 16) & 0xff,
			(le32_to_cpu(blk->ver) >>  8) & 0xff,
			le32_to_cpu(blk->ver) & 0xff);
		dev_dbg(codec->dev, "%s.%d: %d bytes at 0x%x in %x\n",
			file, blocks, le32_to_cpu(blk->len), offset, type);

		reg = 0;
		region_name = "Unknown";
		switch (type) {
		case WMFW_NAME_TEXT:
		case WMFW_INFO_TEXT:
			break;
		case WMFW_ABSOLUTE:
			region_name = "register";
			reg = offset;
			break;
		default:
			dev_err(codec->dev, "Unknown region type %x\n", type);
			break;
		}

		if (reg) {
			ret = regmap_raw_write(regmap, reg, blk->data,
					       le32_to_cpu(blk->len));
			if (ret != 0) {
				dev_err(codec->dev,
					"%s.%d: Failed to write to %x in %s\n",
					file, blocks, reg, region_name);
			}
		}

		pos += le32_to_cpu(blk->len) + sizeof(*blk);
		blocks++;
	}

	if (pos > firmware->size)
		dev_warn(codec->dev, "%s.%d: %d bytes at end of file\n",
			 file, blocks, pos - firmware->size);

	return 0;
}

static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w,
			 struct snd_kcontrol *kcontrol,
			 int event)
@@ -1164,6 +1350,14 @@ static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w,
		if (ret != 0)
			return ret;

		ret = wm2200_setup_algs(codec, base);
		if (ret != 0)
			return ret;

		ret = wm2200_load_coeff(codec, base);
		if (ret != 0)
			return ret;

		/* Start the core running */
		snd_soc_update_bits(codec, w->reg,
				    WM2200_DSP1_CORE_ENA | WM2200_DSP1_START,
+43 −0
Original line number Diff line number Diff line
@@ -43,6 +43,49 @@ struct wmfw_region {
	u8 data[];
} __packed;

struct wmfw_id_hdr {
	__be32 core_id;
	__be32 core_rev;
	__be32 id;
	__be32 ver;
} __packed;

struct wmfw_adsp1_id_hdr {
	struct wmfw_id_hdr fw;
	__be32 zm;
	__be32 dm;
	__be32 algs;
} __packed;

struct wmfw_alg_hdr {
	__be32 id;
	__be32 ver;
} __packed;

struct wmfw_adsp1_alg_hdr {
	struct wmfw_alg_hdr alg;
	__be32 zm;
	__be32 dm;
} __packed;

struct wmfw_coeff_hdr {
	u8 magic[4];
	__le32 len;
	__le32 ver;
	u8 data[];
} __packed;

struct wmfw_coeff_item {
	union {
		__be32 type;
		__le32 offset;
	};
	__le32 id;
	__le32 ver;
	__le32 sr;
	__le32 len;
	u8 data[];
} __packed;
#define WMFW_ADSP1 1

#define WMFW_ABSOLUTE  0xf0