diff options
Diffstat (limited to 'sound/soc/codecs/wm_adsp.c')
| -rw-r--r-- | sound/soc/codecs/wm_adsp.c | 699 | 
1 files changed, 699 insertions, 0 deletions
| diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c new file mode 100644 index 000000000000..ffc89fab96fb --- /dev/null +++ b/sound/soc/codecs/wm_adsp.c @@ -0,0 +1,699 @@ +/* + * wm_adsp.c  --  Wolfson ADSP support + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown <[email protected]> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include <linux/mfd/arizona/registers.h> + +#include "wm_adsp.h" + +#define adsp_crit(_dsp, fmt, ...) \ +	dev_crit(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) +#define adsp_err(_dsp, fmt, ...) \ +	dev_err(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) +#define adsp_warn(_dsp, fmt, ...) \ +	dev_warn(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) +#define adsp_info(_dsp, fmt, ...) \ +	dev_info(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) +#define adsp_dbg(_dsp, fmt, ...) \ +	dev_dbg(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__) + +#define ADSP1_CONTROL_1                   0x00 +#define ADSP1_CONTROL_2                   0x02 +#define ADSP1_CONTROL_3                   0x03 +#define ADSP1_CONTROL_4                   0x04 +#define ADSP1_CONTROL_5                   0x06 +#define ADSP1_CONTROL_6                   0x07 +#define ADSP1_CONTROL_7                   0x08 +#define ADSP1_CONTROL_8                   0x09 +#define ADSP1_CONTROL_9                   0x0A +#define ADSP1_CONTROL_10                  0x0B +#define ADSP1_CONTROL_11                  0x0C +#define ADSP1_CONTROL_12                  0x0D +#define ADSP1_CONTROL_13                  0x0F +#define ADSP1_CONTROL_14                  0x10 +#define ADSP1_CONTROL_15                  0x11 +#define ADSP1_CONTROL_16                  0x12 +#define ADSP1_CONTROL_17                  0x13 +#define ADSP1_CONTROL_18                  0x14 +#define ADSP1_CONTROL_19                  0x16 +#define ADSP1_CONTROL_20                  0x17 +#define ADSP1_CONTROL_21                  0x18 +#define ADSP1_CONTROL_22                  0x1A +#define ADSP1_CONTROL_23                  0x1B +#define ADSP1_CONTROL_24                  0x1C +#define ADSP1_CONTROL_25                  0x1E +#define ADSP1_CONTROL_26                  0x20 +#define ADSP1_CONTROL_27                  0x21 +#define ADSP1_CONTROL_28                  0x22 +#define ADSP1_CONTROL_29                  0x23 +#define ADSP1_CONTROL_30                  0x24 +#define ADSP1_CONTROL_31                  0x26 + +/* + * ADSP1 Control 19 + */ +#define ADSP1_WDMA_BUFFER_LENGTH_MASK     0x00FF  /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define ADSP1_WDMA_BUFFER_LENGTH_SHIFT         0  /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define ADSP1_WDMA_BUFFER_LENGTH_WIDTH         8  /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ + + +/* + * ADSP1 Control 30 + */ +#define ADSP1_DBG_CLK_ENA                 0x0008  /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_MASK            0x0008  /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_SHIFT                3  /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_WIDTH                1  /* DSP1_DBG_CLK_ENA */ +#define ADSP1_SYS_ENA                     0x0004  /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_MASK                0x0004  /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_SHIFT                    2  /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_WIDTH                    1  /* DSP1_SYS_ENA */ +#define ADSP1_CORE_ENA                    0x0002  /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_MASK               0x0002  /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_SHIFT                   1  /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_WIDTH                   1  /* DSP1_CORE_ENA */ +#define ADSP1_START                       0x0001  /* DSP1_START */ +#define ADSP1_START_MASK                  0x0001  /* DSP1_START */ +#define ADSP1_START_SHIFT                      0  /* DSP1_START */ +#define ADSP1_START_WIDTH                      1  /* DSP1_START */ + +#define ADSP2_CONTROL  0 +#define ADSP2_CLOCKING 1 +#define ADSP2_STATUS1  4 + +/* + * ADSP2 Control + */ + +#define ADSP2_MEM_ENA                     0x0010  /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_MASK                0x0010  /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_SHIFT                    4  /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_WIDTH                    1  /* DSP1_MEM_ENA */ +#define ADSP2_SYS_ENA                     0x0004  /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_MASK                0x0004  /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_SHIFT                    2  /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_WIDTH                    1  /* DSP1_SYS_ENA */ +#define ADSP2_CORE_ENA                    0x0002  /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_MASK               0x0002  /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_SHIFT                   1  /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_WIDTH                   1  /* DSP1_CORE_ENA */ +#define ADSP2_START                       0x0001  /* DSP1_START */ +#define ADSP2_START_MASK                  0x0001  /* DSP1_START */ +#define ADSP2_START_SHIFT                      0  /* DSP1_START */ +#define ADSP2_START_WIDTH                      1  /* DSP1_START */ + +/* + * ADSP2 clocking + */ +#define ADSP2_CLK_SEL_MASK                0x0007  /* CLK_SEL_ENA */ +#define ADSP2_CLK_SEL_SHIFT                    0  /* CLK_SEL_ENA */ +#define ADSP2_CLK_SEL_WIDTH                    3  /* CLK_SEL_ENA */ + +/* + * ADSP2 Status 1 + */ +#define ADSP2_RAM_RDY                     0x0001 +#define ADSP2_RAM_RDY_MASK                0x0001 +#define ADSP2_RAM_RDY_SHIFT                    0 +#define ADSP2_RAM_RDY_WIDTH                    1 + + +static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp, +							int type) +{ +	int i; + +	for (i = 0; i < dsp->num_mems; i++) +		if (dsp->mem[i].type == type) +			return &dsp->mem[i]; + +	return NULL; +} + +static int wm_adsp_load(struct wm_adsp *dsp) +{ +	const struct firmware *firmware; +	struct regmap *regmap = dsp->regmap; +	unsigned int pos = 0; +	const struct wmfw_header *header; +	const struct wmfw_adsp1_sizes *adsp1_sizes; +	const struct wmfw_adsp2_sizes *adsp2_sizes; +	const struct wmfw_footer *footer; +	const struct wmfw_region *region; +	const struct wm_adsp_region *mem; +	const char *region_name; +	char *file, *text; +	unsigned int reg; +	int regions = 0; +	int ret, offset, type, sizes; + +	file = kzalloc(PAGE_SIZE, GFP_KERNEL); +	if (file == NULL) +		return -ENOMEM; + +	snprintf(file, PAGE_SIZE, "%s-dsp%d.wmfw", dsp->part, dsp->num); +	file[PAGE_SIZE - 1] = '\0'; + +	ret = request_firmware(&firmware, file, dsp->dev); +	if (ret != 0) { +		adsp_err(dsp, "Failed to request '%s'\n", file); +		goto out; +	} +	ret = -EINVAL; + +	pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); +	if (pos >= firmware->size) { +		adsp_err(dsp, "%s: file too short, %zu bytes\n", +			 file, firmware->size); +		goto out_fw; +	} + +	header = (void*)&firmware->data[0]; + +	if (memcmp(&header->magic[0], "WMFW", 4) != 0) { +		adsp_err(dsp, "%s: invalid magic\n", file); +		goto out_fw; +	} + +	if (header->ver != 0) { +		adsp_err(dsp, "%s: unknown file format %d\n", +			 file, header->ver); +		goto out_fw; +	} + +	if (header->core != dsp->type) { +		adsp_err(dsp, "%s: invalid core %d != %d\n", +			 file, header->core, dsp->type); +		goto out_fw; +	} + +	switch (dsp->type) { +	case WMFW_ADSP1: +		pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); +		adsp1_sizes = (void *)&(header[1]); +		footer = (void *)&(adsp1_sizes[1]); +		sizes = sizeof(*adsp1_sizes); + +		adsp_dbg(dsp, "%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)); +		break; + +	case WMFW_ADSP2: +		pos = sizeof(*header) + sizeof(*adsp2_sizes) + sizeof(*footer); +		adsp2_sizes = (void *)&(header[1]); +		footer = (void *)&(adsp2_sizes[1]); +		sizes = sizeof(*adsp2_sizes); + +		adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", +			 file, le32_to_cpu(adsp2_sizes->xm), +			 le32_to_cpu(adsp2_sizes->ym), +			 le32_to_cpu(adsp2_sizes->pm), +			 le32_to_cpu(adsp2_sizes->zm)); +		break; + +	default: +		BUG_ON(NULL == "Unknown DSP type"); +		goto out_fw; +	} + +	if (le32_to_cpu(header->len) != sizeof(*header) + +	    sizes + sizeof(*footer)) { +		adsp_err(dsp, "%s: unexpected header length %d\n", +			 file, le32_to_cpu(header->len)); +		goto out_fw; +	} + +	adsp_dbg(dsp, "%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; +		mem = wm_adsp_find_region(dsp, type); +		 +		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: +			BUG_ON(!mem); +			region_name = "PM"; +			reg = mem->base + (offset * 3); +			break; +		case WMFW_ADSP1_DM: +			BUG_ON(!mem); +			region_name = "DM"; +			reg = mem->base + (offset * 2); +			break; +		case WMFW_ADSP2_XM: +			BUG_ON(!mem); +			region_name = "XM"; +			reg = mem->base + (offset * 2); +			break; +		case WMFW_ADSP2_YM: +			BUG_ON(!mem); +			region_name = "YM"; +			reg = mem->base + (offset * 2); +			break; +		case WMFW_ADSP1_ZM: +			BUG_ON(!mem); +			region_name = "ZM"; +			reg = mem->base + (offset * 2); +			break; +		default: +			adsp_warn(dsp, +				  "%s.%d: Unknown region type %x at %d(%x)\n", +				  file, regions, type, pos, pos); +			break; +		} + +		adsp_dbg(dsp, "%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)); +			adsp_info(dsp, "%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) { +				adsp_err(dsp, +					"%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_fw; +			} +		} + +		pos += le32_to_cpu(region->len) + sizeof(*region); +		regions++; +	} +	 +	if (pos > firmware->size) +		adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", +			  file, regions, pos - firmware->size); + +out_fw: +	release_firmware(firmware); +out: +	kfree(file); + +	return ret; +} + +static int wm_adsp_load_coeff(struct wm_adsp *dsp) +{ +	struct regmap *regmap = dsp->regmap; +	struct wmfw_coeff_hdr *hdr; +	struct wmfw_coeff_item *blk; +	const struct firmware *firmware; +	const char *region_name; +	int ret, pos, blocks, type, offset, reg; +	char *file; + +	file = kzalloc(PAGE_SIZE, GFP_KERNEL); +	if (file == NULL) +		return -ENOMEM; + +	snprintf(file, PAGE_SIZE, "%s-dsp%d.bin", dsp->part, dsp->num); +	file[PAGE_SIZE - 1] = '\0'; + +	ret = request_firmware(&firmware, file, dsp->dev); +	if (ret != 0) { +		adsp_warn(dsp, "Failed to request '%s'\n", file); +		ret = 0; +		goto out; +	} +	ret = -EINVAL; + +	if (sizeof(*hdr) >= firmware->size) { +		adsp_err(dsp, "%s: file too short, %zu bytes\n", +			file, firmware->size); +		goto out_fw; +	} + +	hdr = (void*)&firmware->data[0]; +	if (memcmp(hdr->magic, "WMDR", 4) != 0) { +		adsp_err(dsp, "%s: invalid magic\n", file); +		return -EINVAL; +	} + +	adsp_dbg(dsp, "%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; + +		adsp_dbg(dsp, "%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); +		adsp_dbg(dsp, "%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: +			adsp_err(dsp, "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) { +				adsp_err(dsp, +					"%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) +		adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", +			  file, blocks, pos - firmware->size); + +out_fw: +	release_firmware(firmware); +out: +	kfree(file); +	return 0; +} + +int wm_adsp1_event(struct snd_soc_dapm_widget *w, +		   struct snd_kcontrol *kcontrol, +		   int event) +{ +	struct snd_soc_codec *codec = w->codec; +	struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec); +	struct wm_adsp *dsp = &dsps[w->shift]; +	int ret; + +	switch (event) { +	case SND_SOC_DAPM_POST_PMU: +		regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, +				   ADSP1_SYS_ENA, ADSP1_SYS_ENA); + +		ret = wm_adsp_load(dsp); +		if (ret != 0) +			goto err; + +		ret = wm_adsp_load_coeff(dsp); +		if (ret != 0) +			goto err; + +		/* Start the core running */ +		regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, +				   ADSP1_CORE_ENA | ADSP1_START, +				   ADSP1_CORE_ENA | ADSP1_START); +		break; + +	case SND_SOC_DAPM_PRE_PMD: +		/* Halt the core */ +		regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, +				   ADSP1_CORE_ENA | ADSP1_START, 0); + +		regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_19, +				   ADSP1_WDMA_BUFFER_LENGTH_MASK, 0); + +		regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, +				   ADSP1_SYS_ENA, 0); +		break; + +	default: +		break; +	} + +	return 0; + +err: +	regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, +			   ADSP1_SYS_ENA, 0); +	return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp1_event); + +static int wm_adsp2_ena(struct wm_adsp *dsp) +{ +	unsigned int val; +	int ret, count; + +	ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, +				 ADSP2_SYS_ENA, ADSP2_SYS_ENA); +	if (ret != 0) +		return ret; + +	/* Wait for the RAM to start, should be near instantaneous */ +	count = 0; +	do { +		ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, +				  &val); +		if (ret != 0) +			return ret; +	} while (!(val & ADSP2_RAM_RDY) && ++count < 10); + +	if (!(val & ADSP2_RAM_RDY)) { +		adsp_err(dsp, "Failed to start DSP RAM\n"); +		return -EBUSY; +	} + +	adsp_dbg(dsp, "RAM ready after %d polls\n", count); +	adsp_info(dsp, "RAM ready after %d polls\n", count); + +	return 0; +} + +int wm_adsp2_event(struct snd_soc_dapm_widget *w, +		   struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_codec *codec = w->codec; +	struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec); +	struct wm_adsp *dsp = &dsps[w->shift]; +	unsigned int val; +	int ret; + +	switch (event) { +	case SND_SOC_DAPM_POST_PMU: +		/* +		 * For simplicity set the DSP clock rate to be the +		 * SYSCLK rate rather than making it configurable. +		 */ +		ret = regmap_read(dsp->regmap, ARIZONA_SYSTEM_CLOCK_1, &val); +		if (ret != 0) { +			adsp_err(dsp, "Failed to read SYSCLK state: %d\n", +				 ret); +			return ret; +		} +		val = (val & ARIZONA_SYSCLK_FREQ_MASK) +			>> ARIZONA_SYSCLK_FREQ_SHIFT; + +		ret = regmap_update_bits(dsp->regmap, +					 dsp->base + ADSP2_CLOCKING, +					 ADSP2_CLK_SEL_MASK, val); +		if (ret != 0) { +			adsp_err(dsp, "Failed to set clock rate: %d\n", +				 ret); +			return ret; +		} + +		if (dsp->dvfs) { +			ret = regmap_read(dsp->regmap, +					  dsp->base + ADSP2_CLOCKING, &val); +			if (ret != 0) { +				dev_err(dsp->dev, +					"Failed to read clocking: %d\n", ret); +				return ret; +			} + +			if ((val & ADSP2_CLK_SEL_MASK) >= 3) { +				ret = regulator_enable(dsp->dvfs); +				if (ret != 0) { +					dev_err(dsp->dev, +						"Failed to enable supply: %d\n", +						ret); +					return ret; +				} + +				ret = regulator_set_voltage(dsp->dvfs, +							    1800000, +							    1800000); +				if (ret != 0) { +					dev_err(dsp->dev, +						"Failed to raise supply: %d\n", +						ret); +					return ret; +				} +			} +		} + +		ret = wm_adsp2_ena(dsp); +		if (ret != 0) +			return ret; + +		ret = wm_adsp_load(dsp); +		if (ret != 0) +			goto err; + +		ret = wm_adsp_load_coeff(dsp); +		if (ret != 0) +			goto err; + +		ret = regmap_update_bits(dsp->regmap, +					 dsp->base + ADSP2_CONTROL, +					 ADSP2_CORE_ENA | ADSP2_START, +					 ADSP2_CORE_ENA | ADSP2_START); +		if (ret != 0) +			goto err; +		break; + +	case SND_SOC_DAPM_PRE_PMD: +		regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, +				   ADSP2_SYS_ENA | ADSP2_CORE_ENA | +				   ADSP2_START, 0); + +		if (dsp->dvfs) { +			ret = regulator_set_voltage(dsp->dvfs, 1200000, +						    1800000); +			if (ret != 0) +				dev_warn(dsp->dev, +					 "Failed to lower supply: %d\n", +					 ret); + +			ret = regulator_disable(dsp->dvfs); +			if (ret != 0) +				dev_err(dsp->dev, +					"Failed to enable supply: %d\n", +					ret); +		} +		break; + +	default: +		break; +	} + +	return 0; +err: +	regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, +			   ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); +	return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp2_event); + +int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs) +{ +	int ret; + +	/* +	 * Disable the DSP memory by default when in reset for a small +	 * power saving. +	 */ +	ret = regmap_update_bits(adsp->regmap, adsp->base + ADSP2_CONTROL, +				 ADSP2_MEM_ENA, 0); +	if (ret != 0) { +		adsp_err(adsp, "Failed to clear memory retention: %d\n", ret); +		return ret; +	} + +	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; +		} + +		ret = regulator_enable(adsp->dvfs); +		if (ret != 0) { +			dev_err(adsp->dev, "Failed to enable DCVDD: %d\n", +				ret); +			return ret; +		} + +		ret = regulator_set_voltage(adsp->dvfs, 1200000, 1800000); +		if (ret != 0) { +			dev_err(adsp->dev, "Failed to initialise DVFS: %d\n", +				ret); +			return ret; +		} + +		ret = regulator_disable(adsp->dvfs); +		if (ret != 0) { +			dev_err(adsp->dev, "Failed to disable DCVDD: %d\n", +				ret); +			return ret; +		} +	} + +	return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_init); |