[alsa-devel] [PATCH v5 0/4] ASoC: mediatek: mt8183-mt6358-ts3a227-max98357: support WoV

This series makes mt6358, cros_ec_codec, and mt8183-mt6358-ts3a227-max98357 support WoV (wake on voice).
ASoC: cros_ec_codec: support WoV - makes cros_ec_codec support WoV.
ASoC: mediatek: mt6358: support WoV - sets necessary registers on mt6358 to support WoV.
ASoC: dt-bindings: mt8183: add ec-codec ASoC: mediatek: mt8183: support WoV - make machine driver mt8183-mt6358-ts3a227-max98357 support WoV if ec-codec is in DTS.
Changes from v4: - remove the first 6 commits which have merged - fix a compile error by adding Kconfig dependency https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/156992.ht...
Changes from v3: - fix a compile error by adding ifdef https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/156750.ht... - fix a coccinelle warning https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/156754.ht...
Changes from v2: - rebase upon to "don't use snd_pcm_ops" series https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/156170.ht... - fix sparse errors https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/156328.ht... - use "reg" for SHM binding https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/156657.ht...
Changes from v1: - fix a compile error and make kbuild bot happy https://mailman.alsa-project.org/pipermail/alsa-devel/2019-October/156315.ht...
Tzung-Bi Shih (4): ASoC: cros_ec_codec: support WoV ASoC: mediatek: mt6358: support WoV ASoC: dt-bindings: mt8183: add ec-codec ASoC: mediatek: mt8183: support WoV
.../sound/mt8183-mt6358-ts3a227-max98357.txt | 3 + drivers/platform/chrome/cros_ec_trace.c | 1 + .../linux/platform_data/cros_ec_commands.h | 69 ++ sound/soc/codecs/Kconfig | 2 + sound/soc/codecs/cros_ec_codec.c | 700 +++++++++++++++++- sound/soc/codecs/mt6358.c | 105 +++ sound/soc/mediatek/Kconfig | 1 + .../mt8183/mt8183-mt6358-ts3a227-max98357.c | 70 +- 8 files changed, 946 insertions(+), 5 deletions(-)

1. Get EC codec's capabilities. 2. Get and set SHM address if any. 3. Transmit language model to EC codec if needed. 4. Start to read audio data from EC codec if receives host event.
Signed-off-by: Tzung-Bi Shih tzungbi@google.com --- drivers/platform/chrome/cros_ec_trace.c | 1 + .../linux/platform_data/cros_ec_commands.h | 69 ++ sound/soc/codecs/Kconfig | 2 + sound/soc/codecs/cros_ec_codec.c | 700 +++++++++++++++++- 4 files changed, 770 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/chrome/cros_ec_trace.c b/drivers/platform/chrome/cros_ec_trace.c index 2ea0d4e0d54d..5af1d66d9eca 100644 --- a/drivers/platform/chrome/cros_ec_trace.c +++ b/drivers/platform/chrome/cros_ec_trace.c @@ -101,6 +101,7 @@ TRACE_SYMBOL(EC_CMD_EC_CODEC), \ TRACE_SYMBOL(EC_CMD_EC_CODEC_DMIC), \ TRACE_SYMBOL(EC_CMD_EC_CODEC_I2S_RX), \ + TRACE_SYMBOL(EC_CMD_EC_CODEC_WOV), \ TRACE_SYMBOL(EC_CMD_REBOOT_EC), \ TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \ TRACE_SYMBOL(EC_CMD_ACPI_READ), \ diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 21db0d4d4025..69210881ebac 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -556,6 +556,9 @@ enum host_event_code { /* Keyboard recovery combo with hardware reinitialization */ EC_HOST_EVENT_KEYBOARD_RECOVERY_HW_REINIT = 30,
+ /* WoV */ + EC_HOST_EVENT_WOV = 31, + /* * The high bit of the event mask is not used as a host event code. If * it reads back as set, then the entire event mask should be @@ -4477,10 +4480,14 @@ enum ec_codec_subcmd { };
enum ec_codec_cap { + EC_CODEC_CAP_WOV_AUDIO_SHM = 0, + EC_CODEC_CAP_WOV_LANG_SHM = 1, EC_CODEC_CAP_LAST = 32, };
enum ec_codec_shm_id { + EC_CODEC_SHM_ID_WOV_AUDIO = 0x0, + EC_CODEC_SHM_ID_WOV_LANG = 0x1, EC_CODEC_SHM_ID_LAST, };
@@ -4641,6 +4648,68 @@ struct __ec_align4 ec_param_ec_codec_i2s_rx { }; };
+/*****************************************************************************/ +/* Commands for WoV on audio codec. */ + +#define EC_CMD_EC_CODEC_WOV 0x00BF + +enum ec_codec_wov_subcmd { + EC_CODEC_WOV_SET_LANG = 0x0, + EC_CODEC_WOV_SET_LANG_SHM = 0x1, + EC_CODEC_WOV_GET_LANG = 0x2, + EC_CODEC_WOV_ENABLE = 0x3, + EC_CODEC_WOV_DISABLE = 0x4, + EC_CODEC_WOV_READ_AUDIO = 0x5, + EC_CODEC_WOV_READ_AUDIO_SHM = 0x6, + EC_CODEC_WOV_SUBCMD_COUNT, +}; + +/* + * @hash is SHA256 of the whole language model. + * @total_len indicates the length of whole language model. + * @offset is the cursor from the beginning of the model. + * @buf is the packet buffer. + * @len denotes how many bytes in the buf. + */ +struct __ec_align4 ec_param_ec_codec_wov_set_lang { + uint8_t hash[32]; + uint32_t total_len; + uint32_t offset; + uint8_t buf[128]; + uint32_t len; +}; + +struct __ec_align4 ec_param_ec_codec_wov_set_lang_shm { + uint8_t hash[32]; + uint32_t total_len; +}; + +struct __ec_align4 ec_param_ec_codec_wov { + uint8_t cmd; /* enum ec_codec_wov_subcmd */ + uint8_t reserved[3]; + + union { + struct ec_param_ec_codec_wov_set_lang + set_lang_param; + struct ec_param_ec_codec_wov_set_lang_shm + set_lang_shm_param; + }; +}; + +struct __ec_align4 ec_response_ec_codec_wov_get_lang { + uint8_t hash[32]; +}; + +struct __ec_align4 ec_response_ec_codec_wov_read_audio { + uint8_t buf[128]; + uint32_t len; +}; + +struct __ec_align4 ec_response_ec_codec_wov_read_audio_shm { + uint32_t offset; + uint32_t len; +}; + /*****************************************************************************/ /* System commands */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5a706102db04..1a4b084cc90d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -509,6 +509,8 @@ config SND_SOC_CQ0093VC config SND_SOC_CROS_EC_CODEC tristate "codec driver for ChromeOS EC" depends on CROS_EC + select CRYPTO + select CRYPTO_SHA256 help If you say yes here you will get support for the ChromeOS Embedded Controller's Audio Codec. diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c index 3d4f9e82d6e9..dd14caf9091a 100644 --- a/sound/soc/codecs/cros_ec_codec.c +++ b/sound/soc/codecs/cros_ec_codec.c @@ -8,10 +8,16 @@ * 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> @@ -23,8 +29,45 @@ struct cros_ec_codec_priv { struct device *dev; struct cros_ec_device *ec_device; + + /* 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 int ec_codec_capable(struct cros_ec_codec_priv *priv, uint8_t cap) +{ + return priv->ec_capabilities & BIT(cap); +} + 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) @@ -57,6 +100,41 @@ static int send_ec_host_command(struct cros_ec_device *ec_dev, uint32_t cmd, return ret; }
+static int calculate_sha256(struct cros_ec_codec_priv *priv, + uint8_t *buf, uint32_t size, uint8_t *digest) +{ + 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) { @@ -147,6 +225,9 @@ static int dmic_probe(struct snd_soc_component *component) 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, @@ -324,23 +405,638 @@ static const struct snd_soc_component_driver i2s_rx_component_driver = { .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_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(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; + } +} + +static bool wov_queue_full(struct cros_ec_codec_priv *priv) +{ + return ((priv->wov_wp + 1) % sizeof(priv->wov_buf)) == priv->wov_rp; +} + +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 void wov_queue_dequeue(struct cros_ec_codec_priv *priv, size_t len) +{ + 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); + + memcpy(runtime->dma_area + priv->wov_dma_offset, + priv->wov_buf + priv->wov_rp, req); + + priv->wov_dma_offset += req; + if (priv->wov_dma_offset == runtime->dma_bytes) + priv->wov_dma_offset = 0; + + 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 void wov_queue_try_dequeue(struct cros_ec_codec_priv *priv) +{ + size_t period_bytes = snd_pcm_lib_period_bytes(priv->wov_substream); + + 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); + } +} + +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; + } + + 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; + } + + 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 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; + + 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; + } + + return -EAGAIN; +} + +static void wov_copy_work(struct work_struct *w) +{ + struct cros_ec_codec_priv *priv = + container_of(w, struct cros_ec_codec_priv, wov_copy_work.work); + int ret; + + mutex_lock(&priv->wov_dma_lock); + if (!priv->wov_substream) { + dev_warn(priv->dev, "no pcm substream\n"); + goto leave; + } + + 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); +} + +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 wov_enable_put(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); + int enabled = ucontrol->value.integer.value[0]; + struct ec_param_ec_codec_wov p; + int ret; + + 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 wov_set_lang_shm(struct cros_ec_codec_priv *priv, + uint8_t *buf, size_t size, uint8_t *digest) +{ + struct ec_param_ec_codec_wov p; + struct ec_param_ec_codec_wov_set_lang_shm *pp = &p.set_lang_shm_param; + int ret; + + if (size > priv->wov_lang_shm_len) { + dev_err(priv->dev, "no enough SHM size: %d\n", + priv->wov_lang_shm_len); + return -EIO; + } + + 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); + + /* 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 wov_set_lang(struct cros_ec_codec_priv *priv, + uint8_t *buf, size_t size, uint8_t *digest) +{ + 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; + + 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 0; +} + +static int wov_hotword_model_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *bytes, + unsigned int size) +{ + 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; + + if (memcmp(digest, r.hash, SHA256_DIGEST_SIZE) == 0) { + dev_dbg(priv->dev, "not updated"); + goto leave; + } + + 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); + +leave: + kfree(buf); + return ret; +} + +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 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; + } +} + +static int wov_probe(struct snd_soc_component *component) +{ + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + int ret; + + 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; + } + + 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); +} + +static void wov_remove(struct snd_soc_component *component) +{ + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + + blocking_notifier_chain_unregister( + &priv->ec_device->event_notifier, &priv->wov_notifier); +} + +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 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); + + 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_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int wov_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + + mutex_lock(&priv->wov_dma_lock); + wov_queue_dequeue(priv, wov_queue_size(priv)); + priv->wov_substream = NULL; + mutex_unlock(&priv->wov_dma_lock); + + cancel_delayed_work_sync(&priv->wov_copy_work); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static snd_pcm_uframes_t wov_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + + return bytes_to_frames(runtime, priv->wov_dma_offset); +} + +static struct page *wov_pcm_page(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + unsigned long offset) +{ + return snd_pcm_lib_get_vmalloc_page(substream, offset); +} + +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, + .page = wov_pcm_page, +}; + static int cros_ec_codec_platform_probe(struct platform_device *pdev) { 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
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
+#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; + + 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);
- return devm_snd_soc_register_component(dev, &i2s_rx_component_driver, - &i2s_rx_dai_driver, 1); + 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, &wov_component_driver, + &wov_dai_driver, 1); }
#ifdef CONFIG_OF

Hi,
On 19/10/19 9:02, Tzung-Bi Shih wrote:
- Get EC codec's capabilities.
- Get and set SHM address if any.
- Transmit language model to EC codec if needed.
- Start to read audio data from EC codec if receives host event.
Signed-off-by: Tzung-Bi Shih tzungbi@google.com
drivers/platform/chrome/cros_ec_trace.c | 1 + .../linux/platform_data/cros_ec_commands.h | 69 ++
For the chrome/platform side.
Acked-by: Enric Balletbo i Serra enric.balletbo@collabora.com
Mark, once applied, could we get an immutable branch from you containing the following already applied patches + this series? We need as we have patches that could conflict on platform/chrome.
3fe5a963c631 ASoC: dt-bindings: cros_ec_codec: add SHM bindings f3e82ad43ca5 ASoC: cros_ec_codec: read max DMIC gain from EC codec 104c6f8f7ff8 platform/chrome: cros_ec: add common commands for EC codec 8f731d4c92c2 ASoC: cros_ec_codec: extract DMIC EC command from I2S RX 727f1c71c780 ASoC: cros_ec_codec: refactor I2S RX 036beb0e85f8 platform/chrome: cros_ec: remove unused EC feature
Thanks, Enric.
sound/soc/codecs/Kconfig | 2 + sound/soc/codecs/cros_ec_codec.c | 700 +++++++++++++++++- 4 files changed, 770 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/chrome/cros_ec_trace.c b/drivers/platform/chrome/cros_ec_trace.c index 2ea0d4e0d54d..5af1d66d9eca 100644 --- a/drivers/platform/chrome/cros_ec_trace.c +++ b/drivers/platform/chrome/cros_ec_trace.c @@ -101,6 +101,7 @@ TRACE_SYMBOL(EC_CMD_EC_CODEC), \ TRACE_SYMBOL(EC_CMD_EC_CODEC_DMIC), \ TRACE_SYMBOL(EC_CMD_EC_CODEC_I2S_RX), \
- TRACE_SYMBOL(EC_CMD_EC_CODEC_WOV), \ TRACE_SYMBOL(EC_CMD_REBOOT_EC), \ TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \ TRACE_SYMBOL(EC_CMD_ACPI_READ), \
diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 21db0d4d4025..69210881ebac 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -556,6 +556,9 @@ enum host_event_code { /* Keyboard recovery combo with hardware reinitialization */ EC_HOST_EVENT_KEYBOARD_RECOVERY_HW_REINIT = 30,
- /* WoV */
- EC_HOST_EVENT_WOV = 31,
- /*
- The high bit of the event mask is not used as a host event code. If
- it reads back as set, then the entire event mask should be
@@ -4477,10 +4480,14 @@ enum ec_codec_subcmd { };
enum ec_codec_cap {
- EC_CODEC_CAP_WOV_AUDIO_SHM = 0,
- EC_CODEC_CAP_WOV_LANG_SHM = 1, EC_CODEC_CAP_LAST = 32,
};
enum ec_codec_shm_id {
- EC_CODEC_SHM_ID_WOV_AUDIO = 0x0,
- EC_CODEC_SHM_ID_WOV_LANG = 0x1, EC_CODEC_SHM_ID_LAST,
};
@@ -4641,6 +4648,68 @@ struct __ec_align4 ec_param_ec_codec_i2s_rx { }; };
+/*****************************************************************************/ +/* Commands for WoV on audio codec. */
+#define EC_CMD_EC_CODEC_WOV 0x00BF
+enum ec_codec_wov_subcmd {
- EC_CODEC_WOV_SET_LANG = 0x0,
- EC_CODEC_WOV_SET_LANG_SHM = 0x1,
- EC_CODEC_WOV_GET_LANG = 0x2,
- EC_CODEC_WOV_ENABLE = 0x3,
- EC_CODEC_WOV_DISABLE = 0x4,
- EC_CODEC_WOV_READ_AUDIO = 0x5,
- EC_CODEC_WOV_READ_AUDIO_SHM = 0x6,
- EC_CODEC_WOV_SUBCMD_COUNT,
+};
+/*
- @hash is SHA256 of the whole language model.
- @total_len indicates the length of whole language model.
- @offset is the cursor from the beginning of the model.
- @buf is the packet buffer.
- @len denotes how many bytes in the buf.
- */
+struct __ec_align4 ec_param_ec_codec_wov_set_lang {
- uint8_t hash[32];
- uint32_t total_len;
- uint32_t offset;
- uint8_t buf[128];
- uint32_t len;
+};
+struct __ec_align4 ec_param_ec_codec_wov_set_lang_shm {
- uint8_t hash[32];
- uint32_t total_len;
+};
+struct __ec_align4 ec_param_ec_codec_wov {
- uint8_t cmd; /* enum ec_codec_wov_subcmd */
- uint8_t reserved[3];
- union {
struct ec_param_ec_codec_wov_set_lang
set_lang_param;
struct ec_param_ec_codec_wov_set_lang_shm
set_lang_shm_param;
- };
+};
+struct __ec_align4 ec_response_ec_codec_wov_get_lang {
- uint8_t hash[32];
+};
+struct __ec_align4 ec_response_ec_codec_wov_read_audio {
- uint8_t buf[128];
- uint32_t len;
+};
+struct __ec_align4 ec_response_ec_codec_wov_read_audio_shm {
- uint32_t offset;
- uint32_t len;
+};
/*****************************************************************************/ /* System commands */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5a706102db04..1a4b084cc90d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -509,6 +509,8 @@ config SND_SOC_CQ0093VC config SND_SOC_CROS_EC_CODEC tristate "codec driver for ChromeOS EC" depends on CROS_EC
- select CRYPTO
- select CRYPTO_SHA256 help If you say yes here you will get support for the ChromeOS Embedded Controller's Audio Codec.
diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c index 3d4f9e82d6e9..dd14caf9091a 100644 --- a/sound/soc/codecs/cros_ec_codec.c +++ b/sound/soc/codecs/cros_ec_codec.c @@ -8,10 +8,16 @@
- 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> @@ -23,8 +29,45 @@ struct cros_ec_codec_priv { struct device *dev; struct cros_ec_device *ec_device;
- /* 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 int ec_codec_capable(struct cros_ec_codec_priv *priv, uint8_t cap) +{
- return priv->ec_capabilities & BIT(cap);
+}
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) @@ -57,6 +100,41 @@ static int send_ec_host_command(struct cros_ec_device *ec_dev, uint32_t cmd, return ret; }
+static int calculate_sha256(struct cros_ec_codec_priv *priv,
uint8_t *buf, uint32_t size, uint8_t *digest)
+{
- 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) { @@ -147,6 +225,9 @@ static int dmic_probe(struct snd_soc_component *component) 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,
@@ -324,23 +405,638 @@ static const struct snd_soc_component_driver i2s_rx_component_driver = { .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_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(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;
- }
+}
+static bool wov_queue_full(struct cros_ec_codec_priv *priv) +{
- return ((priv->wov_wp + 1) % sizeof(priv->wov_buf)) == priv->wov_rp;
+}
+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 void wov_queue_dequeue(struct cros_ec_codec_priv *priv, size_t len) +{
- 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);
memcpy(runtime->dma_area + priv->wov_dma_offset,
priv->wov_buf + priv->wov_rp, req);
priv->wov_dma_offset += req;
if (priv->wov_dma_offset == runtime->dma_bytes)
priv->wov_dma_offset = 0;
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 void wov_queue_try_dequeue(struct cros_ec_codec_priv *priv) +{
- size_t period_bytes = snd_pcm_lib_period_bytes(priv->wov_substream);
- 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);
- }
+}
+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;
- }
- 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;
- }
- 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 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;
- 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;
- }
- return -EAGAIN;
+}
+static void wov_copy_work(struct work_struct *w) +{
- struct cros_ec_codec_priv *priv =
container_of(w, struct cros_ec_codec_priv, wov_copy_work.work);
- int ret;
- mutex_lock(&priv->wov_dma_lock);
- if (!priv->wov_substream) {
dev_warn(priv->dev, "no pcm substream\n");
goto leave;
- }
- 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);
+}
+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 wov_enable_put(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);
- int enabled = ucontrol->value.integer.value[0];
- struct ec_param_ec_codec_wov p;
- int ret;
- 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 wov_set_lang_shm(struct cros_ec_codec_priv *priv,
uint8_t *buf, size_t size, uint8_t *digest)
+{
- struct ec_param_ec_codec_wov p;
- struct ec_param_ec_codec_wov_set_lang_shm *pp = &p.set_lang_shm_param;
- int ret;
- if (size > priv->wov_lang_shm_len) {
dev_err(priv->dev, "no enough SHM size: %d\n",
priv->wov_lang_shm_len);
return -EIO;
- }
- 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);
/* 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 wov_set_lang(struct cros_ec_codec_priv *priv,
uint8_t *buf, size_t size, uint8_t *digest)
+{
- 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;
- 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 0;
+}
+static int wov_hotword_model_put(struct snd_kcontrol *kcontrol,
const unsigned int __user *bytes,
unsigned int size)
+{
- 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;
- if (memcmp(digest, r.hash, SHA256_DIGEST_SIZE) == 0) {
dev_dbg(priv->dev, "not updated");
goto leave;
- }
- 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);
+leave:
- kfree(buf);
- return ret;
+}
+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 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;
- }
+}
+static int wov_probe(struct snd_soc_component *component) +{
- struct cros_ec_codec_priv *priv =
snd_soc_component_get_drvdata(component);
- int ret;
- 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;
- }
- 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);
+}
+static void wov_remove(struct snd_soc_component *component) +{
- struct cros_ec_codec_priv *priv =
snd_soc_component_get_drvdata(component);
- blocking_notifier_chain_unregister(
&priv->ec_device->event_notifier, &priv->wov_notifier);
+}
+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 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);
- 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_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
+}
+static int wov_pcm_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
+{
- struct cros_ec_codec_priv *priv =
snd_soc_component_get_drvdata(component);
- mutex_lock(&priv->wov_dma_lock);
- wov_queue_dequeue(priv, wov_queue_size(priv));
- priv->wov_substream = NULL;
- mutex_unlock(&priv->wov_dma_lock);
- cancel_delayed_work_sync(&priv->wov_copy_work);
- return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+static snd_pcm_uframes_t wov_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
+{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct cros_ec_codec_priv *priv =
snd_soc_component_get_drvdata(component);
- return bytes_to_frames(runtime, priv->wov_dma_offset);
+}
+static struct page *wov_pcm_page(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
unsigned long offset)
+{
- return snd_pcm_lib_get_vmalloc_page(substream, offset);
+}
+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,
- .page = wov_pcm_page,
+};
static int cros_ec_codec_platform_probe(struct platform_device *pdev) { 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
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
+#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;
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);
- return devm_snd_soc_register_component(dev, &i2s_rx_component_driver,
&i2s_rx_dai_driver, 1);
- 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, &wov_component_driver,
&wov_dai_driver, 1);
}
#ifdef CONFIG_OF

On Mon, Oct 21, 2019 at 01:26:04PM +0200, Enric Balletbo i Serra wrote:
Mark, once applied, could we get an immutable branch from you containing the following already applied patches + this series? We need as we have patches that could conflict on platform/chrome.
Ugh. You should have mentioned this before the patches were applied, they're on the development branch that has everything in it like I normally do which I don't want to rebase (and I think I did a merge up of the fixes branch since then anyway). Hopefully Linus will be able to figure out any conflicts that occur.

Hi,
On 21/10/19 13:43, Mark Brown wrote:
On Mon, Oct 21, 2019 at 01:26:04PM +0200, Enric Balletbo i Serra wrote:
Mark, once applied, could we get an immutable branch from you containing the following already applied patches + this series? We need as we have patches that could conflict on platform/chrome.
Ugh. You should have mentioned this before the patches were applied, they're on the development branch that has everything in it like I normally do which I don't want to rebase (and I think I did a merge up of the fixes branch since then anyway). Hopefully Linus will be able to figure out any conflicts that occur.
Ok, I was off the latest two weeks so I didn't catch it :-(. I'll try to figure out the better way to avoid conflicts knowing that these are already queued.
Thanks, Enric.

On Mon, Oct 21, 2019 at 03:12:50PM +0200, Enric Balletbo i Serra wrote:
Ok, I was off the latest two weeks so I didn't catch it :-(. I'll try to figure out the better way to avoid conflicts knowing that these are already queued.
So long as they're easy to resolve it shouldn't be an issue, Linus doesn't super mind fixing things up.

The patch
ASoC: cros_ec_codec: support WoV
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-5.5
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From b6bc07d4360dbf766e551f18e43c67fff6784955 Mon Sep 17 00:00:00 2001
From: Tzung-Bi Shih tzungbi@google.com Date: Sat, 19 Oct 2019 15:02:51 +0800 Subject: [PATCH] ASoC: cros_ec_codec: support WoV
1. Get EC codec's capabilities. 2. Get and set SHM address if any. 3. Transmit language model to EC codec if needed. 4. Start to read audio data from EC codec if receives host event.
Signed-off-by: Tzung-Bi Shih tzungbi@google.com Acked-by: Enric Balletbo i Serra enric.balletbo@collabora.com Link: https://lore.kernel.org/r/20191019143504.1.I5388b69a7a9c551078fed216a77440ce... Signed-off-by: Mark Brown broonie@kernel.org --- drivers/platform/chrome/cros_ec_trace.c | 1 + .../linux/platform_data/cros_ec_commands.h | 69 ++ sound/soc/codecs/Kconfig | 2 + sound/soc/codecs/cros_ec_codec.c | 700 +++++++++++++++++- 4 files changed, 770 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/chrome/cros_ec_trace.c b/drivers/platform/chrome/cros_ec_trace.c index 2ea0d4e0d54d..5af1d66d9eca 100644 --- a/drivers/platform/chrome/cros_ec_trace.c +++ b/drivers/platform/chrome/cros_ec_trace.c @@ -101,6 +101,7 @@ TRACE_SYMBOL(EC_CMD_EC_CODEC), \ TRACE_SYMBOL(EC_CMD_EC_CODEC_DMIC), \ TRACE_SYMBOL(EC_CMD_EC_CODEC_I2S_RX), \ + TRACE_SYMBOL(EC_CMD_EC_CODEC_WOV), \ TRACE_SYMBOL(EC_CMD_REBOOT_EC), \ TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \ TRACE_SYMBOL(EC_CMD_ACPI_READ), \ diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 21db0d4d4025..69210881ebac 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -556,6 +556,9 @@ enum host_event_code { /* Keyboard recovery combo with hardware reinitialization */ EC_HOST_EVENT_KEYBOARD_RECOVERY_HW_REINIT = 30,
+ /* WoV */ + EC_HOST_EVENT_WOV = 31, + /* * The high bit of the event mask is not used as a host event code. If * it reads back as set, then the entire event mask should be @@ -4477,10 +4480,14 @@ enum ec_codec_subcmd { };
enum ec_codec_cap { + EC_CODEC_CAP_WOV_AUDIO_SHM = 0, + EC_CODEC_CAP_WOV_LANG_SHM = 1, EC_CODEC_CAP_LAST = 32, };
enum ec_codec_shm_id { + EC_CODEC_SHM_ID_WOV_AUDIO = 0x0, + EC_CODEC_SHM_ID_WOV_LANG = 0x1, EC_CODEC_SHM_ID_LAST, };
@@ -4641,6 +4648,68 @@ struct __ec_align4 ec_param_ec_codec_i2s_rx { }; };
+/*****************************************************************************/ +/* Commands for WoV on audio codec. */ + +#define EC_CMD_EC_CODEC_WOV 0x00BF + +enum ec_codec_wov_subcmd { + EC_CODEC_WOV_SET_LANG = 0x0, + EC_CODEC_WOV_SET_LANG_SHM = 0x1, + EC_CODEC_WOV_GET_LANG = 0x2, + EC_CODEC_WOV_ENABLE = 0x3, + EC_CODEC_WOV_DISABLE = 0x4, + EC_CODEC_WOV_READ_AUDIO = 0x5, + EC_CODEC_WOV_READ_AUDIO_SHM = 0x6, + EC_CODEC_WOV_SUBCMD_COUNT, +}; + +/* + * @hash is SHA256 of the whole language model. + * @total_len indicates the length of whole language model. + * @offset is the cursor from the beginning of the model. + * @buf is the packet buffer. + * @len denotes how many bytes in the buf. + */ +struct __ec_align4 ec_param_ec_codec_wov_set_lang { + uint8_t hash[32]; + uint32_t total_len; + uint32_t offset; + uint8_t buf[128]; + uint32_t len; +}; + +struct __ec_align4 ec_param_ec_codec_wov_set_lang_shm { + uint8_t hash[32]; + uint32_t total_len; +}; + +struct __ec_align4 ec_param_ec_codec_wov { + uint8_t cmd; /* enum ec_codec_wov_subcmd */ + uint8_t reserved[3]; + + union { + struct ec_param_ec_codec_wov_set_lang + set_lang_param; + struct ec_param_ec_codec_wov_set_lang_shm + set_lang_shm_param; + }; +}; + +struct __ec_align4 ec_response_ec_codec_wov_get_lang { + uint8_t hash[32]; +}; + +struct __ec_align4 ec_response_ec_codec_wov_read_audio { + uint8_t buf[128]; + uint32_t len; +}; + +struct __ec_align4 ec_response_ec_codec_wov_read_audio_shm { + uint32_t offset; + uint32_t len; +}; + /*****************************************************************************/ /* System commands */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5a706102db04..1a4b084cc90d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -509,6 +509,8 @@ config SND_SOC_CQ0093VC config SND_SOC_CROS_EC_CODEC tristate "codec driver for ChromeOS EC" depends on CROS_EC + select CRYPTO + select CRYPTO_SHA256 help If you say yes here you will get support for the ChromeOS Embedded Controller's Audio Codec. diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c index 3d4f9e82d6e9..dd14caf9091a 100644 --- a/sound/soc/codecs/cros_ec_codec.c +++ b/sound/soc/codecs/cros_ec_codec.c @@ -8,10 +8,16 @@ * 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> @@ -23,8 +29,45 @@ struct cros_ec_codec_priv { struct device *dev; struct cros_ec_device *ec_device; + + /* 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 int ec_codec_capable(struct cros_ec_codec_priv *priv, uint8_t cap) +{ + return priv->ec_capabilities & BIT(cap); +} + 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) @@ -57,6 +100,41 @@ static int send_ec_host_command(struct cros_ec_device *ec_dev, uint32_t cmd, return ret; }
+static int calculate_sha256(struct cros_ec_codec_priv *priv, + uint8_t *buf, uint32_t size, uint8_t *digest) +{ + 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) { @@ -147,6 +225,9 @@ static int dmic_probe(struct snd_soc_component *component) 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, @@ -324,23 +405,638 @@ static const struct snd_soc_component_driver i2s_rx_component_driver = { .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_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(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; + } +} + +static bool wov_queue_full(struct cros_ec_codec_priv *priv) +{ + return ((priv->wov_wp + 1) % sizeof(priv->wov_buf)) == priv->wov_rp; +} + +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 void wov_queue_dequeue(struct cros_ec_codec_priv *priv, size_t len) +{ + 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); + + memcpy(runtime->dma_area + priv->wov_dma_offset, + priv->wov_buf + priv->wov_rp, req); + + priv->wov_dma_offset += req; + if (priv->wov_dma_offset == runtime->dma_bytes) + priv->wov_dma_offset = 0; + + 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 void wov_queue_try_dequeue(struct cros_ec_codec_priv *priv) +{ + size_t period_bytes = snd_pcm_lib_period_bytes(priv->wov_substream); + + 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); + } +} + +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; + } + + 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; + } + + 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 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; + + 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; + } + + return -EAGAIN; +} + +static void wov_copy_work(struct work_struct *w) +{ + struct cros_ec_codec_priv *priv = + container_of(w, struct cros_ec_codec_priv, wov_copy_work.work); + int ret; + + mutex_lock(&priv->wov_dma_lock); + if (!priv->wov_substream) { + dev_warn(priv->dev, "no pcm substream\n"); + goto leave; + } + + 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); +} + +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 wov_enable_put(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); + int enabled = ucontrol->value.integer.value[0]; + struct ec_param_ec_codec_wov p; + int ret; + + 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 wov_set_lang_shm(struct cros_ec_codec_priv *priv, + uint8_t *buf, size_t size, uint8_t *digest) +{ + struct ec_param_ec_codec_wov p; + struct ec_param_ec_codec_wov_set_lang_shm *pp = &p.set_lang_shm_param; + int ret; + + if (size > priv->wov_lang_shm_len) { + dev_err(priv->dev, "no enough SHM size: %d\n", + priv->wov_lang_shm_len); + return -EIO; + } + + 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); + + /* 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 wov_set_lang(struct cros_ec_codec_priv *priv, + uint8_t *buf, size_t size, uint8_t *digest) +{ + 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; + + 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 0; +} + +static int wov_hotword_model_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *bytes, + unsigned int size) +{ + 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; + + if (memcmp(digest, r.hash, SHA256_DIGEST_SIZE) == 0) { + dev_dbg(priv->dev, "not updated"); + goto leave; + } + + 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); + +leave: + kfree(buf); + return ret; +} + +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 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; + } +} + +static int wov_probe(struct snd_soc_component *component) +{ + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + int ret; + + 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; + } + + 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); +} + +static void wov_remove(struct snd_soc_component *component) +{ + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + + blocking_notifier_chain_unregister( + &priv->ec_device->event_notifier, &priv->wov_notifier); +} + +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 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); + + 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_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int wov_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + + mutex_lock(&priv->wov_dma_lock); + wov_queue_dequeue(priv, wov_queue_size(priv)); + priv->wov_substream = NULL; + mutex_unlock(&priv->wov_dma_lock); + + cancel_delayed_work_sync(&priv->wov_copy_work); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static snd_pcm_uframes_t wov_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + + return bytes_to_frames(runtime, priv->wov_dma_offset); +} + +static struct page *wov_pcm_page(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + unsigned long offset) +{ + return snd_pcm_lib_get_vmalloc_page(substream, offset); +} + +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, + .page = wov_pcm_page, +}; + static int cros_ec_codec_platform_probe(struct platform_device *pdev) { 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
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
+#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; + + 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);
- return devm_snd_soc_register_component(dev, &i2s_rx_component_driver, - &i2s_rx_dai_driver, 1); + 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, &wov_component_driver, + &wov_dai_driver, 1); }
#ifdef CONFIG_OF

Switch mono DMIC on to support wake-on-voice.
Signed-off-by: Tzung-Bi Shih tzungbi@google.com --- sound/soc/codecs/mt6358.c | 105 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+)
diff --git a/sound/soc/codecs/mt6358.c b/sound/soc/codecs/mt6358.c index bb737fd678cc..1b830ea4f6ed 100644 --- a/sound/soc/codecs/mt6358.c +++ b/sound/soc/codecs/mt6358.c @@ -93,6 +93,8 @@ struct mt6358_priv { int mtkaif_protocol;
struct regulator *avdd_reg; + + int wov_enabled; };
int mt6358_set_mtkaif_protocol(struct snd_soc_component *cmpnt, @@ -464,6 +466,106 @@ static int mt6358_put_volsw(struct snd_kcontrol *kcontrol, return ret; }
+static void mt6358_restore_pga(struct mt6358_priv *priv); + +static int mt6358_enable_wov_phase2(struct mt6358_priv *priv) +{ + /* analog */ + regmap_update_bits(priv->regmap, MT6358_AUDDEC_ANA_CON13, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_DCXO_CW14, 0xffff, 0xa2b5); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON1, + 0xffff, 0x0800); + mt6358_restore_pga(priv); + + regmap_update_bits(priv->regmap, MT6358_DCXO_CW13, 0xffff, 0x9929); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON9, + 0xffff, 0x0025); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON8, + 0xffff, 0x0005); + + /* digital */ + regmap_update_bits(priv->regmap, MT6358_AUD_TOP_CKPDN_CON0, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_GPIO_MODE3, 0xffff, 0x0120); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG0, 0xffff, 0xffff); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG1, 0xffff, 0x0200); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG2, 0xffff, 0x2424); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG3, 0xffff, 0xdbac); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG4, 0xffff, 0x029e); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG5, 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_POSDIV_CFG0, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_HPF_CFG0, + 0xffff, 0x0451); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_TOP, 0xffff, 0x68d1); + + return 0; +} + +static int mt6358_disable_wov_phase2(struct mt6358_priv *priv) +{ + /* digital */ + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_TOP, 0xffff, 0xc000); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_HPF_CFG0, + 0xffff, 0x0450); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_POSDIV_CFG0, + 0xffff, 0x0c00); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG5, 0xffff, 0x0100); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG4, 0xffff, 0x006c); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG3, 0xffff, 0xa879); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG2, 0xffff, 0x2323); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG1, 0xffff, 0x0400); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG0, 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_GPIO_MODE3, 0xffff, 0x02d8); + regmap_update_bits(priv->regmap, MT6358_AUD_TOP_CKPDN_CON0, + 0xffff, 0x0000); + + /* analog */ + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON8, + 0xffff, 0x0004); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON9, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_DCXO_CW13, 0xffff, 0x9829); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON1, + 0xffff, 0x0000); + mt6358_restore_pga(priv); + regmap_update_bits(priv->regmap, MT6358_DCXO_CW14, 0xffff, 0xa2b5); + regmap_update_bits(priv->regmap, MT6358_AUDDEC_ANA_CON13, + 0xffff, 0x0010); + + return 0; +} + +static int mt6358_get_wov(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct mt6358_priv *priv = snd_soc_component_get_drvdata(c); + + ucontrol->value.integer.value[0] = priv->wov_enabled; + return 0; +} + +static int mt6358_put_wov(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct mt6358_priv *priv = snd_soc_component_get_drvdata(c); + int enabled = ucontrol->value.integer.value[0]; + + if (priv->wov_enabled != enabled) { + if (enabled) + mt6358_enable_wov_phase2(priv); + else + mt6358_disable_wov_phase2(priv); + + priv->wov_enabled = enabled; + } + + return 0; +} + static const DECLARE_TLV_DB_SCALE(playback_tlv, -1000, 100, 0); static const DECLARE_TLV_DB_SCALE(pga_tlv, 0, 600, 0);
@@ -483,6 +585,9 @@ static const struct snd_kcontrol_new mt6358_snd_controls[] = { MT6358_AUDENC_ANA_CON0, MT6358_AUDENC_ANA_CON1, 8, 4, 0, snd_soc_get_volsw, mt6358_put_volsw, pga_tlv), + + SOC_SINGLE_BOOL_EXT("Wake-on-Voice Phase2 Switch", 0, + mt6358_get_wov, mt6358_put_wov), };
/* MUX */

The patch
ASoC: mediatek: mt6358: support WoV
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-5.5
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 8e8c533b132ad0ce9c99a50ff4c910a8dd7efb8e Mon Sep 17 00:00:00 2001
From: Tzung-Bi Shih tzungbi@google.com Date: Sat, 19 Oct 2019 15:02:52 +0800 Subject: [PATCH] ASoC: mediatek: mt6358: support WoV
Switch mono DMIC on to support wake-on-voice.
Signed-off-by: Tzung-Bi Shih tzungbi@google.com Link: https://lore.kernel.org/r/20191019143504.2.I57266d36564f393e9d701c9db648cc2e... Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/codecs/mt6358.c | 105 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+)
diff --git a/sound/soc/codecs/mt6358.c b/sound/soc/codecs/mt6358.c index bb737fd678cc..1b830ea4f6ed 100644 --- a/sound/soc/codecs/mt6358.c +++ b/sound/soc/codecs/mt6358.c @@ -93,6 +93,8 @@ struct mt6358_priv { int mtkaif_protocol;
struct regulator *avdd_reg; + + int wov_enabled; };
int mt6358_set_mtkaif_protocol(struct snd_soc_component *cmpnt, @@ -464,6 +466,106 @@ static int mt6358_put_volsw(struct snd_kcontrol *kcontrol, return ret; }
+static void mt6358_restore_pga(struct mt6358_priv *priv); + +static int mt6358_enable_wov_phase2(struct mt6358_priv *priv) +{ + /* analog */ + regmap_update_bits(priv->regmap, MT6358_AUDDEC_ANA_CON13, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_DCXO_CW14, 0xffff, 0xa2b5); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON1, + 0xffff, 0x0800); + mt6358_restore_pga(priv); + + regmap_update_bits(priv->regmap, MT6358_DCXO_CW13, 0xffff, 0x9929); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON9, + 0xffff, 0x0025); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON8, + 0xffff, 0x0005); + + /* digital */ + regmap_update_bits(priv->regmap, MT6358_AUD_TOP_CKPDN_CON0, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_GPIO_MODE3, 0xffff, 0x0120); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG0, 0xffff, 0xffff); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG1, 0xffff, 0x0200); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG2, 0xffff, 0x2424); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG3, 0xffff, 0xdbac); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG4, 0xffff, 0x029e); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG5, 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_POSDIV_CFG0, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_HPF_CFG0, + 0xffff, 0x0451); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_TOP, 0xffff, 0x68d1); + + return 0; +} + +static int mt6358_disable_wov_phase2(struct mt6358_priv *priv) +{ + /* digital */ + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_TOP, 0xffff, 0xc000); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_HPF_CFG0, + 0xffff, 0x0450); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_POSDIV_CFG0, + 0xffff, 0x0c00); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG5, 0xffff, 0x0100); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG4, 0xffff, 0x006c); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG3, 0xffff, 0xa879); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG2, 0xffff, 0x2323); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG1, 0xffff, 0x0400); + regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG0, 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_GPIO_MODE3, 0xffff, 0x02d8); + regmap_update_bits(priv->regmap, MT6358_AUD_TOP_CKPDN_CON0, + 0xffff, 0x0000); + + /* analog */ + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON8, + 0xffff, 0x0004); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON9, + 0xffff, 0x0000); + regmap_update_bits(priv->regmap, MT6358_DCXO_CW13, 0xffff, 0x9829); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON1, + 0xffff, 0x0000); + mt6358_restore_pga(priv); + regmap_update_bits(priv->regmap, MT6358_DCXO_CW14, 0xffff, 0xa2b5); + regmap_update_bits(priv->regmap, MT6358_AUDDEC_ANA_CON13, + 0xffff, 0x0010); + + return 0; +} + +static int mt6358_get_wov(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct mt6358_priv *priv = snd_soc_component_get_drvdata(c); + + ucontrol->value.integer.value[0] = priv->wov_enabled; + return 0; +} + +static int mt6358_put_wov(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct mt6358_priv *priv = snd_soc_component_get_drvdata(c); + int enabled = ucontrol->value.integer.value[0]; + + if (priv->wov_enabled != enabled) { + if (enabled) + mt6358_enable_wov_phase2(priv); + else + mt6358_disable_wov_phase2(priv); + + priv->wov_enabled = enabled; + } + + return 0; +} + static const DECLARE_TLV_DB_SCALE(playback_tlv, -1000, 100, 0); static const DECLARE_TLV_DB_SCALE(pga_tlv, 0, 600, 0);
@@ -483,6 +585,9 @@ static const struct snd_kcontrol_new mt6358_snd_controls[] = { MT6358_AUDENC_ANA_CON0, MT6358_AUDENC_ANA_CON1, 8, 4, 0, snd_soc_get_volsw, mt6358_put_volsw, pga_tlv), + + SOC_SINGLE_BOOL_EXT("Wake-on-Voice Phase2 Switch", 0, + mt6358_get_wov, mt6358_put_wov), };
/* MUX */

Add an optional property "ec-codec". If specified, mt8183 could use the "wake on voice" feature offered by EC codec.
Acked-by: Rob Herring robh@kernel.org Signed-off-by: Tzung-Bi Shih tzungbi@google.com --- .../bindings/sound/mt8183-mt6358-ts3a227-max98357.txt | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt b/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt index 17ff3892f439..decaa013a07e 100644 --- a/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt +++ b/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt @@ -6,12 +6,15 @@ Required properties:
Optional properties: - mediatek,headset-codec: the phandles of ts3a227 codecs +- mediatek,ec-codec: the phandle of EC codecs. + See google,cros-ec-codec.txt for more details.
Example:
sound { compatible = "mediatek,mt8183_mt6358_ts3a227_max98357"; mediatek,headset-codec = <&ts3a227>; + mediatek,ec-codec = <&ec_codec>; mediatek,platform = <&afe>; };

The patch
ASoC: dt-bindings: mt8183: add ec-codec
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-5.5
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 0cce736d389cbeae6a7d0a72bafb4408d21eb44a Mon Sep 17 00:00:00 2001
From: Tzung-Bi Shih tzungbi@google.com Date: Sat, 19 Oct 2019 15:02:53 +0800 Subject: [PATCH] ASoC: dt-bindings: mt8183: add ec-codec
Add an optional property "ec-codec". If specified, mt8183 could use the "wake on voice" feature offered by EC codec.
Acked-by: Rob Herring robh@kernel.org Signed-off-by: Tzung-Bi Shih tzungbi@google.com Link: https://lore.kernel.org/r/20191019143504.3.Iec97a3f137148cdf316056612590b3e0... Signed-off-by: Mark Brown broonie@kernel.org --- .../bindings/sound/mt8183-mt6358-ts3a227-max98357.txt | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt b/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt index 17ff3892f439..decaa013a07e 100644 --- a/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt +++ b/Documentation/devicetree/bindings/sound/mt8183-mt6358-ts3a227-max98357.txt @@ -6,12 +6,15 @@ Required properties:
Optional properties: - mediatek,headset-codec: the phandles of ts3a227 codecs +- mediatek,ec-codec: the phandle of EC codecs. + See google,cros-ec-codec.txt for more details.
Example:
sound { compatible = "mediatek,mt8183_mt6358_ts3a227_max98357"; mediatek,headset-codec = <&ts3a227>; + mediatek,ec-codec = <&ec_codec>; mediatek,platform = <&afe>; };

Add DAI link and pin muxing for wake on voice.
Signed-off-by: Tzung-Bi Shih tzungbi@google.com --- sound/soc/mediatek/Kconfig | 1 + .../mt8183/mt8183-mt6358-ts3a227-max98357.c | 70 ++++++++++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 111e44b64b38..8b29f3979899 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -125,6 +125,7 @@ config SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A select SND_SOC_MAX98357A select SND_SOC_BT_SCO select SND_SOC_TS3A227E + select SND_SOC_CROS_EC_CODEC help This adds ASoC driver for Mediatek MT8183 boards with the MT6358 TS3A227E MAX98357A audio codec. diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c index bb9cdc0d6552..0555f7d73d05 100644 --- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c @@ -19,11 +19,12 @@ enum PINCTRL_PIN_STATE { PIN_STATE_DEFAULT = 0, PIN_TDM_OUT_ON, PIN_TDM_OUT_OFF, + PIN_WOV, PIN_STATE_MAX };
static const char * const mt8183_pin_str[PIN_STATE_MAX] = { - "default", "aud_tdm_out_on", "aud_tdm_out_off", + "default", "aud_tdm_out_on", "aud_tdm_out_off", "wov", };
struct mt8183_mt6358_ts3a227_max98357_priv { @@ -142,6 +143,11 @@ SND_SOC_DAILINK_DEFS(playback_hdmi, DAILINK_COMP_ARRAY(COMP_DUMMY()), DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(wake_on_voice, + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + /* BE */ SND_SOC_DAILINK_DEFS(primary_codec, DAILINK_COMP_ARRAY(COMP_CPU("ADDA")), @@ -229,6 +235,41 @@ static struct snd_soc_ops mt8183_mt6358_tdm_ops = { .shutdown = mt8183_mt6358_tdm_shutdown, };
+static int +mt8183_mt6358_ts3a227_max98357_wov_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct mt8183_mt6358_ts3a227_max98357_priv *priv = + snd_soc_card_get_drvdata(card); + + return pinctrl_select_state(priv->pinctrl, + priv->pin_states[PIN_WOV]); +} + +static void +mt8183_mt6358_ts3a227_max98357_wov_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct mt8183_mt6358_ts3a227_max98357_priv *priv = + snd_soc_card_get_drvdata(card); + int ret; + + ret = pinctrl_select_state(priv->pinctrl, + priv->pin_states[PIN_STATE_DEFAULT]); + if (ret) + dev_err(card->dev, "%s failed to select state %d\n", + __func__, ret); +} + +static const struct snd_soc_ops mt8183_mt6358_ts3a227_max98357_wov_ops = { + .startup = mt8183_mt6358_ts3a227_max98357_wov_startup, + .shutdown = mt8183_mt6358_ts3a227_max98357_wov_shutdown, +}; + static struct snd_soc_dai_link mt8183_mt6358_ts3a227_max98357_dai_links[] = { /* FE */ @@ -306,6 +347,15 @@ mt8183_mt6358_ts3a227_max98357_dai_links[] = { .dpcm_playback = 1, SND_SOC_DAILINK_REG(playback_hdmi), }, + { + .name = "Wake on Voice", + .stream_name = "Wake on Voice", + .ignore_suspend = 1, + .ignore = 1, + SND_SOC_DAILINK_REG(wake_on_voice), + .ops = &mt8183_mt6358_ts3a227_max98357_wov_ops, + }, + /* BE */ { .name = "Primary Codec", @@ -429,7 +479,7 @@ static int mt8183_mt6358_ts3a227_max98357_dev_probe(struct platform_device *pdev) { struct snd_soc_card *card = &mt8183_mt6358_ts3a227_max98357_card; - struct device_node *platform_node; + struct device_node *platform_node, *ec_codec; struct snd_soc_dai_link *dai_link; struct mt8183_mt6358_ts3a227_max98357_priv *priv; int ret; @@ -444,10 +494,24 @@ mt8183_mt6358_ts3a227_max98357_dev_probe(struct platform_device *pdev) return -EINVAL; }
+ ec_codec = of_parse_phandle(pdev->dev.of_node, "mediatek,ec-codec", 0); + for_each_card_prelinks(card, i, dai_link) { if (dai_link->platforms->name) continue; - dai_link->platforms->of_node = platform_node; + + if (ec_codec && strcmp(dai_link->name, "Wake on Voice") == 0) { + dai_link->cpus[0].name = NULL; + dai_link->cpus[0].of_node = ec_codec; + dai_link->cpus[0].dai_name = NULL; + dai_link->codecs[0].name = NULL; + dai_link->codecs[0].of_node = ec_codec; + dai_link->codecs[0].dai_name = "Wake on Voice"; + dai_link->platforms[0].of_node = ec_codec; + dai_link->ignore = 0; + } else { + dai_link->platforms->of_node = platform_node; + } }
mt8183_mt6358_ts3a227_max98357_headset_dev.dlc.of_node =

The patch
ASoC: mediatek: mt8183: support WoV
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-5.5
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 2cc3cd5fdc8ba07ba590204a3a6f6d16289d25e5 Mon Sep 17 00:00:00 2001
From: Tzung-Bi Shih tzungbi@google.com Date: Sat, 19 Oct 2019 15:02:54 +0800 Subject: [PATCH] ASoC: mediatek: mt8183: support WoV
Add DAI link and pin muxing for wake on voice.
Signed-off-by: Tzung-Bi Shih tzungbi@google.com Link: https://lore.kernel.org/r/20191019143504.4.Ibf012d0cd8679d846213606dc5f426ae... Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/mediatek/Kconfig | 1 + .../mt8183/mt8183-mt6358-ts3a227-max98357.c | 70 ++++++++++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 111e44b64b38..8b29f3979899 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -125,6 +125,7 @@ config SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A select SND_SOC_MAX98357A select SND_SOC_BT_SCO select SND_SOC_TS3A227E + select SND_SOC_CROS_EC_CODEC help This adds ASoC driver for Mediatek MT8183 boards with the MT6358 TS3A227E MAX98357A audio codec. diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c index bb9cdc0d6552..0555f7d73d05 100644 --- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c @@ -19,11 +19,12 @@ enum PINCTRL_PIN_STATE { PIN_STATE_DEFAULT = 0, PIN_TDM_OUT_ON, PIN_TDM_OUT_OFF, + PIN_WOV, PIN_STATE_MAX };
static const char * const mt8183_pin_str[PIN_STATE_MAX] = { - "default", "aud_tdm_out_on", "aud_tdm_out_off", + "default", "aud_tdm_out_on", "aud_tdm_out_off", "wov", };
struct mt8183_mt6358_ts3a227_max98357_priv { @@ -142,6 +143,11 @@ SND_SOC_DAILINK_DEFS(playback_hdmi, DAILINK_COMP_ARRAY(COMP_DUMMY()), DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(wake_on_voice, + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + /* BE */ SND_SOC_DAILINK_DEFS(primary_codec, DAILINK_COMP_ARRAY(COMP_CPU("ADDA")), @@ -229,6 +235,41 @@ static struct snd_soc_ops mt8183_mt6358_tdm_ops = { .shutdown = mt8183_mt6358_tdm_shutdown, };
+static int +mt8183_mt6358_ts3a227_max98357_wov_startup( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct mt8183_mt6358_ts3a227_max98357_priv *priv = + snd_soc_card_get_drvdata(card); + + return pinctrl_select_state(priv->pinctrl, + priv->pin_states[PIN_WOV]); +} + +static void +mt8183_mt6358_ts3a227_max98357_wov_shutdown( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct mt8183_mt6358_ts3a227_max98357_priv *priv = + snd_soc_card_get_drvdata(card); + int ret; + + ret = pinctrl_select_state(priv->pinctrl, + priv->pin_states[PIN_STATE_DEFAULT]); + if (ret) + dev_err(card->dev, "%s failed to select state %d\n", + __func__, ret); +} + +static const struct snd_soc_ops mt8183_mt6358_ts3a227_max98357_wov_ops = { + .startup = mt8183_mt6358_ts3a227_max98357_wov_startup, + .shutdown = mt8183_mt6358_ts3a227_max98357_wov_shutdown, +}; + static struct snd_soc_dai_link mt8183_mt6358_ts3a227_max98357_dai_links[] = { /* FE */ @@ -306,6 +347,15 @@ mt8183_mt6358_ts3a227_max98357_dai_links[] = { .dpcm_playback = 1, SND_SOC_DAILINK_REG(playback_hdmi), }, + { + .name = "Wake on Voice", + .stream_name = "Wake on Voice", + .ignore_suspend = 1, + .ignore = 1, + SND_SOC_DAILINK_REG(wake_on_voice), + .ops = &mt8183_mt6358_ts3a227_max98357_wov_ops, + }, + /* BE */ { .name = "Primary Codec", @@ -429,7 +479,7 @@ static int mt8183_mt6358_ts3a227_max98357_dev_probe(struct platform_device *pdev) { struct snd_soc_card *card = &mt8183_mt6358_ts3a227_max98357_card; - struct device_node *platform_node; + struct device_node *platform_node, *ec_codec; struct snd_soc_dai_link *dai_link; struct mt8183_mt6358_ts3a227_max98357_priv *priv; int ret; @@ -444,10 +494,24 @@ mt8183_mt6358_ts3a227_max98357_dev_probe(struct platform_device *pdev) return -EINVAL; }
+ ec_codec = of_parse_phandle(pdev->dev.of_node, "mediatek,ec-codec", 0); + for_each_card_prelinks(card, i, dai_link) { if (dai_link->platforms->name) continue; - dai_link->platforms->of_node = platform_node; + + if (ec_codec && strcmp(dai_link->name, "Wake on Voice") == 0) { + dai_link->cpus[0].name = NULL; + dai_link->cpus[0].of_node = ec_codec; + dai_link->cpus[0].dai_name = NULL; + dai_link->codecs[0].name = NULL; + dai_link->codecs[0].of_node = ec_codec; + dai_link->codecs[0].dai_name = "Wake on Voice"; + dai_link->platforms[0].of_node = ec_codec; + dai_link->ignore = 0; + } else { + dai_link->platforms->of_node = platform_node; + } }
mt8183_mt6358_ts3a227_max98357_headset_dev.dlc.of_node =
participants (3)
-
Enric Balletbo i Serra
-
Mark Brown
-
Tzung-Bi Shih