diff options
Diffstat (limited to 'sound/soc/codecs/cros_ec_codec.c')
| -rw-r--r-- | sound/soc/codecs/cros_ec_codec.c | 1128 | 
1 files changed, 875 insertions, 253 deletions
diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c index 3c1bd24a1057..7b17f39a6a10 100644 --- a/sound/soc/codecs/cros_ec_codec.c +++ b/sound/soc/codecs/cros_ec_codec.c @@ -1,15 +1,23 @@  // SPDX-License-Identifier: GPL-2.0  /* - * Driver for ChromeOS Embedded Controller codec. + * Copyright 2019 Google, Inc. + * + * ChromeOS Embedded Controller codec driver.   *   * This driver uses the cros-ec interface to communicate with the ChromeOS   * EC for audio function.   */ +#include <crypto/hash.h> +#include <crypto/sha.h>  #include <linux/delay.h>  #include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h>  #include <linux/kernel.h>  #include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h>  #include <linux/platform_data/cros_ec_commands.h>  #include <linux/platform_data/cros_ec_proto.h>  #include <linux/platform_device.h> @@ -18,92 +26,279 @@  #include <sound/soc.h>  #include <sound/tlv.h> -#define DRV_NAME "cros-ec-codec" - -/** - * struct cros_ec_codec_data - ChromeOS EC codec driver data. - * @dev:		Device structure used in sysfs. - * @ec_device:		cros_ec_device structure to talk to the physical device. - * @component:		Pointer to the component. - * @max_dmic_gain:	Maximum gain in dB supported by EC codec. - */ -struct cros_ec_codec_data { +struct cros_ec_codec_priv {  	struct device *dev;  	struct cros_ec_device *ec_device; -	struct snd_soc_component *component; -	unsigned int max_dmic_gain; + +	/* common */ +	uint32_t ec_capabilities; + +	uint64_t ec_shm_addr; +	uint32_t ec_shm_len; + +	uint64_t ap_shm_phys_addr; +	uint32_t ap_shm_len; +	uint64_t ap_shm_addr; +	uint64_t ap_shm_last_alloc; + +	/* DMIC */ +	atomic_t dmic_probed; + +	/* WoV */ +	bool wov_enabled; +	uint8_t *wov_audio_shm_p; +	uint32_t wov_audio_shm_len; +	uint8_t wov_audio_shm_type; +	uint8_t *wov_lang_shm_p; +	uint32_t wov_lang_shm_len; +	uint8_t wov_lang_shm_type; + +	struct mutex wov_dma_lock; +	uint8_t wov_buf[64000]; +	uint32_t wov_rp, wov_wp; +	size_t wov_dma_offset; +	bool wov_burst_read; +	struct snd_pcm_substream *wov_substream; +	struct delayed_work wov_copy_work; +	struct notifier_block wov_notifier;  }; -static const DECLARE_TLV_DB_SCALE(ec_mic_gain_tlv, 0, 100, 0); +static int ec_codec_capable(struct cros_ec_codec_priv *priv, uint8_t cap) +{ +	return priv->ec_capabilities & BIT(cap); +} -static int ec_command_get_gain(struct snd_soc_component *component, -			       struct ec_param_codec_i2s *param, -			       struct ec_codec_i2s_gain *resp) +static int send_ec_host_command(struct cros_ec_device *ec_dev, uint32_t cmd, +				uint8_t *out, size_t outsize, +				uint8_t *in, size_t insize)  { -	struct cros_ec_codec_data *codec_data = -		snd_soc_component_get_drvdata(component); -	struct cros_ec_device *ec_device = codec_data->ec_device; -	u8 buffer[sizeof(struct cros_ec_command) + -		  max(sizeof(struct ec_param_codec_i2s), -		      sizeof(struct ec_codec_i2s_gain))]; -	struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;  	int ret; +	struct cros_ec_command *msg; + +	msg = kmalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); +	if (!msg) +		return -ENOMEM;  	msg->version = 0; -	msg->command = EC_CMD_CODEC_I2S; -	msg->outsize = sizeof(struct ec_param_codec_i2s); -	msg->insize = sizeof(struct ec_codec_i2s_gain); +	msg->command = cmd; +	msg->outsize = outsize; +	msg->insize = insize; -	memcpy(msg->data, param, msg->outsize); +	if (outsize) +		memcpy(msg->data, out, outsize); -	ret = cros_ec_cmd_xfer_status(ec_device, msg); -	if (ret > 0) -		memcpy(resp, msg->data, msg->insize); +	ret = cros_ec_cmd_xfer_status(ec_dev, msg); +	if (ret < 0) +		goto error; + +	if (insize) +		memcpy(in, msg->data, insize); +	ret = 0; +error: +	kfree(msg);  	return ret;  } -/* - * Wrapper for EC command without response. - */ -static int ec_command_no_resp(struct snd_soc_component *component, -			      struct ec_param_codec_i2s *param) +static int calculate_sha256(struct cros_ec_codec_priv *priv, +			    uint8_t *buf, uint32_t size, uint8_t *digest)  { -	struct cros_ec_codec_data *codec_data = +	struct crypto_shash *tfm; + +	tfm = crypto_alloc_shash("sha256", CRYPTO_ALG_TYPE_SHASH, 0); +	if (IS_ERR(tfm)) { +		dev_err(priv->dev, "can't alloc shash\n"); +		return PTR_ERR(tfm); +	} + +	{ +		SHASH_DESC_ON_STACK(desc, tfm); + +		desc->tfm = tfm; + +		crypto_shash_digest(desc, buf, size, digest); +		shash_desc_zero(desc); +	} + +	crypto_free_shash(tfm); + +#ifdef DEBUG +	{ +		char digest_str[65]; + +		bin2hex(digest_str, digest, 32); +		digest_str[64] = 0; +		dev_dbg(priv->dev, "hash=%s\n", digest_str); +	} +#endif + +	return 0; +} + +static int dmic_get_gain(struct snd_kcontrol *kcontrol, +			 struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_component *component = +		snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv =  		snd_soc_component_get_drvdata(component); -	struct cros_ec_device *ec_device = codec_data->ec_device; -	u8 buffer[sizeof(struct cros_ec_command) + -		  sizeof(struct ec_param_codec_i2s)]; -	struct cros_ec_command *msg = (struct cros_ec_command *)&buffer; +	struct ec_param_ec_codec_dmic p; +	struct ec_response_ec_codec_dmic_get_gain_idx r; +	int ret; -	msg->version = 0; -	msg->command = EC_CMD_CODEC_I2S; -	msg->outsize = sizeof(struct ec_param_codec_i2s); -	msg->insize = 0; +	p.cmd = EC_CODEC_DMIC_GET_GAIN_IDX; +	p.get_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_0; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret < 0) +		return ret; +	ucontrol->value.integer.value[0] = r.gain; -	memcpy(msg->data, param, msg->outsize); +	p.cmd = EC_CODEC_DMIC_GET_GAIN_IDX; +	p.get_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_1; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret < 0) +		return ret; +	ucontrol->value.integer.value[1] = r.gain; -	return cros_ec_cmd_xfer_status(ec_device, msg); +	return 0;  } -static int set_i2s_config(struct snd_soc_component *component, -			  enum ec_i2s_config i2s_config) +static int dmic_put_gain(struct snd_kcontrol *kcontrol, +			 struct snd_ctl_elem_value *ucontrol)  { -	struct ec_param_codec_i2s param; +	struct snd_soc_component *component = +		snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct soc_mixer_control *control = +		(struct soc_mixer_control *)kcontrol->private_value; +	int max_dmic_gain = control->max; +	int left = ucontrol->value.integer.value[0]; +	int right = ucontrol->value.integer.value[1]; +	struct ec_param_ec_codec_dmic p; +	int ret; + +	if (left > max_dmic_gain || right > max_dmic_gain) +		return -EINVAL; -	dev_dbg(component->dev, "%s set I2S format to %u\n", __func__, -		i2s_config); +	dev_dbg(component->dev, "set mic gain to %u, %u\n", left, right); -	param.cmd = EC_CODEC_I2S_SET_CONFIG; -	param.i2s_config = i2s_config; +	p.cmd = EC_CODEC_DMIC_SET_GAIN_IDX; +	p.set_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_0; +	p.set_gain_idx_param.gain = left; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), NULL, 0); +	if (ret < 0) +		return ret; + +	p.cmd = EC_CODEC_DMIC_SET_GAIN_IDX; +	p.set_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_1; +	p.set_gain_idx_param.gain = right; +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static const DECLARE_TLV_DB_SCALE(dmic_gain_tlv, 0, 100, 0); + +enum { +	DMIC_CTL_GAIN = 0, +}; + +static struct snd_kcontrol_new dmic_controls[] = { +	[DMIC_CTL_GAIN] = +		SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM, +				   0, 0, 0, dmic_get_gain, dmic_put_gain, +				   dmic_gain_tlv), +}; + +static int dmic_probe(struct snd_soc_component *component) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct device *dev = priv->dev; +	struct soc_mixer_control *control; +	struct ec_param_ec_codec_dmic p; +	struct ec_response_ec_codec_dmic_get_max_gain r; +	int ret; + +	if (!atomic_add_unless(&priv->dmic_probed, 1, 1)) +		return 0; + +	p.cmd = EC_CODEC_DMIC_GET_MAX_GAIN; + +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret < 0) { +		dev_warn(dev, "get_max_gain() unsupported\n"); +		return 0; +	} + +	dev_dbg(dev, "max gain = %d\n", r.max_gain); + +	control = (struct soc_mixer_control *) +		dmic_controls[DMIC_CTL_GAIN].private_value; +	control->max = r.max_gain; +	control->platform_max = r.max_gain; -	return ec_command_no_resp(component, ¶m); +	return snd_soc_add_component_controls(component, +			&dmic_controls[DMIC_CTL_GAIN], 1);  } -static int cros_ec_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +static int i2s_rx_hw_params(struct snd_pcm_substream *substream, +			    struct snd_pcm_hw_params *params, +			    struct snd_soc_dai *dai)  {  	struct snd_soc_component *component = dai->component; -	enum ec_i2s_config i2s_config; +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_i2s_rx p; +	enum ec_codec_i2s_rx_sample_depth depth; +	int ret; + +	if (params_rate(params) != 48000) +		return -EINVAL; + +	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_S16_LE: +		depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_16; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_24; +		break; +	default: +		return -EINVAL; +	} + +	dev_dbg(component->dev, "set depth to %u\n", depth); + +	p.cmd = EC_CODEC_I2S_RX_SET_SAMPLE_DEPTH; +	p.set_sample_depth_param.depth = depth; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				   (uint8_t *)&p, sizeof(p), NULL, 0); +	if (ret < 0) +		return ret; + +	dev_dbg(component->dev, "set bclk to %u\n", +		snd_soc_params_to_bclk(params)); + +	p.cmd = EC_CODEC_I2S_RX_SET_BCLK; +	p.set_bclk_param.bclk = snd_soc_params_to_bclk(params); +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +	struct snd_soc_component *component = dai->component; +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_i2s_rx p; +	enum ec_codec_i2s_rx_daifmt daifmt;  	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {  	case SND_SOC_DAIFMT_CBS_CFS: @@ -121,300 +316,727 @@ static int cros_ec_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)  	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {  	case SND_SOC_DAIFMT_I2S: -		i2s_config = EC_DAI_FMT_I2S; +		daifmt = EC_CODEC_I2S_RX_DAIFMT_I2S;  		break; -  	case SND_SOC_DAIFMT_RIGHT_J: -		i2s_config = EC_DAI_FMT_RIGHT_J; +		daifmt = EC_CODEC_I2S_RX_DAIFMT_RIGHT_J;  		break; -  	case SND_SOC_DAIFMT_LEFT_J: -		i2s_config = EC_DAI_FMT_LEFT_J; +		daifmt = EC_CODEC_I2S_RX_DAIFMT_LEFT_J;  		break; +	default: +		return -EINVAL; +	} -	case SND_SOC_DAIFMT_DSP_A: -		i2s_config = EC_DAI_FMT_PCM_A; -		break; +	dev_dbg(component->dev, "set format to %u\n", daifmt); -	case SND_SOC_DAIFMT_DSP_B: -		i2s_config = EC_DAI_FMT_PCM_B; -		break; +	p.cmd = EC_CODEC_I2S_RX_SET_DAIFMT; +	p.set_daifmt_param.daifmt = daifmt; +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static const struct snd_soc_dai_ops i2s_rx_dai_ops = { +	.hw_params = i2s_rx_hw_params, +	.set_fmt = i2s_rx_set_fmt, +}; +static int i2s_rx_event(struct snd_soc_dapm_widget *w, +			struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_component *component = +		snd_soc_dapm_to_component(w->dapm); +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_i2s_rx p; + +	switch (event) { +	case SND_SOC_DAPM_PRE_PMU: +		dev_dbg(component->dev, "enable I2S RX\n"); +		p.cmd = EC_CODEC_I2S_RX_ENABLE; +		break; +	case SND_SOC_DAPM_PRE_PMD: +		dev_dbg(component->dev, "disable I2S RX\n"); +		p.cmd = EC_CODEC_I2S_RX_DISABLE; +		break;  	default: -		return -EINVAL; +		return 0;  	} -	return set_i2s_config(component, i2s_config); +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static struct snd_soc_dapm_widget i2s_rx_dapm_widgets[] = { +	SND_SOC_DAPM_INPUT("DMIC"), +	SND_SOC_DAPM_SUPPLY("I2S RX Enable", SND_SOC_NOPM, 0, 0, i2s_rx_event, +			    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +	SND_SOC_DAPM_AIF_OUT("I2S RX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static struct snd_soc_dapm_route i2s_rx_dapm_routes[] = { +	{"I2S RX", NULL, "DMIC"}, +	{"I2S RX", NULL, "I2S RX Enable"}, +}; + +static struct snd_soc_dai_driver i2s_rx_dai_driver = { +	.name = "EC Codec I2S RX", +	.capture = { +		.stream_name = "I2S Capture", +		.channels_min = 2, +		.channels_max = 2, +		.rates = SNDRV_PCM_RATE_48000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | +			SNDRV_PCM_FMTBIT_S24_LE, +	}, +	.ops = &i2s_rx_dai_ops, +}; + +static int i2s_rx_probe(struct snd_soc_component *component) +{ +	return dmic_probe(component);  } -static int set_i2s_sample_depth(struct snd_soc_component *component, -				enum ec_sample_depth_value depth) +static const struct snd_soc_component_driver i2s_rx_component_driver = { +	.probe			= i2s_rx_probe, +	.dapm_widgets		= i2s_rx_dapm_widgets, +	.num_dapm_widgets	= ARRAY_SIZE(i2s_rx_dapm_widgets), +	.dapm_routes		= i2s_rx_dapm_routes, +	.num_dapm_routes	= ARRAY_SIZE(i2s_rx_dapm_routes), +}; + +static void *wov_map_shm(struct cros_ec_codec_priv *priv, +			 uint8_t shm_id, uint32_t *len, uint8_t *type)  { -	struct ec_param_codec_i2s param; +	struct ec_param_ec_codec p; +	struct ec_response_ec_codec_get_shm_addr r; +	uint32_t req, offset; + +	p.cmd = EC_CODEC_GET_SHM_ADDR; +	p.get_shm_addr_param.shm_id = shm_id; +	if (send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC, +				 (uint8_t *)&p, sizeof(p), +				 (uint8_t *)&r, sizeof(r)) < 0) { +		dev_err(priv->dev, "failed to EC_CODEC_GET_SHM_ADDR\n"); +		return NULL; +	} -	dev_dbg(component->dev, "%s set depth to %u\n", __func__, depth); +	dev_dbg(priv->dev, "phys_addr=%#llx, len=%#x\n", r.phys_addr, r.len); + +	*len = r.len; +	*type = r.type; + +	switch (r.type) { +	case EC_CODEC_SHM_TYPE_EC_RAM: +		return (void __force *)devm_ioremap_wc(priv->dev, +				r.phys_addr + priv->ec_shm_addr, r.len); +	case EC_CODEC_SHM_TYPE_SYSTEM_RAM: +		if (r.phys_addr) { +			dev_err(priv->dev, "unknown status\n"); +			return NULL; +		} + +		req = round_up(r.len, PAGE_SIZE); +		dev_dbg(priv->dev, "round up from %u to %u\n", r.len, req); + +		if (priv->ap_shm_last_alloc + req > +		    priv->ap_shm_phys_addr + priv->ap_shm_len) { +			dev_err(priv->dev, "insufficient space for AP SHM\n"); +			return NULL; +		} + +		dev_dbg(priv->dev, "alloc AP SHM addr=%#llx, len=%#x\n", +			priv->ap_shm_last_alloc, req); + +		p.cmd = EC_CODEC_SET_SHM_ADDR; +		p.set_shm_addr_param.phys_addr = priv->ap_shm_last_alloc; +		p.set_shm_addr_param.len = req; +		p.set_shm_addr_param.shm_id = shm_id; +		if (send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC, +					 (uint8_t *)&p, sizeof(p), +					 NULL, 0) < 0) { +			dev_err(priv->dev, "failed to EC_CODEC_SET_SHM_ADDR\n"); +			return NULL; +		} + +		/* +		 * Note: EC codec only requests for `r.len' but we allocate +		 * round up PAGE_SIZE `req'. +		 */ +		offset = priv->ap_shm_last_alloc - priv->ap_shm_phys_addr; +		priv->ap_shm_last_alloc += req; + +		return (void *)(uintptr_t)(priv->ap_shm_addr + offset); +	default: +		return NULL; +	} +} -	param.cmd = EC_CODEC_SET_SAMPLE_DEPTH; -	param.depth = depth; +static bool wov_queue_full(struct cros_ec_codec_priv *priv) +{ +	return ((priv->wov_wp + 1) % sizeof(priv->wov_buf)) == priv->wov_rp; +} -	return ec_command_no_resp(component, ¶m); +static size_t wov_queue_size(struct cros_ec_codec_priv *priv) +{ +	if (priv->wov_wp >= priv->wov_rp) +		return priv->wov_wp - priv->wov_rp; +	else +		return sizeof(priv->wov_buf) - priv->wov_rp + priv->wov_wp;  } -static int set_i2s_bclk(struct snd_soc_component *component, uint32_t bclk) +static void wov_queue_dequeue(struct cros_ec_codec_priv *priv, size_t len)  { -	struct ec_param_codec_i2s param; +	struct snd_pcm_runtime *runtime = priv->wov_substream->runtime; +	size_t req; + +	while (len) { +		req = min(len, runtime->dma_bytes - priv->wov_dma_offset); +		if (priv->wov_wp >= priv->wov_rp) +			req = min(req, (size_t)priv->wov_wp - priv->wov_rp); +		else +			req = min(req, sizeof(priv->wov_buf) - priv->wov_rp); -	dev_dbg(component->dev, "%s set i2s bclk to %u\n", __func__, bclk); +		memcpy(runtime->dma_area + priv->wov_dma_offset, +		       priv->wov_buf + priv->wov_rp, req); -	param.cmd = EC_CODEC_I2S_SET_BCLK; -	param.bclk = bclk; +		priv->wov_dma_offset += req; +		if (priv->wov_dma_offset == runtime->dma_bytes) +			priv->wov_dma_offset = 0; -	return ec_command_no_resp(component, ¶m); +		priv->wov_rp += req; +		if (priv->wov_rp == sizeof(priv->wov_buf)) +			priv->wov_rp = 0; + +		len -= req; +	} + +	snd_pcm_period_elapsed(priv->wov_substream);  } -static int cros_ec_i2s_hw_params(struct snd_pcm_substream *substream, -				 struct snd_pcm_hw_params *params, -				 struct snd_soc_dai *dai) +static void wov_queue_try_dequeue(struct cros_ec_codec_priv *priv)  { -	struct snd_soc_component *component = dai->component; -	unsigned int rate, bclk; -	int ret; +	size_t period_bytes = snd_pcm_lib_period_bytes(priv->wov_substream); -	rate = params_rate(params); -	if (rate != 48000) -		return -EINVAL; +	while (period_bytes && wov_queue_size(priv) >= period_bytes) { +		wov_queue_dequeue(priv, period_bytes); +		period_bytes = snd_pcm_lib_period_bytes(priv->wov_substream); +	} +} -	switch (params_format(params)) { -	case SNDRV_PCM_FORMAT_S16_LE: -		ret = set_i2s_sample_depth(component, EC_CODEC_SAMPLE_DEPTH_16); -		break; -	case SNDRV_PCM_FORMAT_S24_LE: -		ret = set_i2s_sample_depth(component, EC_CODEC_SAMPLE_DEPTH_24); -		break; -	default: -		return -EINVAL; +static void wov_queue_enqueue(struct cros_ec_codec_priv *priv, +			      uint8_t *addr, size_t len, bool iomem) +{ +	size_t req; + +	while (len) { +		if (wov_queue_full(priv)) { +			wov_queue_try_dequeue(priv); + +			if (wov_queue_full(priv)) { +				dev_err(priv->dev, "overrun detected\n"); +				return; +			} +		} + +		if (priv->wov_wp >= priv->wov_rp) +			req = sizeof(priv->wov_buf) - priv->wov_wp; +		else +			/* Note: waste 1-byte to differentiate full and empty */ +			req = priv->wov_rp - priv->wov_wp - 1; +		req = min(req, len); + +		if (iomem) +			memcpy_fromio(priv->wov_buf + priv->wov_wp, +				      (void __force __iomem *)addr, req); +		else +			memcpy(priv->wov_buf + priv->wov_wp, addr, req); + +		priv->wov_wp += req; +		if (priv->wov_wp == sizeof(priv->wov_buf)) +			priv->wov_wp = 0; + +		addr += req; +		len -= req;  	} -	if (ret < 0) + +	wov_queue_try_dequeue(priv); +} + +static int wov_read_audio_shm(struct cros_ec_codec_priv *priv) +{ +	struct ec_param_ec_codec_wov p; +	struct ec_response_ec_codec_wov_read_audio_shm r; +	int ret; + +	p.cmd = EC_CODEC_WOV_READ_AUDIO_SHM; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret) { +		dev_err(priv->dev, "failed to EC_CODEC_WOV_READ_AUDIO_SHM\n");  		return ret; +	} -	bclk = snd_soc_params_to_bclk(params); -	return set_i2s_bclk(component, bclk); +	if (!r.len) +		dev_dbg(priv->dev, "no data, sleep\n"); +	else +		wov_queue_enqueue(priv, priv->wov_audio_shm_p + r.offset, r.len, +			priv->wov_audio_shm_type == EC_CODEC_SHM_TYPE_EC_RAM); +	return -EAGAIN;  } -static const struct snd_soc_dai_ops cros_ec_i2s_dai_ops = { -	.hw_params = cros_ec_i2s_hw_params, -	.set_fmt = cros_ec_i2s_set_dai_fmt, -}; +static int wov_read_audio(struct cros_ec_codec_priv *priv) +{ +	struct ec_param_ec_codec_wov p; +	struct ec_response_ec_codec_wov_read_audio r; +	int remain = priv->wov_burst_read ? 16000 : 320; +	int ret; -static struct snd_soc_dai_driver cros_ec_dai[] = { -	{ -		.name = "cros_ec_codec I2S", -		.id = 0, -		.capture = { -			.stream_name = "I2S Capture", -			.channels_min = 2, -			.channels_max = 2, -			.rates = SNDRV_PCM_RATE_48000, -			.formats = SNDRV_PCM_FMTBIT_S16_LE | -				   SNDRV_PCM_FMTBIT_S24_LE, -		}, -		.ops = &cros_ec_i2s_dai_ops, +	while (remain >= 0) { +		p.cmd = EC_CODEC_WOV_READ_AUDIO; +		ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +					   (uint8_t *)&p, sizeof(p), +					   (uint8_t *)&r, sizeof(r)); +		if (ret) { +			dev_err(priv->dev, +				"failed to EC_CODEC_WOV_READ_AUDIO\n"); +			return ret; +		} + +		if (!r.len) { +			dev_dbg(priv->dev, "no data, sleep\n"); +			priv->wov_burst_read = false; +			break; +		} + +		wov_queue_enqueue(priv, r.buf, r.len, false); +		remain -= r.len;  	} -}; -static int get_ec_mic_gain(struct snd_soc_component *component, -			   u8 *left, u8 *right) +	return -EAGAIN; +} + +static void wov_copy_work(struct work_struct *w)  { -	struct ec_param_codec_i2s param; -	struct ec_codec_i2s_gain resp; +	struct cros_ec_codec_priv *priv = +		container_of(w, struct cros_ec_codec_priv, wov_copy_work.work);  	int ret; -	param.cmd = EC_CODEC_GET_GAIN; +	mutex_lock(&priv->wov_dma_lock); +	if (!priv->wov_substream) { +		dev_warn(priv->dev, "no pcm substream\n"); +		goto leave; +	} -	ret = ec_command_get_gain(component, ¶m, &resp); -	if (ret < 0) -		return ret; +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) +		ret = wov_read_audio_shm(priv); +	else +		ret = wov_read_audio(priv); + +	if (ret == -EAGAIN) +		schedule_delayed_work(&priv->wov_copy_work, +				      msecs_to_jiffies(10)); +	else if (ret) +		dev_err(priv->dev, "failed to read audio data\n"); +leave: +	mutex_unlock(&priv->wov_dma_lock); +} -	*left = resp.left; -	*right = resp.right; +static int wov_enable_get(struct snd_kcontrol *kcontrol, +			  struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv = snd_soc_component_get_drvdata(c); +	ucontrol->value.integer.value[0] = priv->wov_enabled;  	return 0;  } -static int mic_gain_get(struct snd_kcontrol *kcontrol, -			struct snd_ctl_elem_value *ucontrol) +static int wov_enable_put(struct snd_kcontrol *kcontrol, +			  struct snd_ctl_elem_value *ucontrol)  { -	struct snd_soc_component *component = -		snd_soc_kcontrol_component(kcontrol); -	u8 left, right; +	struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv = snd_soc_component_get_drvdata(c); +	int enabled = ucontrol->value.integer.value[0]; +	struct ec_param_ec_codec_wov p;  	int ret; -	ret = get_ec_mic_gain(component, &left, &right); -	if (ret) -		return ret; - -	ucontrol->value.integer.value[0] = left; -	ucontrol->value.integer.value[1] = right; +	if (priv->wov_enabled != enabled) { +		if (enabled) +			p.cmd = EC_CODEC_WOV_ENABLE; +		else +			p.cmd = EC_CODEC_WOV_DISABLE; + +		ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +					   (uint8_t *)&p, sizeof(p), NULL, 0); +		if (ret) { +			dev_err(priv->dev, "failed to %s wov\n", +				enabled ? "enable" : "disable"); +			return ret; +		} + +		priv->wov_enabled = enabled; +	}  	return 0;  } -static int set_ec_mic_gain(struct snd_soc_component *component, -			   u8 left, u8 right) +static int wov_set_lang_shm(struct cros_ec_codec_priv *priv, +			    uint8_t *buf, size_t size, uint8_t *digest)  { -	struct ec_param_codec_i2s param; +	struct ec_param_ec_codec_wov p; +	struct ec_param_ec_codec_wov_set_lang_shm *pp = &p.set_lang_shm_param; +	int ret; -	dev_dbg(component->dev, "%s set mic gain to %u, %u\n", -		__func__, left, right); +	if (size > priv->wov_lang_shm_len) { +		dev_err(priv->dev, "no enough SHM size: %d\n", +			priv->wov_lang_shm_len); +		return -EIO; +	} -	param.cmd = EC_CODEC_SET_GAIN; -	param.gain.left = left; -	param.gain.right = right; +	switch (priv->wov_lang_shm_type) { +	case EC_CODEC_SHM_TYPE_EC_RAM: +		memcpy_toio((void __force __iomem *)priv->wov_lang_shm_p, +			    buf, size); +		memset_io((void __force __iomem *)priv->wov_lang_shm_p + size, +			  0, priv->wov_lang_shm_len - size); +		break; +	case EC_CODEC_SHM_TYPE_SYSTEM_RAM: +		memcpy(priv->wov_lang_shm_p, buf, size); +		memset(priv->wov_lang_shm_p + size, 0, +		       priv->wov_lang_shm_len - size); -	return ec_command_no_resp(component, ¶m); +		/* make sure write to memory before calling host command */ +		wmb(); +		break; +	} + +	p.cmd = EC_CODEC_WOV_SET_LANG_SHM; +	memcpy(pp->hash, digest, SHA256_DIGEST_SIZE); +	pp->total_len = size; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +				   (uint8_t *)&p, sizeof(p), NULL, 0); +	if (ret) { +		dev_err(priv->dev, "failed to EC_CODEC_WOV_SET_LANG_SHM\n"); +		return ret; +	} + +	return 0;  } -static int mic_gain_put(struct snd_kcontrol *kcontrol, -			struct snd_ctl_elem_value *ucontrol) +static int wov_set_lang(struct cros_ec_codec_priv *priv, +			uint8_t *buf, size_t size, uint8_t *digest)  { -	struct snd_soc_component *component = -		snd_soc_kcontrol_component(kcontrol); -	struct cros_ec_codec_data *codec_data = -		snd_soc_component_get_drvdata(component); -	int left = ucontrol->value.integer.value[0]; -	int right = ucontrol->value.integer.value[1]; -	unsigned int max_dmic_gain = codec_data->max_dmic_gain; +	struct ec_param_ec_codec_wov p; +	struct ec_param_ec_codec_wov_set_lang *pp = &p.set_lang_param; +	size_t i, req; +	int ret; -	if (left > max_dmic_gain || right > max_dmic_gain) -		return -EINVAL; +	for (i = 0; i < size; i += req) { +		req = min(size - i, ARRAY_SIZE(pp->buf)); + +		p.cmd = EC_CODEC_WOV_SET_LANG; +		memcpy(pp->hash, digest, SHA256_DIGEST_SIZE); +		pp->total_len = size; +		pp->offset = i; +		memcpy(pp->buf, buf + i, req); +		pp->len = req; +		ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +					   (uint8_t *)&p, sizeof(p), NULL, 0); +		if (ret) { +			dev_err(priv->dev, "failed to EC_CODEC_WOV_SET_LANG\n"); +			return ret; +		} +	} -	return set_ec_mic_gain(component, (u8)left, (u8)right); +	return 0;  } -static struct snd_kcontrol_new mic_gain_control = -	SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM, 0, 0, 0, -			   mic_gain_get, mic_gain_put, ec_mic_gain_tlv); - -static int enable_i2s(struct snd_soc_component *component, int enable) +static int wov_hotword_model_put(struct snd_kcontrol *kcontrol, +				 const unsigned int __user *bytes, +				 unsigned int size)  { -	struct ec_param_codec_i2s param; +	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_wov p; +	struct ec_response_ec_codec_wov_get_lang r; +	uint8_t digest[SHA256_DIGEST_SIZE]; +	uint8_t *buf; +	int ret; + +	/* Skips the TLV header. */ +	bytes += 2; +	size -= 8; + +	dev_dbg(priv->dev, "%s: size=%d\n", __func__, size); + +	buf = memdup_user(bytes, size); +	if (IS_ERR(buf)) +		return PTR_ERR(buf); + +	ret = calculate_sha256(priv, buf, size, digest); +	if (ret) +		goto leave; + +	p.cmd = EC_CODEC_WOV_GET_LANG; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret) +		goto leave; -	dev_dbg(component->dev, "%s set i2s to %u\n", __func__, enable); +	if (memcmp(digest, r.hash, SHA256_DIGEST_SIZE) == 0) { +		dev_dbg(priv->dev, "not updated"); +		goto leave; +	} -	param.cmd = EC_CODEC_I2S_ENABLE; -	param.i2s_enable = enable; +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_LANG_SHM)) +		ret = wov_set_lang_shm(priv, buf, size, digest); +	else +		ret = wov_set_lang(priv, buf, size, digest); -	return ec_command_no_resp(component, ¶m); +leave: +	kfree(buf); +	return ret;  } -static int cros_ec_i2s_enable_event(struct snd_soc_dapm_widget *w, -				    struct snd_kcontrol *kcontrol, int event) +static struct snd_kcontrol_new wov_controls[] = { +	SOC_SINGLE_BOOL_EXT("Wake-on-Voice Switch", 0, +			    wov_enable_get, wov_enable_put), +	SND_SOC_BYTES_TLV("Hotword Model", 0x11000, NULL, +			  wov_hotword_model_put), +}; + +static struct snd_soc_dai_driver wov_dai_driver = { +	.name = "Wake on Voice", +	.capture = { +		.stream_name = "WoV Capture", +		.channels_min = 1, +		.channels_max = 1, +		.rates = SNDRV_PCM_RATE_16000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +}; + +static int wov_host_event(struct notifier_block *nb, +			  unsigned long queued_during_suspend, void *notify)  { -	struct snd_soc_component *component = -		snd_soc_dapm_to_component(w->dapm); +	struct cros_ec_codec_priv *priv = +		container_of(nb, struct cros_ec_codec_priv, wov_notifier); +	u32 host_event; + +	dev_dbg(priv->dev, "%s\n", __func__); + +	host_event = cros_ec_get_host_event(priv->ec_device); +	if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_WOV)) { +		schedule_delayed_work(&priv->wov_copy_work, 0); +		return NOTIFY_OK; +	} else { +		return NOTIFY_DONE; +	} +} -	switch (event) { -	case SND_SOC_DAPM_PRE_PMU: -		dev_dbg(component->dev, -			"%s got SND_SOC_DAPM_PRE_PMU event\n", __func__); -		return enable_i2s(component, 1); +static int wov_probe(struct snd_soc_component *component) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	int ret; -	case SND_SOC_DAPM_PRE_PMD: -		dev_dbg(component->dev, -			"%s got SND_SOC_DAPM_PRE_PMD event\n", __func__); -		return enable_i2s(component, 0); +	mutex_init(&priv->wov_dma_lock); +	INIT_DELAYED_WORK(&priv->wov_copy_work, wov_copy_work); + +	priv->wov_notifier.notifier_call = wov_host_event; +	ret = blocking_notifier_chain_register( +			&priv->ec_device->event_notifier, &priv->wov_notifier); +	if (ret) +		return ret; + +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_LANG_SHM)) { +		priv->wov_lang_shm_p = wov_map_shm(priv, +				EC_CODEC_SHM_ID_WOV_LANG, +				&priv->wov_lang_shm_len, +				&priv->wov_lang_shm_type); +		if (!priv->wov_lang_shm_p) +			return -EFAULT;  	} -	return 0; +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) { +		priv->wov_audio_shm_p = wov_map_shm(priv, +				EC_CODEC_SHM_ID_WOV_AUDIO, +				&priv->wov_audio_shm_len, +				&priv->wov_audio_shm_type); +		if (!priv->wov_audio_shm_p) +			return -EFAULT; +	} + +	return dmic_probe(component);  } -/* - * The goal of this DAPM route is to turn on/off I2S using EC - * host command when capture stream is started/stopped. - */ -static const struct snd_soc_dapm_widget cros_ec_codec_dapm_widgets[] = { -	SND_SOC_DAPM_INPUT("DMIC"), +static void wov_remove(struct snd_soc_component *component) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); -	/* -	 * Control EC to enable/disable I2S. -	 */ -	SND_SOC_DAPM_SUPPLY("I2S Enable", SND_SOC_NOPM, -			    0, 0, cros_ec_i2s_enable_event, -			    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +	blocking_notifier_chain_unregister( +			&priv->ec_device->event_notifier, &priv->wov_notifier); +} -	SND_SOC_DAPM_AIF_OUT("I2STX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0), -}; +static int wov_pcm_open(struct snd_soc_component *component, +			struct snd_pcm_substream *substream) +{ +	static const struct snd_pcm_hardware hw_param = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_MMAP_VALID, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +		.rates = SNDRV_PCM_RATE_16000, +		.channels_min = 1, +		.channels_max = 1, +		.period_bytes_min = PAGE_SIZE, +		.period_bytes_max = 0x20000 / 8, +		.periods_min = 8, +		.periods_max = 8, +		.buffer_bytes_max = 0x20000, +	}; + +	return snd_soc_set_runtime_hwparams(substream, &hw_param); +} -static const struct snd_soc_dapm_route cros_ec_codec_dapm_routes[] = { -	{ "I2STX", NULL, "DMIC" }, -	{ "I2STX", NULL, "I2S Enable" }, -}; +static int wov_pcm_hw_params(struct snd_soc_component *component, +			     struct snd_pcm_substream *substream, +			     struct snd_pcm_hw_params *hw_params) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); -/* - * Read maximum gain from device property and set it to mixer control. - */ -static int cros_ec_set_gain_range(struct device *dev) +	mutex_lock(&priv->wov_dma_lock); +	priv->wov_substream = substream; +	priv->wov_rp = priv->wov_wp = 0; +	priv->wov_dma_offset = 0; +	priv->wov_burst_read = true; +	mutex_unlock(&priv->wov_dma_lock); + +	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int wov_pcm_hw_free(struct snd_soc_component *component, +			   struct snd_pcm_substream *substream)  { -	struct soc_mixer_control *control; -	struct cros_ec_codec_data *codec_data = dev_get_drvdata(dev); -	int rc; +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); -	rc = device_property_read_u32(dev, "max-dmic-gain", -				      &codec_data->max_dmic_gain); -	if (rc) -		return rc; +	mutex_lock(&priv->wov_dma_lock); +	wov_queue_dequeue(priv, wov_queue_size(priv)); +	priv->wov_substream = NULL; +	mutex_unlock(&priv->wov_dma_lock); -	control = (struct soc_mixer_control *) -				mic_gain_control.private_value; -	control->max = codec_data->max_dmic_gain; -	control->platform_max = codec_data->max_dmic_gain; +	cancel_delayed_work_sync(&priv->wov_copy_work); -	return 0; +	return snd_pcm_lib_free_pages(substream);  } -static int cros_ec_codec_probe(struct snd_soc_component *component) +static snd_pcm_uframes_t wov_pcm_pointer(struct snd_soc_component *component, +					 struct snd_pcm_substream *substream)  { -	int rc; - -	struct cros_ec_codec_data *codec_data = +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct cros_ec_codec_priv *priv =  		snd_soc_component_get_drvdata(component); -	rc = cros_ec_set_gain_range(codec_data->dev); -	if (rc) -		return rc; +	return bytes_to_frames(runtime, priv->wov_dma_offset); +} -	return snd_soc_add_component_controls(component, &mic_gain_control, 1); +static int wov_pcm_new(struct snd_soc_component *component, +		       struct snd_soc_pcm_runtime *rtd) +{ +	snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, SNDRV_DMA_TYPE_VMALLOC, +					      NULL, 0, 0); +	return 0;  } -static const struct snd_soc_component_driver cros_ec_component_driver = { -	.probe			= cros_ec_codec_probe, -	.dapm_widgets		= cros_ec_codec_dapm_widgets, -	.num_dapm_widgets	= ARRAY_SIZE(cros_ec_codec_dapm_widgets), -	.dapm_routes		= cros_ec_codec_dapm_routes, -	.num_dapm_routes	= ARRAY_SIZE(cros_ec_codec_dapm_routes), +static const struct snd_soc_component_driver wov_component_driver = { +	.probe		= wov_probe, +	.remove		= wov_remove, +	.controls	= wov_controls, +	.num_controls	= ARRAY_SIZE(wov_controls), +	.open		= wov_pcm_open, +	.hw_params	= wov_pcm_hw_params, +	.hw_free	= wov_pcm_hw_free, +	.pointer	= wov_pcm_pointer, +	.pcm_construct	= wov_pcm_new,  }; -/* - * Platform device and platform driver fro cros-ec-codec. - */ -static int cros_ec_codec_platform_probe(struct platform_device *pd) +static int cros_ec_codec_platform_probe(struct platform_device *pdev)  { -	struct device *dev = &pd->dev; -	struct cros_ec_device *ec_device = dev_get_drvdata(pd->dev.parent); -	struct cros_ec_codec_data *codec_data; +	struct device *dev = &pdev->dev; +	struct cros_ec_device *ec_device = dev_get_drvdata(pdev->dev.parent); +	struct cros_ec_codec_priv *priv; +	struct ec_param_ec_codec p; +	struct ec_response_ec_codec_get_capabilities r; +	int ret; +#ifdef CONFIG_OF +	struct device_node *node; +	struct resource res; +	u64 ec_shm_size; +	const __be32 *regaddr_p; +#endif -	codec_data = devm_kzalloc(dev, sizeof(struct cros_ec_codec_data), -				  GFP_KERNEL); -	if (!codec_data) +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +	if (!priv)  		return -ENOMEM; -	codec_data->dev = dev; -	codec_data->ec_device = ec_device; +#ifdef CONFIG_OF +	regaddr_p = of_get_address(dev->of_node, 0, &ec_shm_size, NULL); +	if (regaddr_p) { +		priv->ec_shm_addr = of_read_number(regaddr_p, 2); +		priv->ec_shm_len = ec_shm_size; -	platform_set_drvdata(pd, codec_data); +		dev_dbg(dev, "ec_shm_addr=%#llx len=%#x\n", +			priv->ec_shm_addr, priv->ec_shm_len); +	} + +	node = of_parse_phandle(dev->of_node, "memory-region", 0); +	if (node) { +		ret = of_address_to_resource(node, 0, &res); +		if (!ret) { +			priv->ap_shm_phys_addr = res.start; +			priv->ap_shm_len = resource_size(&res); +			priv->ap_shm_addr = +				(uint64_t)(uintptr_t)devm_ioremap_wc( +					dev, priv->ap_shm_phys_addr, +					priv->ap_shm_len); +			priv->ap_shm_last_alloc = priv->ap_shm_phys_addr; + +			dev_dbg(dev, "ap_shm_phys_addr=%#llx len=%#x\n", +				priv->ap_shm_phys_addr, priv->ap_shm_len); +		} +	} +#endif + +	priv->dev = dev; +	priv->ec_device = ec_device; +	atomic_set(&priv->dmic_probed, 0); + +	p.cmd = EC_CODEC_GET_CAPABILITIES; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret) { +		dev_err(dev, "failed to EC_CODEC_GET_CAPABILITIES\n"); +		return ret; +	} +	priv->ec_capabilities = r.capabilities; + +	platform_set_drvdata(pdev, priv); + +	ret = devm_snd_soc_register_component(dev, &i2s_rx_component_driver, +					      &i2s_rx_dai_driver, 1); +	if (ret) +		return ret; -	return devm_snd_soc_register_component(dev, &cros_ec_component_driver, -					  cros_ec_dai, ARRAY_SIZE(cros_ec_dai)); +	return devm_snd_soc_register_component(dev, &wov_component_driver, +					       &wov_dai_driver, 1);  }  #ifdef CONFIG_OF @@ -427,7 +1049,7 @@ MODULE_DEVICE_TABLE(of, cros_ec_codec_of_match);  static struct platform_driver cros_ec_codec_platform_driver = {  	.driver = { -		.name = DRV_NAME, +		.name = "cros-ec-codec",  		.of_match_table = of_match_ptr(cros_ec_codec_of_match),  	},  	.probe = cros_ec_codec_platform_probe, @@ -438,4 +1060,4 @@ module_platform_driver(cros_ec_codec_platform_driver);  MODULE_LICENSE("GPL v2");  MODULE_DESCRIPTION("ChromeOS EC codec driver");  MODULE_AUTHOR("Cheng-Yi Chiang <[email protected]>"); -MODULE_ALIAS("platform:" DRV_NAME); +MODULE_ALIAS("platform:cros-ec-codec");  |