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

Commit 95b5fa1a authored by Mark Brown's avatar Mark Brown
Browse files

Merge branch 'topic/adsp' of...

Merge branch 'topic/adsp' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into asoc-arizona
parents 2a0db307 2159ad93
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -146,6 +146,11 @@ config SND_SOC_WM_HUBS
	default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y
	default m if SND_SOC_WM8993=m || SND_SOC_WM8994=m

config SND_SOC_WM_ADSP
	tristate
	default y if SND_SOC_WM2200=y
	default m if SND_SOC_WM2200=m

config SND_SOC_AB8500_CODEC
	tristate

+2 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ snd-soc-twl6040-objs := twl6040.o
snd-soc-uda134x-objs := uda134x.o
snd-soc-uda1380-objs := uda1380.o
snd-soc-wl1273-objs := wl1273.o
snd-soc-wm-adsp-objs := wm_adsp.o
snd-soc-wm0010-objs := wm0010.o
snd-soc-wm1250-ev1-objs := wm1250-ev1.o
snd-soc-wm2000-objs := wm2000.o
@@ -229,6 +230,7 @@ obj-$(CONFIG_SND_SOC_WM9090) += snd-soc-wm9090.o
obj-$(CONFIG_SND_SOC_WM9705)	+= snd-soc-wm9705.o
obj-$(CONFIG_SND_SOC_WM9712)	+= snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713)	+= snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_ADSP)	+= snd-soc-wm-adsp.o
obj-$(CONFIG_SND_SOC_WM_HUBS)	+= snd-soc-wm-hubs.o

# Amp
+601 −33
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/firmware.h>
#include <linux/gcd.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
@@ -32,6 +33,39 @@
#include <sound/wm2200.h>

#include "wm2200.h"
#include "wmfw.h"

#define WM2200_DSP_CONTROL_1                   0x00
#define WM2200_DSP_CONTROL_2                   0x02
#define WM2200_DSP_CONTROL_3                   0x03
#define WM2200_DSP_CONTROL_4                   0x04
#define WM2200_DSP_CONTROL_5                   0x06
#define WM2200_DSP_CONTROL_6                   0x07
#define WM2200_DSP_CONTROL_7                   0x08
#define WM2200_DSP_CONTROL_8                   0x09
#define WM2200_DSP_CONTROL_9                   0x0A
#define WM2200_DSP_CONTROL_10                  0x0B
#define WM2200_DSP_CONTROL_11                  0x0C
#define WM2200_DSP_CONTROL_12                  0x0D
#define WM2200_DSP_CONTROL_13                  0x0F
#define WM2200_DSP_CONTROL_14                  0x10
#define WM2200_DSP_CONTROL_15                  0x11
#define WM2200_DSP_CONTROL_16                  0x12
#define WM2200_DSP_CONTROL_17                  0x13
#define WM2200_DSP_CONTROL_18                  0x14
#define WM2200_DSP_CONTROL_19                  0x16
#define WM2200_DSP_CONTROL_20                  0x17
#define WM2200_DSP_CONTROL_21                  0x18
#define WM2200_DSP_CONTROL_22                  0x1A
#define WM2200_DSP_CONTROL_23                  0x1B
#define WM2200_DSP_CONTROL_24                  0x1C
#define WM2200_DSP_CONTROL_25                  0x1E
#define WM2200_DSP_CONTROL_26                  0x20
#define WM2200_DSP_CONTROL_27                  0x21
#define WM2200_DSP_CONTROL_28                  0x22
#define WM2200_DSP_CONTROL_29                  0x23
#define WM2200_DSP_CONTROL_30                  0x24
#define WM2200_DSP_CONTROL_31                  0x26

/* The code assumes DCVDD is generated internally */
#define WM2200_NUM_CORE_SUPPLIES 2
@@ -64,6 +98,66 @@ struct wm2200_priv {
	int sysclk;
};

#define WM2200_DSP_RANGE_BASE (WM2200_MAX_REGISTER + 1)
#define WM2200_DSP_SPACING 12288

#define WM2200_DSP1_DM_BASE (WM2200_DSP_RANGE_BASE + (0 * WM2200_DSP_SPACING))
#define WM2200_DSP1_PM_BASE (WM2200_DSP_RANGE_BASE + (1 * WM2200_DSP_SPACING))
#define WM2200_DSP1_ZM_BASE (WM2200_DSP_RANGE_BASE + (2 * WM2200_DSP_SPACING))
#define WM2200_DSP2_DM_BASE (WM2200_DSP_RANGE_BASE + (3 * WM2200_DSP_SPACING))
#define WM2200_DSP2_PM_BASE (WM2200_DSP_RANGE_BASE + (4 * WM2200_DSP_SPACING))
#define WM2200_DSP2_ZM_BASE (WM2200_DSP_RANGE_BASE + (5 * WM2200_DSP_SPACING))

static const struct regmap_range_cfg wm2200_ranges[] = {
	/* DSP1 DM */
	{ .range_min = WM2200_DSP1_DM_BASE,
	  .range_max = WM2200_DSP1_DM_BASE + 12287,
	  .selector_reg = WM2200_DSP1_CONTROL_3,
	  .selector_mask = WM2200_DSP1_PAGE_BASE_DM_0_MASK,
	  .selector_shift = WM2200_DSP1_PAGE_BASE_DM_0_SHIFT,
	  .window_start = WM2200_DSP1_DM_0, .window_len = 2048, },

	/* DSP1 PM */
	{ .range_min = WM2200_DSP1_PM_BASE,
	  .range_max = WM2200_DSP1_PM_BASE + 12287,
	  .selector_reg = WM2200_DSP1_CONTROL_2,
	  .selector_mask = WM2200_DSP1_PAGE_BASE_PM_0_MASK,
	  .selector_shift = WM2200_DSP1_PAGE_BASE_PM_0_SHIFT,
	  .window_start = WM2200_DSP1_PM_0, .window_len = 768, },

	/* DSP1 ZM */
	{ .range_min = WM2200_DSP1_ZM_BASE,
	  .range_max = WM2200_DSP1_ZM_BASE + 2047,
	  .selector_reg = WM2200_DSP1_CONTROL_4,
	  .selector_mask = WM2200_DSP1_PAGE_BASE_ZM_0_MASK,
	  .selector_shift = WM2200_DSP1_PAGE_BASE_ZM_0_SHIFT,
	  .window_start = WM2200_DSP1_ZM_0, .window_len = 1024, },

	/* DSP2 DM */
	{ .range_min = WM2200_DSP2_DM_BASE,
	  .range_max = WM2200_DSP2_DM_BASE + 4095,
	  .selector_reg = WM2200_DSP2_CONTROL_3,
	  .selector_mask = WM2200_DSP2_PAGE_BASE_DM_0_MASK,
	  .selector_shift = WM2200_DSP2_PAGE_BASE_DM_0_SHIFT,
	  .window_start = WM2200_DSP2_DM_0, .window_len = 2048, },

	/* DSP2 PM */
	{ .range_min = WM2200_DSP2_PM_BASE,
	  .range_max = WM2200_DSP2_PM_BASE + 11287,
	  .selector_reg = WM2200_DSP2_CONTROL_2,
	  .selector_mask = WM2200_DSP2_PAGE_BASE_PM_0_MASK,
	  .selector_shift = WM2200_DSP2_PAGE_BASE_PM_0_SHIFT,
	  .window_start = WM2200_DSP2_PM_0, .window_len = 768, },

	/* DSP2 ZM */
	{ .range_min = WM2200_DSP2_ZM_BASE,
	  .range_max = WM2200_DSP2_ZM_BASE + 2047,
	  .selector_reg = WM2200_DSP2_CONTROL_4,
	  .selector_mask = WM2200_DSP2_PAGE_BASE_ZM_0_MASK,
	  .selector_shift = WM2200_DSP2_PAGE_BASE_ZM_0_SHIFT,
	  .window_start = WM2200_DSP2_ZM_0, .window_len = 1024, },
};

static struct reg_default wm2200_reg_defaults[] = {
	{ 0x000B, 0x0000 },   /* R11    - Tone Generator 1 */
	{ 0x0102, 0x0000 },   /* R258   - Clocking 3 */
@@ -407,6 +501,16 @@ static struct reg_default wm2200_reg_defaults[] = {

static bool wm2200_volatile_register(struct device *dev, unsigned int reg)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(wm2200_ranges); i++)
		if ((reg >= wm2200_ranges[i].window_start &&
		     reg <= wm2200_ranges[i].window_start +
		     wm2200_ranges[i].window_len) ||
		    (reg >= wm2200_ranges[i].range_min &&
		     reg <= wm2200_ranges[i].range_max))
			return true;

	switch (reg) {
	case WM2200_SOFTWARE_RESET:
	case WM2200_DEVICE_REVISION:
@@ -423,6 +527,16 @@ static bool wm2200_volatile_register(struct device *dev, unsigned int reg)

static bool wm2200_readable_register(struct device *dev, unsigned int reg)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(wm2200_ranges); i++)
		if ((reg >= wm2200_ranges[i].window_start &&
		     reg <= wm2200_ranges[i].window_start +
		     wm2200_ranges[i].window_len) ||
		    (reg >= wm2200_ranges[i].range_min &&
		     reg <= wm2200_ranges[i].range_max))
			return true;

	switch (reg) {
	case WM2200_SOFTWARE_RESET:
	case WM2200_DEVICE_REVISION:
@@ -873,6 +987,400 @@ static int wm2200_reset(struct wm2200_priv *wm2200)
	}
}

static int wm2200_dsp_load(struct snd_soc_codec *codec, int base)
{
	const struct firmware *firmware;
	struct regmap *regmap = codec->control_data;
	unsigned int pos = 0;
	const struct wmfw_header *header;
	const struct wmfw_adsp1_sizes *adsp1_sizes;
	const struct wmfw_footer *footer;
	const struct wmfw_region *region;
	const char *file, *region_name;
	char *text;
	unsigned int dm, pm, zm, reg;
	int regions = 0;
	int ret, offset, type;

	switch (base) {
	case WM2200_DSP1_CONTROL_1:
		file = "wm2200-dsp1.wmfw";
		dm = WM2200_DSP1_DM_BASE;
		pm = WM2200_DSP1_PM_BASE;
		zm = WM2200_DSP1_ZM_BASE;
		break;
	case WM2200_DSP2_CONTROL_1:
		file = "wm2200-dsp2.wmfw";
		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;
	}

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

	header = (void*)&firmware->data[0];

	if (memcmp(&header->magic[0], "WMFW", 4) != 0) {
		dev_err(codec->dev, "%s: invalid magic\n", file);
		return -EINVAL;
	}

	if (header->ver != 0) {
		dev_err(codec->dev, "%s: unknown file format %d\n",
			file, header->ver);
		return -EINVAL;
	}

	if (le32_to_cpu(header->len) != sizeof(*header) +
	    sizeof(*adsp1_sizes) + sizeof(*footer)) {
		dev_err(codec->dev, "%s: unexpected header length %d\n",
			file, le32_to_cpu(header->len));
		return -EINVAL;
	}

	if (header->core != WMFW_ADSP1) {
		dev_err(codec->dev, "%s: invalid core %d\n",
			file, header->core);
		return -EINVAL;
	}

	adsp1_sizes = (void *)&(header[1]);
	footer = (void *)&(adsp1_sizes[1]);

	dev_dbg(codec->dev, "%s: %d DM, %d PM, %d ZM\n",
		file, le32_to_cpu(adsp1_sizes->dm),
		le32_to_cpu(adsp1_sizes->pm), le32_to_cpu(adsp1_sizes->zm));

	dev_dbg(codec->dev, "%s: timestamp %llu\n", file,
		le64_to_cpu(footer->timestamp));

	while (pos < firmware->size &&
	       pos - firmware->size > sizeof(*region)) {
		region = (void *)&(firmware->data[pos]);
		region_name = "Unknown";
		reg = 0;
		text = NULL;
		offset = le32_to_cpu(region->offset) & 0xffffff;
		type = be32_to_cpu(region->type) & 0xff;
		
		switch (type) {
		case WMFW_NAME_TEXT:
			region_name = "Firmware name";
			text = kzalloc(le32_to_cpu(region->len) + 1,
				       GFP_KERNEL);
			break;
		case WMFW_INFO_TEXT:
			region_name = "Information";
			text = kzalloc(le32_to_cpu(region->len) + 1,
				       GFP_KERNEL);
			break;
		case WMFW_ABSOLUTE:
			region_name = "Absolute";
			reg = offset;
			break;
		case WMFW_ADSP1_PM:
			region_name = "PM";
			reg = pm + (offset * 3);
			break;
		case WMFW_ADSP1_DM:
			region_name = "DM";
			reg = dm + (offset * 2);
			break;
		case WMFW_ADSP1_ZM:
			region_name = "ZM";
			reg = zm + (offset * 2);
			break;
		default:
			dev_warn(codec->dev,
				 "%s.%d: Unknown region type %x at %d(%x)\n",
				 file, regions, type, pos, pos);
			break;
		}

		dev_dbg(codec->dev, "%s.%d: %d bytes at %d in %s\n", file,
			regions, le32_to_cpu(region->len), offset,
			region_name);

		if (text) {
			memcpy(text, region->data, le32_to_cpu(region->len));
			dev_info(codec->dev, "%s: %s\n", file, text);
			kfree(text);
		}

		if (reg) {
			ret = regmap_raw_write(regmap, reg, region->data,
					       le32_to_cpu(region->len));
			if (ret != 0) {
				dev_err(codec->dev,
					"%s.%d: Failed to write %d bytes at %d in %s: %d\n",
					file, regions,
					le32_to_cpu(region->len), offset,
					region_name, ret);
				goto out;
			}
		}

		pos += le32_to_cpu(region->len) + sizeof(*region);
		regions++;
	}

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

out:
	release_firmware(firmware);
	
	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)
{
	struct snd_soc_codec *codec = w->codec;
	int base = w->reg - WM2200_DSP_CONTROL_30;
	int ret;

	switch (event) {
	case SND_SOC_DAPM_POST_PMU:
		ret = wm2200_dsp_load(codec, base);
		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,
				    WM2200_DSP1_CORE_ENA | WM2200_DSP1_START);
		break;

	case SND_SOC_DAPM_PRE_PMD:
		/* Halt the core */
		snd_soc_update_bits(codec, w->reg,
				    WM2200_DSP1_CORE_ENA | WM2200_DSP1_START,
				    0);

		snd_soc_update_bits(codec, base + WM2200_DSP_CONTROL_19,
				    WM2200_DSP1_WDMA_BUFFER_LENGTH_MASK, 0);
		break;

	default:
		break;
	}

	return 0;
}

static DECLARE_TLV_DB_SCALE(in_tlv, -6300, 100, 0);
static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0);
static DECLARE_TLV_DB_SCALE(out_tlv, -6400, 100, 0);
@@ -880,7 +1388,7 @@ static DECLARE_TLV_DB_SCALE(out_tlv, -6400, 100, 0);
static const char *wm2200_mixer_texts[] = {
	"None",
	"Tone Generator",
	"AEC loopback",
	"AEC Loopback",
	"IN1L",
	"IN1R",
	"IN2L",
@@ -976,6 +1484,20 @@ static int wm2200_mixer_values[] = {
	static WM2200_MUX_CTL_DECL(name##_in3); \
	static WM2200_MUX_CTL_DECL(name##_in4)

#define WM2200_DSP_ENUMS(name, base_reg) \
	static WM2200_MUX_ENUM_DECL(name##_aux1_enum, base_reg);     \
	static WM2200_MUX_ENUM_DECL(name##_aux2_enum, base_reg + 1); \
	static WM2200_MUX_ENUM_DECL(name##_aux3_enum, base_reg + 2); \
	static WM2200_MUX_ENUM_DECL(name##_aux4_enum, base_reg + 3); \
	static WM2200_MUX_ENUM_DECL(name##_aux5_enum, base_reg + 4); \
	static WM2200_MUX_ENUM_DECL(name##_aux6_enum, base_reg + 5); \
	static WM2200_MUX_CTL_DECL(name##_aux1); \
	static WM2200_MUX_CTL_DECL(name##_aux2); \
	static WM2200_MUX_CTL_DECL(name##_aux3); \
	static WM2200_MUX_CTL_DECL(name##_aux4); \
	static WM2200_MUX_CTL_DECL(name##_aux5); \
	static WM2200_MUX_CTL_DECL(name##_aux6);

static const struct snd_kcontrol_new wm2200_snd_controls[] = {
SOC_SINGLE("IN1 High Performance Switch", WM2200_IN1L_CONTROL,
	   WM2200_IN1_OSR_SHIFT, 1, 0),
@@ -1051,6 +1573,9 @@ WM2200_MIXER_ENUMS(DSP1R, WM2200_DSP1RMIX_INPUT_1_SOURCE);
WM2200_MIXER_ENUMS(DSP2L, WM2200_DSP2LMIX_INPUT_1_SOURCE);
WM2200_MIXER_ENUMS(DSP2R, WM2200_DSP2RMIX_INPUT_1_SOURCE);

WM2200_DSP_ENUMS(DSP1, WM2200_DSP1AUX1MIX_INPUT_1_SOURCE);
WM2200_DSP_ENUMS(DSP2, WM2200_DSP2AUX1MIX_INPUT_1_SOURCE);

WM2200_MIXER_ENUMS(LHPF1, WM2200_LHPF1MIX_INPUT_1_SOURCE);
WM2200_MIXER_ENUMS(LHPF2, WM2200_LHPF2MIX_INPUT_1_SOURCE);

@@ -1064,8 +1589,19 @@ WM2200_MIXER_ENUMS(LHPF2, WM2200_LHPF2MIX_INPUT_1_SOURCE);
	WM2200_MUX(name_str " Input 4", &name##_in4_mux), \
	SND_SOC_DAPM_MIXER(name_str " Mixer", SND_SOC_NOPM, 0, 0, NULL, 0)

#define WM2200_DSP_WIDGETS(name, name_str) \
	WM2200_MIXER_WIDGETS(name##L, name_str "L"), \
	WM2200_MIXER_WIDGETS(name##R, name_str "R"), \
	WM2200_MUX(name_str " Aux 1", &name##_aux1_mux), \
	WM2200_MUX(name_str " Aux 2", &name##_aux2_mux), \
	WM2200_MUX(name_str " Aux 3", &name##_aux3_mux), \
	WM2200_MUX(name_str " Aux 4", &name##_aux4_mux), \
	WM2200_MUX(name_str " Aux 5", &name##_aux5_mux), \
	WM2200_MUX(name_str " Aux 6", &name##_aux6_mux)

#define WM2200_MIXER_INPUT_ROUTES(name)	\
	{ name, "Tone Generator", "Tone Generator" }, \
	{ name, "AEC Loopback", "AEC Loopback" }, \
        { name, "IN1L", "IN1L PGA" }, \
        { name, "IN1R", "IN1R PGA" }, \
        { name, "IN2L", "IN2L PGA" }, \
@@ -1106,6 +1642,33 @@ WM2200_MIXER_ENUMS(LHPF2, WM2200_LHPF2MIX_INPUT_1_SOURCE);
	WM2200_MIXER_INPUT_ROUTES(name " Input 3"), \
	WM2200_MIXER_INPUT_ROUTES(name " Input 4")

#define WM2200_DSP_AUX_ROUTES(name) \
	{ name, NULL, name " Aux 1" }, \
	{ name, NULL, name " Aux 2" }, \
	{ name, NULL, name " Aux 3" }, \
	{ name, NULL, name " Aux 4" }, \
	{ name, NULL, name " Aux 5" }, \
	{ name, NULL, name " Aux 6" }, \
	WM2200_MIXER_INPUT_ROUTES(name " Aux 1"), \
	WM2200_MIXER_INPUT_ROUTES(name " Aux 2"), \
	WM2200_MIXER_INPUT_ROUTES(name " Aux 3"), \
	WM2200_MIXER_INPUT_ROUTES(name " Aux 4"), \
	WM2200_MIXER_INPUT_ROUTES(name " Aux 5"), \
	WM2200_MIXER_INPUT_ROUTES(name " Aux 6")

static const char *wm2200_aec_loopback_texts[] = {
	"OUT1L", "OUT1R", "OUT2L", "OUT2R",
};

static const struct soc_enum wm2200_aec_loopback =
	SOC_ENUM_SINGLE(WM2200_DAC_AEC_CONTROL_1,
			WM2200_AEC_LOOPBACK_SRC_SHIFT,
			ARRAY_SIZE(wm2200_aec_loopback_texts),
			wm2200_aec_loopback_texts);

static const struct snd_kcontrol_new wm2200_aec_loopback_mux =
	SOC_DAPM_ENUM("AEC Loopback", wm2200_aec_loopback);

static const struct snd_soc_dapm_widget wm2200_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("SYSCLK", WM2200_CLOCKING_3, WM2200_SYSCLK_ENA_SHIFT, 0,
		    NULL, 0),
@@ -1165,8 +1728,12 @@ SND_SOC_DAPM_PGA("LHPF1", WM2200_HPLPF1_1, WM2200_LHPF1_ENA_SHIFT, 0,
SND_SOC_DAPM_PGA("LHPF2", WM2200_HPLPF2_1, WM2200_LHPF2_ENA_SHIFT, 0,
		 NULL, 0),

SND_SOC_DAPM_PGA_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0, NULL, 0),
SND_SOC_DAPM_PGA_E("DSP2", SND_SOC_NOPM, 1, 0, NULL, 0, NULL, 0),
SND_SOC_DAPM_PGA_E("DSP1", WM2200_DSP1_CONTROL_30, WM2200_DSP1_SYS_ENA_SHIFT,
		   0, NULL, 0, wm2200_dsp_ev,
		   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_PGA_E("DSP2", WM2200_DSP2_CONTROL_30, WM2200_DSP2_SYS_ENA_SHIFT,
		   0, NULL, 0, wm2200_dsp_ev,
		   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),

SND_SOC_DAPM_AIF_OUT("AIF1TX1", "Capture", 0,
		    WM2200_AUDIO_IF_1_22, WM2200_AIF1TX1_ENA_SHIFT, 0),
@@ -1181,6 +1748,9 @@ SND_SOC_DAPM_AIF_OUT("AIF1TX5", "Capture", 4,
SND_SOC_DAPM_AIF_OUT("AIF1TX6", "Capture", 5,
		    WM2200_AUDIO_IF_1_22, WM2200_AIF1TX6_ENA_SHIFT, 0),

SND_SOC_DAPM_MUX("AEC Loopback", WM2200_DAC_AEC_CONTROL_1,
		 WM2200_AEC_LOOPBACK_ENA_SHIFT, 0, &wm2200_aec_loopback_mux),

SND_SOC_DAPM_PGA_S("OUT1L", 0, WM2200_OUTPUT_ENABLES,
		   WM2200_OUT1L_ENA_SHIFT, 0, NULL, 0),
SND_SOC_DAPM_PGA_S("OUT1R", 0, WM2200_OUTPUT_ENABLES,
@@ -1231,10 +1801,8 @@ WM2200_MIXER_WIDGETS(EQR, "EQR"),
WM2200_MIXER_WIDGETS(LHPF1, "LHPF1"),
WM2200_MIXER_WIDGETS(LHPF2, "LHPF2"),

WM2200_MIXER_WIDGETS(DSP1L, "DSP1L"),
WM2200_MIXER_WIDGETS(DSP1R, "DSP1R"),
WM2200_MIXER_WIDGETS(DSP2L, "DSP2L"),
WM2200_MIXER_WIDGETS(DSP2R, "DSP2R"),
WM2200_DSP_WIDGETS(DSP1, "DSP1"),
WM2200_DSP_WIDGETS(DSP2, "DSP2"),

WM2200_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"),
WM2200_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"),
@@ -1326,11 +1894,19 @@ static const struct snd_soc_dapm_route wm2200_dapm_routes[] = {
	{ "SPK", NULL, "OUT2L" },
	{ "SPK", NULL, "OUT2R" },

	{ "AEC Loopback", "OUT1L", "OUT1L" },
	{ "AEC Loopback", "OUT1R", "OUT1R" },
	{ "AEC Loopback", "OUT2L", "OUT2L" },
	{ "AEC Loopback", "OUT2R", "OUT2R" },

	WM2200_MIXER_ROUTES("DSP1", "DSP1L"),
	WM2200_MIXER_ROUTES("DSP1", "DSP1R"),
	WM2200_MIXER_ROUTES("DSP2", "DSP2L"),
	WM2200_MIXER_ROUTES("DSP2", "DSP2R"),

	WM2200_DSP_AUX_ROUTES("DSP1"),
	WM2200_DSP_AUX_ROUTES("DSP2"),

	WM2200_MIXER_ROUTES("OUT1L", "OUT1L"),
	WM2200_MIXER_ROUTES("OUT1R", "OUT1R"),
	WM2200_MIXER_ROUTES("OUT2L", "OUT2L"),
@@ -1968,12 +2544,15 @@ static const struct regmap_config wm2200_regmap = {
	.reg_bits = 16,
	.val_bits = 16,

	.max_register = WM2200_MAX_REGISTER,
	.max_register = WM2200_MAX_REGISTER + (ARRAY_SIZE(wm2200_ranges) *
					       WM2200_DSP_SPACING),
	.reg_defaults = wm2200_reg_defaults,
	.num_reg_defaults = ARRAY_SIZE(wm2200_reg_defaults),
	.volatile_reg = wm2200_volatile_register,
	.readable_reg = wm2200_readable_register,
	.cache_type = REGCACHE_RBTREE,
	.ranges = wm2200_ranges,
	.num_ranges = ARRAY_SIZE(wm2200_ranges),
};

static const unsigned int wm2200_dig_vu[] = {
@@ -2011,7 +2590,7 @@ static __devinit int wm2200_i2c_probe(struct i2c_client *i2c,
	wm2200->dev = &i2c->dev;
	init_completion(&wm2200->fll_lock);

	wm2200->regmap = regmap_init_i2c(i2c, &wm2200_regmap);
	wm2200->regmap = devm_regmap_init_i2c(i2c, &wm2200_regmap);
	if (IS_ERR(wm2200->regmap)) {
		ret = PTR_ERR(wm2200->regmap);
		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
@@ -2027,7 +2606,8 @@ static __devinit int wm2200_i2c_probe(struct i2c_client *i2c,
	for (i = 0; i < ARRAY_SIZE(wm2200->core_supplies); i++)
		wm2200->core_supplies[i].supply = wm2200_core_supply_names[i];

	ret = regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm2200->core_supplies),
	ret = devm_regulator_bulk_get(&i2c->dev,
				      ARRAY_SIZE(wm2200->core_supplies),
				      wm2200->core_supplies);
	if (ret != 0) {
		dev_err(&i2c->dev, "Failed to request core supplies: %d\n",
@@ -2044,8 +2624,9 @@ static __devinit int wm2200_i2c_probe(struct i2c_client *i2c,
	}

	if (wm2200->pdata.ldo_ena) {
		ret = gpio_request_one(wm2200->pdata.ldo_ena,
				       GPIOF_OUT_INIT_HIGH, "WM2200 LDOENA");
		ret = devm_gpio_request_one(&i2c->dev, wm2200->pdata.ldo_ena,
					    GPIOF_OUT_INIT_HIGH,
					    "WM2200 LDOENA");
		if (ret < 0) {
			dev_err(&i2c->dev, "Failed to request LDOENA %d: %d\n",
				wm2200->pdata.ldo_ena, ret);
@@ -2055,8 +2636,9 @@ static __devinit int wm2200_i2c_probe(struct i2c_client *i2c,
	}

	if (wm2200->pdata.reset) {
		ret = gpio_request_one(wm2200->pdata.reset,
				       GPIOF_OUT_INIT_HIGH, "WM2200 /RESET");
		ret = devm_gpio_request_one(&i2c->dev, wm2200->pdata.reset,
					    GPIOF_OUT_INIT_HIGH,
					    "WM2200 /RESET");
		if (ret < 0) {
			dev_err(&i2c->dev, "Failed to request /RESET %d: %d\n",
				wm2200->pdata.reset, ret);
@@ -2166,23 +2748,16 @@ static __devinit int wm2200_i2c_probe(struct i2c_client *i2c,
err_pm_runtime:
	pm_runtime_disable(&i2c->dev);
err_reset:
	if (wm2200->pdata.reset) {
	if (wm2200->pdata.reset)
		gpio_set_value_cansleep(wm2200->pdata.reset, 0);
		gpio_free(wm2200->pdata.reset);
	}
err_ldo:
	if (wm2200->pdata.ldo_ena) {
	if (wm2200->pdata.ldo_ena)
		gpio_set_value_cansleep(wm2200->pdata.ldo_ena, 0);
		gpio_free(wm2200->pdata.ldo_ena);
	}
err_enable:
	regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies),
			       wm2200->core_supplies);
err_core:
	regulator_bulk_free(ARRAY_SIZE(wm2200->core_supplies),
			    wm2200->core_supplies);
err_regmap:
	regmap_exit(wm2200->regmap);
err:
	return ret;
}
@@ -2194,17 +2769,10 @@ static __devexit int wm2200_i2c_remove(struct i2c_client *i2c)
	snd_soc_unregister_codec(&i2c->dev);
	if (i2c->irq)
		free_irq(i2c->irq, wm2200);
	if (wm2200->pdata.reset) {
	if (wm2200->pdata.reset)
		gpio_set_value_cansleep(wm2200->pdata.reset, 0);
		gpio_free(wm2200->pdata.reset);
	}
	if (wm2200->pdata.ldo_ena) {
	if (wm2200->pdata.ldo_ena)
		gpio_set_value_cansleep(wm2200->pdata.ldo_ena, 0);
		gpio_free(wm2200->pdata.ldo_ena);
	}
	regulator_bulk_free(ARRAY_SIZE(wm2200->core_supplies),
			    wm2200->core_supplies);
	regmap_exit(wm2200->regmap);

	return 0;
}
Loading