summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Muller <mullerf@google.com>2022-08-24 10:09:38 -0700
committerFlorian Muller <mullerf@google.com>2022-08-24 10:09:38 -0700
commit76cbe542ddefebf0ffac6cdf8a1b58b1e8887c2f (patch)
treeb72a01affc9a71fb93e97c64582d8e46754d5c2a
parent40a04dfd502fc9adbee9feedd5bf5f56b3acf37a (diff)
downloadmcu_mic_codec-76cbe542ddefebf0ffac6cdf8a1b58b1e8887c2f.tar.gz
mcu_mic_codec: Port initial driver from R11
This is just the initial driver code drop with no difference with R11. Test: Manual Bug: 243575790 Bug: 219605378 Signed-off-by: Florian Muller <mullerf@google.com> Change-Id: If75606675f2955ac732699985754a9d8f43a0c20
-rw-r--r--Kbuild5
-rw-r--r--Kconfig4
-rw-r--r--Makefile17
-rw-r--r--mcu_mic_codec.c414
-rw-r--r--mcu_mic_codec.txt12
5 files changed, 452 insertions, 0 deletions
diff --git a/Kbuild b/Kbuild
new file mode 100644
index 0000000..a37aaca
--- /dev/null
+++ b/Kbuild
@@ -0,0 +1,5 @@
+#
+# Makefile for the kernel ASOC MCU Mic codec driver.
+#
+
+obj-$(CONFIG_SND_SOC_MCU_MIC_CODEC) += mcu_mic_codec.o
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..0ac3832
--- /dev/null
+++ b/Kconfig
@@ -0,0 +1,4 @@
+config SND_SOC_MCU_MIC_CODEC
+ tristate "ASOC MCU Mic codec"
+ help
+ ASOC Codec driver to communicate with Microphone on MCU.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8257f30
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+#
+# Makefile for ASOC MCU Mic codec driver.
+#
+
+default: all
+
+KBUILD_OPTIONS := CONFIG_SND_SOC_MCU_MIC_CODEC=m
+
+all:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS) KBUILD_EXTRA_SYMBOLS="$(OUT_DIR)/../exynos-google-cw-extra/drivers/sensorhub/nanohub/Module.symvers"
+
+modules_install:
+ $(MAKE) INSTALL_MOD_STRIP=1 M=$(M) -C $(KERNEL_SRC) modules_install
+
+clean::
+ rm -f *.o *.ko *.mod.c *.mod.o *~ .*.cmd Module.symvers
+ rm -rf .tmp_versions
diff --git a/mcu_mic_codec.c b/mcu_mic_codec.c
new file mode 100644
index 0000000..6284f94
--- /dev/null
+++ b/mcu_mic_codec.c
@@ -0,0 +1,414 @@
+/**
+ * The module represents a ASOC codec driver responsible for turning Microphone
+ * on the MCU on or off.
+ *
+ * Currently it's only a stub: it does not actually communicate with the MCU
+ * yet, that will be added later when the communication lib is available.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/completion.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define NANOHUB_AUDIO_CHANNEL_ID 16
+
+// Values here should match MCU FW values defined in audio_services.cc
+#define DMIC_MCU_MESSAGE_VERSION 1
+#define DMIC_MCU_MESSAGE_MIC_ON 0x01
+#define DMIC_MCU_MESSAGE_SAMPLE_RATE_KHZ 0x02
+#define DMIC_MCU_MESSAGE_CHANNEL 0x03
+#define DMIC_MCU_MESSAGE_GAIN 0x04
+
+enum dmic_mcu_channel {
+ DMIC_MCU_CHANNEL_LEFT,
+ DMIC_MCU_CHANNEL_RIGHT,
+ DMIC_MCU_CHANNEL_FALSE_STEREO,
+};
+
+static const char *const mcu_dmic_channel_enum_texts[] = {
+ "Left",
+ "Right",
+ "False Stereo",
+};
+
+static const unsigned int mcu_dmic_channel_enum_values[] = {
+ DMIC_MCU_CHANNEL_LEFT,
+ DMIC_MCU_CHANNEL_RIGHT,
+ DMIC_MCU_CHANNEL_FALSE_STEREO,
+};
+
+SOC_VALUE_ENUM_SINGLE_DECL(mcu_dmic_channel_enum, SND_SOC_NOPM, 0, 0,
+ mcu_dmic_channel_enum_texts,
+ mcu_dmic_channel_enum_values);
+
+extern ssize_t nanohub_send_message(int channel_id, const char *buffer,
+ size_t length);
+
+struct mcu_mic_codec_data {
+ int mic_on;
+ int sample_rate_hz;
+ enum dmic_mcu_channel channel;
+ int gain;
+};
+
+static int dmic_mcu_send_message(struct snd_soc_component *component,
+ char mcu_message_id, char data)
+{
+ char buffer[3];
+ ssize_t bytes;
+
+ buffer[0] = DMIC_MCU_MESSAGE_VERSION; // version
+ buffer[1] = mcu_message_id; // message identifier
+ buffer[2] = data;
+
+ bytes = nanohub_send_message(NANOHUB_AUDIO_CHANNEL_ID, buffer,
+ sizeof(buffer));
+ if (bytes != sizeof(buffer)) {
+ dev_err(component->dev, "Bytes sent expected = %zd, actual = %zd\n",
+ sizeof(buffer), bytes);
+ return -EIO;
+ }
+ return 0;
+}
+
+// The callback that is called when ASOC needs to fetch the value of the
+// property 'DMIC_MCU Input Gain'.
+static int dmic_mcu_input_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+
+ dev_info(component->dev, "Called dmic_mcu_input_gain_get\n");
+
+ ucontrol->value.integer.value[0] = codec_data->gain;
+
+ dev_info(component->dev, "mcu_input_gain: %d\n", codec_data->gain);
+
+ return 0;
+}
+
+// The callback that is called when ASOC needs to set the value of the
+// property 'DMIC_MCU Input Gain'.
+static int dmic_mcu_input_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+ int value = ucontrol->value.integer.value[0];
+ int ret;
+
+ dev_info(component->dev, "Called dmic_mcu_input_gain_put\n");
+
+ if (value < 0 || value > 63) {
+ dev_info(component->dev,
+ "Invalid value: %d, it must be between 0 and 63\n",
+ value);
+ return -EINVAL;
+ }
+
+ ret = dmic_mcu_send_message(component, DMIC_MCU_MESSAGE_GAIN, value);
+ if (ret != 0) {
+ return ret;
+ }
+
+ codec_data->gain = value;
+ dev_info(component->dev, "new mcu_input_gain: %d\n", codec_data->gain);
+
+ return 0;
+}
+
+// The callback that is called when ASOC needs to fetch the value of the
+// property 'DMIC_MCU Channel'.
+static int dmic_mcu_channel_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+
+ dev_info(component->dev, "Called dmic_mcu_channel_get\n");
+
+ ucontrol->value.integer.value[0] = codec_data->channel;
+
+ dev_info(component->dev, "mcu_mic_channel: %d (%s)\n", codec_data->channel,
+ mcu_dmic_channel_enum_texts[codec_data->channel]);
+
+ return 0;
+}
+
+// The callback that is called when ASOC needs to set the value of the
+// property 'DMIC_MCU Channel'.
+static int dmic_mcu_channel_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+ int value = ucontrol->value.integer.value[0];
+ int ret;
+
+ dev_info(component->dev, "Called dmic_mcu_channel_put\n");
+
+ if (value != DMIC_MCU_CHANNEL_LEFT &&
+ value != DMIC_MCU_CHANNEL_RIGHT &&
+ value != DMIC_MCU_CHANNEL_FALSE_STEREO) {
+ dev_info(component->dev,
+ "Invalid value: %d, it must be Left, Right or 'False Stereo'.\n",
+ value);
+ return -EINVAL;
+ }
+
+ ret = dmic_mcu_send_message(component, DMIC_MCU_MESSAGE_CHANNEL, value);
+ if (ret != 0) {
+ return ret;
+ }
+
+ codec_data->channel = value;
+ dev_info(component->dev, "new mcu_mic_channel: %d (%s)\n",
+ codec_data->channel,
+ mcu_dmic_channel_enum_texts[codec_data->channel]);
+
+ return 0;
+}
+
+// The callback that is called when ASOC needs to fetch the value of the
+// property 'DMIC_MCU Sample Rate'.
+static int dmic_mcu_sample_rate_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+ dev_info(component->dev, "Called dmic_mcu_sample_rate_get\n");
+ ucontrol->value.integer.value[0] = codec_data->sample_rate_hz;
+ dev_info(component->dev, "mcu_mic_sample_rate: %d\n",
+ codec_data->sample_rate_hz);
+ return 0;
+}
+
+// The callback that is called when ASOC needs to set the value of the
+// property 'DMIC_MCU Sample Rate'.
+static int dmic_mcu_sample_rate_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+ int value = ucontrol->value.integer.value[0];
+ int ret;
+
+ dev_info(component->dev, "Called dmic_mcu_sample_rate_put\n");
+
+ if (value != 8000 && value != 16000 && value != 48000) {
+ dev_info(component->dev,
+ "Invalid Sample Rate: %d. (Valid rates: 8000, 16000, 48000 Hertz)\n",
+ value);
+ return -EINVAL;
+ }
+
+ ret = dmic_mcu_send_message(component, DMIC_MCU_MESSAGE_SAMPLE_RATE_KHZ,
+ value / 1000);
+ if (ret != 0) {
+ return ret;
+ }
+
+ codec_data->sample_rate_hz = value;
+ dev_info(component->dev, "new mcu_mic_sample_rate: %d\n",
+ codec_data->sample_rate_hz);
+
+ return 0;
+}
+
+// The callback that is called when ASOC needs to fetch the value of the
+// property 'DMIC_MCU On'.
+static int dmic_mcu_on_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+
+ dev_info(component->dev, "Called dmic_mcu_on_get\n");
+
+ ucontrol->value.integer.value[0] = codec_data->mic_on;
+
+ dev_info(component->dev, "mcu_mic_on: %d\n", codec_data->mic_on);
+
+ return 0;
+}
+
+// The callback that is called when ASOC needs to set the value of the
+// property 'DMIC_MCU On'.
+static int dmic_mcu_on_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct mcu_mic_codec_data *codec_data =
+ snd_soc_component_get_drvdata(component);
+ int value = ucontrol->value.integer.value[0];
+ int ret;
+
+ dev_info(component->dev, "Called static int dmic_mcu_on_put\n");
+
+ if (value != 0 && value != 1) {
+ dev_info(component->dev,
+ "Invalid value: %d, it must be 1 or 0.\n", value);
+ return -EINVAL;
+ }
+
+ ret = dmic_mcu_send_message(component, DMIC_MCU_MESSAGE_MIC_ON,
+ value ? 1 : 0);
+ if (ret != 0) {
+ return ret;
+ }
+
+ codec_data->mic_on = value;
+ dev_info(component->dev, "new mcu_mic_on: %d\n", codec_data->mic_on);
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_controls[] = {
+ // Defines a property DMIC_MCU On.
+ // It can be switched in console for example using tinyalsa:
+ // tinymix 'DMIC_MCU On' 1
+ SOC_SINGLE_EXT("DMIC_MCU On", SND_SOC_NOPM, 0, 1, 0, dmic_mcu_on_get,
+ dmic_mcu_on_put),
+ SOC_SINGLE_EXT("DMIC_MCU Sample Rate", SND_SOC_NOPM, 0, 48000, 0,
+ dmic_mcu_sample_rate_get, dmic_mcu_sample_rate_put),
+ SOC_VALUE_ENUM_EXT("DMIC_MCU Channel", mcu_dmic_channel_enum,
+ dmic_mcu_channel_get, dmic_mcu_channel_put),
+ // Input Gain is mapped from [-32, 31] to [0, 63]. This is because
+ // tinymix does not accept negative numbers. It processes the
+ // negative sign as an argument option prefix.
+ SOC_SINGLE_EXT("DMIC_MCU Input Gain", SND_SOC_NOPM, 0, 63, 0,
+ dmic_mcu_input_gain_get, dmic_mcu_input_gain_put),
+};
+
+static int mcu_codec_probe(struct snd_soc_component *component)
+{
+ dev_info(component->dev, "Called mcu_codec_probe.\n");
+
+ return 0;
+}
+
+static void mcu_codec_remove(struct snd_soc_component *component)
+{
+ dev_info(component->dev, "Called mcu_codec_remove.\n");
+}
+
+static struct snd_soc_component_driver mcu_mic_soc_component_driver = {
+ .probe = mcu_codec_probe,
+ .remove = mcu_codec_remove,
+ .controls = snd_controls,
+ .num_controls = ARRAY_SIZE(snd_controls),
+};
+
+/*
+ * The codec DAI driver. It's properties are not used at the moment, as the main
+ * goal of the codec is to turn the Mic on/off.
+ */
+static struct snd_soc_dai_driver mcu_mic_soc_dai_driver = {
+ .name = "mcu-mic-codec-dai",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_LE),
+ },
+};
+
+static int mcu_mic_probe(struct platform_device *pdev)
+{
+ int ret;
+ // Structure used to store mcu dmic state:
+ // [on/off] 1: it is on, 0: it is off.
+ // [sample rate]: Units of hertz
+ struct mcu_mic_codec_data *codec_data;
+
+ dev_info(&pdev->dev, "Called mcu_mic_probe\n");
+
+ codec_data = devm_kzalloc(&pdev->dev, sizeof(struct mcu_mic_codec_data),
+ GFP_KERNEL);
+ if (!codec_data) {
+ dev_err(&pdev->dev, "No memory for codec_data\n");
+ return -ENOMEM;
+ }
+ codec_data->mic_on = 0;
+ codec_data->sample_rate_hz = 16000;
+ codec_data->channel = DMIC_MCU_CHANNEL_RIGHT;
+ // gain is offset by 32. Therefore 32 means 0 gain
+ codec_data->gain = 37;
+ platform_set_drvdata(pdev, codec_data);
+
+ ret = snd_soc_register_component(&pdev->dev,
+ &mcu_mic_soc_component_driver,
+ &mcu_mic_soc_dai_driver, 1);
+
+ dev_info(&pdev->dev, "mcu_mic_probe ret: %d\n", ret);
+
+ return ret;
+}
+
+static int mcu_mic_remove(struct platform_device *pdev)
+{
+ dev_info(&pdev->dev, "Called mcu_mic_remove\n");
+
+ snd_soc_unregister_component(&pdev->dev);
+
+ return 0;
+}
+
+const struct of_device_id mcu_mic_of_match[] = {
+ {
+ .compatible = "google,mcu_mic_codec",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mcu_mic_of_match);
+
+static struct platform_driver mcu_mic_driver = {
+ .driver = {
+ .name = "mcu_mic_codec",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(mcu_mic_of_match),
+ },
+ .probe = mcu_mic_probe,
+ .remove = mcu_mic_remove,
+};
+
+/* Register the platform driver */
+module_platform_driver(mcu_mic_driver);
+
+MODULE_DESCRIPTION("ASoC mcu mic codec driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mcu_mic-codec");
diff --git a/mcu_mic_codec.txt b/mcu_mic_codec.txt
new file mode 100644
index 0000000..33fe809
--- /dev/null
+++ b/mcu_mic_codec.txt
@@ -0,0 +1,12 @@
+MCU Microphone ASOC DAI codec
+
+Required properties:
+
+ - compatible : "google,mcu_mic_codec"
+
+Example:
+
+mcu_mic_codec: audio_codec {
+ status = "okay";
+ compatible = "google,mcu_mic_codec";
+};