motmdm.c handles audio configuration on "gsmtty2"; it needs to know
whether we are in call or not; that part is in motmdm-state.c and
listens on "gsmtty1".

To configure Alsamixer for voice calls do for example:
    
Speaker Right -> Voice
Call Noise Cancellation -> Unmute
Call Output -> Speakerphone
Call -> 100
Mic2 -> 40
Left -> Mic 2
Voice -> 55
Mic2 -> 40
Left -> Mic 2

Tony wrote original version using custom interface to n_gsm, Pavel
switched it to plain serdev and split it to two drivers to be easier
to debug and understand. Credit is Tony's, bugs are probably Pavel's.
    
Signed-off-by: Pavel Machek <pa...@ucw.cz>
Co-authored-by: Tony Lindgren <t...@atomide.com>

---

v2: fix compile error.

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ba4eb54aafcb..94000ab3cf3e 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -932,6 +932,14 @@ config SND_SOC_MAX9860
        depends on I2C
        select REGMAP_I2C
 
+config SND_SOC_MOTMDM
+       tristate "Motorola Modem TS 27.010 Voice Call Codec"
+       depends on SERIAL_DEV_BUS
+       help
+         Enable support for Motorola TS 27.010 serdev voice
+         call codec driver for Motorola Mapphone series of
+         devices such as Droid 4.
+
 config SND_SOC_MSM8916_WCD_ANALOG
        tristate "Qualcomm MSM8916 WCD Analog Codec"
        depends on SPMI || COMPILE_TEST
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index d277f0366e09..f63022e0a292 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -128,6 +128,8 @@ snd-soc-max9850-objs := max9850.o
 snd-soc-max9860-objs := max9860.o
 snd-soc-mc13783-objs := mc13783.o
 snd-soc-ml26124-objs := ml26124.o
+snd-soc-motmdm-objs := motmdm.o
+snd-soc-motmdm-state-objs := motmdm-state.o
 snd-soc-msm8916-analog-objs := msm8916-wcd-analog.o
 snd-soc-msm8916-digital-objs := msm8916-wcd-digital.o
 snd-soc-mt6351-objs := mt6351.o
@@ -443,6 +445,8 @@ obj-$(CONFIG_SND_SOC_MAX9850)       += snd-soc-max9850.o
 obj-$(CONFIG_SND_SOC_MAX9860)  += snd-soc-max9860.o
 obj-$(CONFIG_SND_SOC_MC13783)  += snd-soc-mc13783.o
 obj-$(CONFIG_SND_SOC_ML26124)  += snd-soc-ml26124.o
+obj-$(CONFIG_SND_SOC_MOTMDM)   += snd-soc-motmdm.o
+obj-$(CONFIG_SND_SOC_MOTMDM)   += snd-soc-motmdm-state.o
 obj-$(CONFIG_SND_SOC_MSM8916_WCD_ANALOG) +=snd-soc-msm8916-analog.o
 obj-$(CONFIG_SND_SOC_MSM8916_WCD_DIGITAL) +=snd-soc-msm8916-digital.o
 obj-$(CONFIG_SND_SOC_MT6351)   += snd-soc-mt6351.o
diff --git a/sound/soc/codecs/motmdm-state.c b/sound/soc/codecs/motmdm-state.c
new file mode 100644
index 000000000000..166de19be256
--- /dev/null
+++ b/sound/soc/codecs/motmdm-state.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Mapphone MDM6600 voice call audio support
+ * Copyright 2018 - 2020 Tony Lindgren <t...@atomide.com>
+ * Copyright 2020 - 2021 Pavel Machek <pa...@ucw.cz>
+ *
+ * Designed to provide notifications about voice call state to the
+ * motmdm.c driver. This one listens on "gsmtty1".
+ */
+
+#include <linux/init.h>
+#include <linux/kfifo.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/serdev.h>
+
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#define MOTMDM_HEADER_LEN      5                       /* U1234 */
+#define MOTMDM_AUDIO_MAX_LEN   128
+#define MOTMDM_VOICE_RESP_LEN  7                       /* U1234~+CIEV= */
+
+struct motmdm_driver_data {
+       struct serdev_device *serdev;
+       unsigned char *buf;
+       size_t len;
+       spinlock_t lock;        /* enable/disabled lock */
+};
+
+static BLOCKING_NOTIFIER_HEAD(modem_state_chain_head);
+
+int register_modem_state_notifier(struct notifier_block *nb)
+{
+        return blocking_notifier_chain_register(&modem_state_chain_head, nb);
+}
+EXPORT_SYMBOL_GPL(register_modem_state_notifier);
+
+int unregister_modem_state_notifier(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_unregister(&modem_state_chain_head, nb);
+}
+EXPORT_SYMBOL_GPL(unregister_modem_state_notifier);
+
+static int modem_state_notifier_call_chain(unsigned long val)
+{
+        int ret;
+        ret = __blocking_notifier_call_chain(&modem_state_chain_head, val, 
NULL,
+                                            -1, NULL);
+        return notifier_to_errno(ret);
+}
+
+/* Parses the voice call state from unsolicited notifications on dlci1 */
+static int motmdm_voice_get_state(struct motmdm_driver_data *ddata,
+                                  const unsigned char *buf,
+                                  size_t len)
+{
+       struct device *dev = &ddata->serdev->dev;
+       bool enable;
+       const unsigned char *state;
+
+       if (len < MOTMDM_HEADER_LEN + MOTMDM_VOICE_RESP_LEN + 5)
+               return 0;
+
+       /* We only care about the unsolicted messages */
+       if (buf[MOTMDM_HEADER_LEN] != '~')
+               return 0;
+
+       if (strncmp(buf + MOTMDM_HEADER_LEN + 1, "+CIEV=", 6))
+               return len;
+
+       state = buf + MOTMDM_HEADER_LEN + MOTMDM_VOICE_RESP_LEN;
+       dev_info(dev, "%s: ciev=%5s\n", __func__, state);
+
+       if (!strncmp(state, "1,1,0", 5) ||      /* connecting */
+           !strncmp(state, "1,4,0", 5) ||      /* incoming call */
+           !strncmp(state, "1,2,0", 5))        /* connected */
+               enable = true;
+       else if (!strncmp(state, "1,0,0", 5) || /* disconnected */
+               !strncmp(state, "1,0,2", 5))    /* call failed */
+               enable = false;
+       else
+               return len;
+
+       modem_state_notifier_call_chain(enable);
+       return len;
+}
+
+static int voice_receive_data(struct serdev_device *serdev,
+                            const unsigned char *buf,
+                            size_t len)
+{
+        struct motmdm_driver_data *ddata = serdev_device_get_drvdata(serdev);
+
+       if (len > MOTMDM_AUDIO_MAX_LEN)
+               len = MOTMDM_AUDIO_MAX_LEN;
+
+       if (len <= MOTMDM_HEADER_LEN)
+               return 0;
+
+       if (buf[MOTMDM_HEADER_LEN] == '~')
+               motmdm_voice_get_state(ddata, buf, len);
+
+       return len;
+}
+
+static const struct serdev_device_ops voice_serdev_ops = {
+        .receive_buf    = voice_receive_data,
+        .write_wakeup   = serdev_device_write_wakeup,
+};
+
+static void motmdm_free_voice_serdev(struct motmdm_driver_data *ddata)
+{
+       serdev_device_close(ddata->serdev);
+}
+
+static int motmdm_soc_probe(struct serdev_device *serdev)
+{
+       struct device *dev = &serdev->dev;
+       struct motmdm_driver_data *ddata;
+       int error;
+
+       ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+       if (!ddata)
+               return -ENOMEM;
+
+       ddata->serdev = serdev;
+       spin_lock_init(&ddata->lock);
+       ddata->len = MOTMDM_AUDIO_MAX_LEN;
+
+       ddata->buf = devm_kzalloc(dev, ddata->len, GFP_KERNEL);
+       if (!ddata->buf)
+               return -ENOMEM;
+
+       serdev_device_set_drvdata(ddata->serdev, ddata);
+        serdev_device_set_client_ops(ddata->serdev, &voice_serdev_ops);
+
+        error = serdev_device_open(ddata->serdev);
+       return error;
+}
+
+static void motmdm_state_remove(struct serdev_device *serdev)
+{
+       struct motmdm_driver_data *ddata = serdev_device_get_drvdata(serdev);
+
+       motmdm_free_voice_serdev(ddata);
+}
+
+static int motmdm_state_probe(struct serdev_device *serdev)
+{
+       return motmdm_soc_probe(serdev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id motmdm_of_match[] = {
+       { .compatible = "motorola,mapphone-mdm6600-modem" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, motmdm_of_match);
+#endif
+
+static struct serdev_device_driver motmdm_state_driver = {
+       .driver = {
+               .name           = "mot-mdm6600-modem",
+               .of_match_table = of_match_ptr(motmdm_of_match),
+       },
+       .probe  = motmdm_state_probe,
+       .remove = motmdm_state_remove,
+};
+module_serdev_device_driver(motmdm_state_driver);
+
+MODULE_ALIAS("platform:motmdm-state");
+MODULE_DESCRIPTION("Motorola Mapphone MDM6600 modem state driver");
+MODULE_AUTHOR("Pavel Machek <pa...@ucw.cz>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/motmdm.c b/sound/soc/codecs/motmdm.c
new file mode 100644
index 000000000000..0ee546133471
--- /dev/null
+++ b/sound/soc/codecs/motmdm.c
@@ -0,0 +1,688 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Mapphone MDM6600 voice call audio support
+ * Copyright 2018 - 2020 Tony Lindgren <t...@atomide.com>
+ * Copyright 2020 - 2021 Pavel Machek <pa...@ucw.cz>
+ *
+ * This one handles audio configuration on "gsmtty2"; it needs to know
+ * whether we are in call or not, and that part is in motmdm-state.c
+ * and listens on "gsmtty1".
+ */
+
+#include <linux/init.h>
+#include <linux/kfifo.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/serdev.h>
+
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "motmdm-state.h"
+
+#define MOTMDM_HEADER_LEN      5                       /* U1234 */
+
+#define MOTMDM_AUDIO_RESP_LEN  6                       /* U1234+XXXX= */
+#define MOTMDM_AUDIO_MAX_LEN   128
+
+#define MOTMDM_VOICE_RESP_LEN  7                       /* U1234~+CIEV= */
+
+struct motmdm_driver_data {
+       struct notifier_block notifier;
+       struct snd_soc_component *component;
+       struct snd_soc_dai *master_dai;
+       struct device *modem;
+       struct serdev_device *serdev;
+       struct regmap *regmap;
+       unsigned char *buf;
+       size_t len;
+       unsigned int parsed:1;
+       unsigned int enabled:1;
+       spinlock_t lock;        /* enable/disabled lock */
+       struct mutex mutex;     /* for sending commands */
+       wait_queue_head_t read_queue;
+
+       unsigned int dtmf_val;
+       unsigned int dtmf_en;
+};
+
+enum motmdm_cmd {
+       CMD_AT_EACC,
+       CMD_AT_CLVL,
+       CMD_AT_NREC,
+};
+
+const char * const motmd_read_fmt[] = {
+       [CMD_AT_EACC] = "AT+EACC?",
+       [CMD_AT_CLVL] = "AT+CLVL?",
+       [CMD_AT_NREC] = "AT+NREC?",
+};
+
+const char * const motmd_write_fmt[] = {
+       [CMD_AT_EACC] = "AT+EACC=%u,0",
+       [CMD_AT_CLVL] = "AT+CLVL=%u",
+       [CMD_AT_NREC] = "AT+NREC=%u",
+};
+
+/*
+ * Currently unconfigured additional inactive (error producing) options
+ * seem to be:
+ * "TTY Headset", "HCQ Headset", "VCQ Headset", "No-Mic Headset",
+ * "Handset Fluence Med", "Handset Fluence Low", "Car Dock", "Lapdock"
+ */
+static const char * const motmdm_out_mux_texts[] = {
+       "Handset", "Headset", "Speakerphone", "Bluetooth",
+};
+
+static SOC_ENUM_SINGLE_EXT_DECL(motmdm_out_enum, motmdm_out_mux_texts);
+
+static const DECLARE_TLV_DB_SCALE(motmdm_gain_tlv, 0, 100, 0);
+
+int motmdm_send_command(struct motmdm_driver_data *ddata,
+                            const u8 *buf, int len)
+{
+       struct device *dev = ddata->component->dev;
+       const int timeout_ms = 1000;
+       unsigned char cmd[MOTMDM_AUDIO_MAX_LEN];
+       int ret, cmdlen;
+
+       cmdlen = len + 5 + 1;
+       if (cmdlen > MOTMDM_AUDIO_MAX_LEN)
+               return -EINVAL;
+
+       mutex_lock(&ddata->mutex);
+       memset(ddata->buf, 0, ddata->len);
+       ddata->parsed = false;
+       snprintf(cmd, cmdlen, "U%04li%s", jiffies % 10000, buf);
+
+       ret = serdev_device_write(ddata->serdev, cmd, cmdlen, 
MAX_SCHEDULE_TIMEOUT);
+       if (ret < 0)
+               goto out_unlock;
+
+        serdev_device_wait_until_sent(ddata->serdev, 0);
+
+       ret = wait_event_timeout(ddata->read_queue, ddata->parsed,
+                                msecs_to_jiffies(timeout_ms));
+       if (ret == 0) {
+               ret = -ETIMEDOUT;
+               goto out_unlock;
+       } else if (ret < 0) {
+               goto out_unlock;
+       }
+
+       if (strstr(ddata->buf, "ERROR")) {
+               dev_err(dev, "command %s error %s\n", cmd, ddata->buf);
+               ret = -EPIPE;
+       }
+
+       ret = len;
+
+out_unlock:
+       mutex_unlock(&ddata->mutex);
+       printk("send_command -- ret %d\n", ret);
+
+       return ret;
+}
+
+static int motmdm_read_reg(void *context, unsigned int reg,
+                          unsigned int *value)
+{
+       struct snd_soc_component *component = context;
+       struct motmdm_driver_data *ddata = 
snd_soc_component_get_drvdata(component);
+       const unsigned char *cmd;
+       unsigned int val;
+       int error;
+
+       cmd = motmd_read_fmt[reg];
+       error = motmdm_send_command(ddata, cmd, strlen(cmd));
+       if (error < 0) {
+               dev_err(component->dev, "%s: %s failed with %i\n",
+                       __func__, cmd, error);
+
+               return error;
+       }
+
+       error = kstrtouint(ddata->buf + MOTMDM_AUDIO_RESP_LEN, 0, &val);
+       if (error)
+               return -ENODEV;
+
+       *value = val;
+
+       return error;
+}
+
+static int motmdm_write_reg(void *context, unsigned int reg,
+                           unsigned int value)
+{
+       struct snd_soc_component *component = context;
+       struct motmdm_driver_data *ddata = 
snd_soc_component_get_drvdata(component);
+       const unsigned char *fmt, *cmd;
+       int error;
+
+       fmt = motmd_write_fmt[reg];
+       cmd = kasprintf(GFP_KERNEL, fmt, value);
+       if (!cmd) {
+               error = -ENOMEM;
+               goto free;
+       }
+
+       error = motmdm_send_command(ddata, cmd, strlen(cmd));
+       if (error < 0)
+               dev_err(component->dev, "%s: %s failed with %i\n",
+                       __func__, cmd, error);
+
+free:
+       kfree(cmd);
+
+       return error;
+}
+
+static const struct reg_default motmdm_reg_defaults[] = {
+       { CMD_AT_EACC, 0x0 },
+       { CMD_AT_CLVL, 0x0 },
+};
+
+static const struct regmap_config motmdm_regmap = {
+       .reg_bits = 32,
+       .reg_stride = 1,
+       .val_bits = 32,
+       .max_register = CMD_AT_NREC,
+       .reg_defaults = motmdm_reg_defaults,
+       .num_reg_defaults = ARRAY_SIZE(motmdm_reg_defaults),
+       .cache_type = REGCACHE_RBTREE,
+       .reg_read = motmdm_read_reg,
+       .reg_write = motmdm_write_reg,
+};
+
+static int motmdm_value_get(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *ucontrol,
+                           enum motmdm_cmd reg,
+                           int cmd_base)
+{
+       struct snd_soc_component *component = 
snd_soc_kcontrol_component(kcontrol);
+       struct motmdm_driver_data *ddata = 
snd_soc_component_get_drvdata(component);
+       unsigned int val;
+       int error;
+
+       error = regmap_read(ddata->regmap, reg, &val);
+       if (error)
+               return error;
+
+       if (val >= cmd_base)
+               val -= cmd_base;
+
+       ucontrol->value.enumerated.item[0] = val;
+
+       return 0;
+}
+
+static int motmdm_value_put(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *ucontrol,
+                           enum motmdm_cmd reg,
+                           int cmd_base)
+{
+       struct snd_soc_component *component = 
snd_soc_kcontrol_component(kcontrol);
+       struct motmdm_driver_data *ddata = 
snd_soc_component_get_drvdata(component);
+       int error;
+
+       error = regmap_write(ddata->regmap, reg,
+                            ucontrol->value.enumerated.item[0] + cmd_base);
+       if (error)
+               return error;
+
+       regcache_mark_dirty(ddata->regmap);
+
+       return error;
+}
+
+static int motmdm_audio_out_get(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       return motmdm_value_get(kcontrol, ucontrol, CMD_AT_EACC, 1);
+}
+
+static int motmdm_audio_out_put(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       return motmdm_value_put(kcontrol, ucontrol, CMD_AT_EACC, 1);
+}
+
+static int motmdm_gain_get(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+       return motmdm_value_get(kcontrol, ucontrol, CMD_AT_CLVL, 0);
+}
+
+static int motmdm_gain_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+       return motmdm_value_put(kcontrol, ucontrol, CMD_AT_CLVL, 0);
+}
+
+static int motmdm_noise_get(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       return motmdm_value_get(kcontrol, ucontrol, CMD_AT_NREC, 0);
+}
+
+static int motmdm_noise_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+       return motmdm_value_put(kcontrol, ucontrol, CMD_AT_NREC, 0);
+}
+
+static const char * const motmdm_tonegen_dtmf_key_txt[] = {
+       "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D",
+       "*", "#"
+};
+
+static SOC_ENUM_SINGLE_EXT_DECL(motmd_tonegen_dtmf_enum,
+                               motmdm_tonegen_dtmf_key_txt);
+
+static int motmdm_dtmf_get(struct snd_kcontrol *kcontrol,
+                          struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component =
+               snd_soc_kcontrol_component(kcontrol);
+       struct motmdm_driver_data *ddata =
+               snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.enumerated.item[0] = ddata->dtmf_val;
+
+       return 0;
+}
+
+static int motmdm_dtmf_put(struct snd_kcontrol *kcontrol,
+                          struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component =
+               snd_soc_kcontrol_component(kcontrol);
+       struct motmdm_driver_data *ddata =
+               snd_soc_component_get_drvdata(component);
+
+       ddata->dtmf_val = ucontrol->value.enumerated.item[0];
+
+       return 0;
+}
+
+static int motmdm_tonegen_dtmf_send_get(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component =
+               snd_soc_kcontrol_component(kcontrol);
+       struct motmdm_driver_data *ddata =
+               snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.enumerated.item[0] = ddata->dtmf_en;
+
+       return 0;
+}
+
+static int motmdm_tonegen_dtmf_send_put(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component =
+               snd_soc_kcontrol_component(kcontrol);
+       struct motmdm_driver_data *ddata =
+               snd_soc_component_get_drvdata(component);
+       const unsigned char *cmd, *fmt = "AT+DTSE=%s,%i";
+       const char *tone = "";
+       int error;
+
+       if (!ddata->enabled)
+               return 0;
+
+       ddata->dtmf_en = ucontrol->value.enumerated.item[0];
+       if (ddata->dtmf_en)
+               tone = motmdm_tonegen_dtmf_key_txt[ddata->dtmf_val];
+
+       /* Value 0 enables tone generator, 1 disables it */
+       cmd = kasprintf(GFP_KERNEL, fmt, tone, !ddata->dtmf_en);
+
+       error = motmdm_send_command(ddata, cmd, strlen(cmd));
+       if (error < 0) {
+               dev_err(component->dev, "%s: %s failed with %i\n",
+                       __func__, cmd, error);
+               goto free;
+       }
+
+free:
+       kfree(cmd);
+
+       return error;
+}
+
+static int
+motmdm_enable_primary_dai(struct snd_soc_component *component)
+{
+       struct motmdm_driver_data *ddata =
+               snd_soc_component_get_drvdata(component);
+       int error;
+
+       if (!ddata->master_dai)
+               return -ENODEV;
+
+       error = snd_soc_dai_set_sysclk(ddata->master_dai, 1, 19200000,
+                                      SND_SOC_CLOCK_OUT);
+       if (error)
+               return error;
+
+       error = snd_soc_dai_set_fmt(ddata->master_dai,
+                                   SND_SOC_DAIFMT_I2S |
+                                   SND_SOC_DAIFMT_NB_NF |
+                                   SND_SOC_DAIFMT_CBM_CFM);
+       if (error)
+               return error;
+
+       error = snd_soc_dai_set_tdm_slot(ddata->master_dai, 0, 1, 1, 8);
+       if (error)
+               return error;
+
+       return error;
+}
+
+static int
+motmdm_disable_primary_dai(struct snd_soc_component *component)
+{
+       struct motmdm_driver_data *ddata =
+               snd_soc_component_get_drvdata(component);
+       int error;
+
+       if (!ddata->master_dai) {
+               return -ENODEV;
+       }
+
+       error = snd_soc_dai_set_sysclk(ddata->master_dai, 0, 26000000,
+                                      SND_SOC_CLOCK_OUT);
+       if (error) {
+               return error;
+       }
+
+       error = snd_soc_dai_set_fmt(ddata->master_dai,
+                                   SND_SOC_DAIFMT_CBM_CFM);
+       if (error) {
+               return error;
+       }
+
+       error = snd_soc_dai_set_tdm_slot(ddata->master_dai, 0, 0, 0, 48);
+       if (error) {
+               return error;
+       }
+
+       return error;
+}
+
+static int motmdm_find_primary_dai(struct snd_soc_component *component,
+       const char *name)
+{
+       struct motmdm_driver_data *ddata =
+               snd_soc_component_get_drvdata(component);
+       struct device_node *bitclkmaster = NULL, *framemaster = NULL;
+       struct device_node *ep, *master_ep, *master = NULL;
+       struct snd_soc_dai_link_component dlc = { 0 };
+       unsigned int daifmt;
+
+       ep = of_graph_get_next_endpoint(component->dev->of_node, NULL);
+       if (!ep)
+               return -ENODEV;
+
+       master_ep = of_graph_get_remote_endpoint(ep);
+       of_node_put(ep);
+       if (!master_ep)
+               return -ENODEV;
+
+       daifmt = snd_soc_of_parse_daifmt(master_ep, NULL,
+                                        &bitclkmaster, &framemaster);
+       of_node_put(master_ep);
+       if (bitclkmaster && framemaster)
+               master = of_graph_get_port_parent(bitclkmaster);
+       of_node_put(bitclkmaster);
+       of_node_put(framemaster);
+       if (!master)
+               return -ENODEV;
+
+       dlc.of_node = master;
+       dlc.dai_name = name;
+       ddata->master_dai = snd_soc_find_dai(&dlc);
+       of_node_put(master);
+       if (!ddata->master_dai)
+               return -EPROBE_DEFER;
+
+       dev_info(component->dev, "Master DAI is %s\n",
+                dev_name(ddata->master_dai->dev));
+
+       return 0;
+}
+
+static int motmdm_parse_tdm(struct snd_soc_component *component)
+{
+       return motmdm_find_primary_dai(component, "cpcap-voice");
+}
+
+static const struct snd_kcontrol_new motmdm_snd_controls[] = {
+        SOC_ENUM_EXT("Call Output", motmdm_out_enum,
+                     motmdm_audio_out_get,
+                     motmdm_audio_out_put),
+        SOC_SINGLE_EXT_TLV("Call Volume",
+                          0, 0, 7, 0,
+                          motmdm_gain_get,
+                          motmdm_gain_put,
+                          motmdm_gain_tlv),
+       SOC_SINGLE_BOOL_EXT("Call Noise Cancellation", 0,
+                           motmdm_noise_get,
+                           motmdm_noise_put),
+       SOC_ENUM_EXT("Call DTMF", motmd_tonegen_dtmf_enum,
+                    motmdm_dtmf_get,
+                    motmdm_dtmf_put),
+       SOC_SINGLE_BOOL_EXT("Call DTMF Send", 0,
+                           motmdm_tonegen_dtmf_send_get,
+                           motmdm_tonegen_dtmf_send_put),
+};
+
+static struct snd_soc_dai_driver motmdm_dai[] = {
+       {
+               .name = "mdm-call",
+               .playback = {
+                       .stream_name = "Voice Call Playback",
+                       .channels_min = 1,
+                       .channels_max = 2,
+                       .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+                       .formats = SNDRV_PCM_FMTBIT_S16_LE,
+               },
+               .capture = {
+                       .stream_name = "Voice Call Capture",
+                       .channels_min = 1,
+                       .channels_max = 2,
+                       .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+                       .formats = SNDRV_PCM_FMTBIT_S16_LE,
+               },
+       },
+};
+
+static int
+motmdm_notifier_call(struct notifier_block *bl, unsigned long state,
+                                                        void *unused)
+{
+       struct motmdm_driver_data *ddata =
+               container_of(bl, struct motmdm_driver_data, notifier);
+       bool enable, notify = false;
+       unsigned long flags;
+
+       enable = !!state;
+
+       spin_lock_irqsave(&ddata->lock, flags);
+       if (ddata->enabled != enable) {
+               ddata->enabled = enable;
+               notify = true;
+       }
+       spin_unlock_irqrestore(&ddata->lock, flags);
+
+       if (!notify)
+               return NOTIFY_DONE;
+
+       if (enable)
+               motmdm_enable_primary_dai(ddata->component);
+       else
+               motmdm_disable_primary_dai(ddata->component);
+
+       return NOTIFY_DONE;
+}
+
+static int voice_receive_data(struct serdev_device *serdev,
+                            const unsigned char *buf,
+                            size_t len)
+{
+        struct motmdm_driver_data *ddata = serdev_device_get_drvdata(serdev);
+       struct device *dev = ddata->component->dev;
+
+       printk("voice_receive_data: have %s %d\n", buf, len);
+
+       if (len > MOTMDM_AUDIO_MAX_LEN)
+               len = MOTMDM_AUDIO_MAX_LEN;
+
+       if (len <= MOTMDM_HEADER_LEN)
+               return 0;
+
+       printk("voice_receive_data: command reply? -- %s %d\n", buf, len);
+
+       snprintf(ddata->buf, len - MOTMDM_HEADER_LEN, buf + MOTMDM_HEADER_LEN);
+       dev_info(dev, "%s: received: %s\n", __func__, ddata->buf);
+       ddata->parsed = true;
+       wake_up(&ddata->read_queue);
+
+       return len;
+}
+
+static const struct serdev_device_ops voice_serdev_ops = {
+        .receive_buf    = voice_receive_data,
+        .write_wakeup   = serdev_device_write_wakeup,
+};
+
+static void motmdm_free_voice_serdev(struct motmdm_driver_data *ddata)
+{
+       serdev_device_close(ddata->serdev);
+}
+
+static int motmdm_soc_probe(struct snd_soc_component *component)
+{
+       struct motmdm_driver_data *ddata;
+       const unsigned char *cmd = "AT+CMUT=0";
+       int error;
+       u32 line;
+
+       ddata = devm_kzalloc(component->dev, sizeof(*ddata), GFP_KERNEL);
+       if (!ddata)
+               return -ENOMEM;
+
+       error = of_property_read_u32(component->dev->of_node, "reg", &line);
+       if (error)
+               return error;
+
+       ddata->serdev = (struct serdev_device *) component->dev;
+       ddata->component = component;
+       ddata->modem = component->dev->parent;
+       mutex_init(&ddata->mutex);
+       init_waitqueue_head(&ddata->read_queue);
+       ddata->len = PAGE_SIZE;
+       spin_lock_init(&ddata->lock);
+       snd_soc_component_set_drvdata(component, ddata);
+       ddata->len = MOTMDM_AUDIO_MAX_LEN;
+
+       ddata->buf = devm_kzalloc(component->dev, ddata->len, GFP_KERNEL);
+       if (!ddata->buf)
+               return -ENOMEM;
+
+       ddata->regmap = devm_regmap_init(component->dev, NULL, component,
+                                        &motmdm_regmap);
+       if (IS_ERR(ddata->regmap)) {
+               error = PTR_ERR(ddata->regmap);
+               dev_err(component->dev, "%s: Failed to allocate regmap: %d\n",
+                       __func__, error);
+
+               return error;
+       }
+
+       serdev_device_set_drvdata(ddata->serdev, ddata);
+        serdev_device_set_client_ops(ddata->serdev, &voice_serdev_ops);
+
+        error = serdev_device_open(ddata->serdev);
+        if (error)
+                return error;
+
+       error = motmdm_parse_tdm(component);
+       if (error)
+               goto unregister_serdev;
+
+       regcache_sync(ddata->regmap);
+
+       error = motmdm_send_command(ddata, cmd, strlen(cmd));
+       if (error < 0)
+               goto unregister_serdev;
+
+       error = motmdm_disable_primary_dai(ddata->component);
+       if (error)
+               goto unregister_serdev;
+
+       ddata->notifier.notifier_call = motmdm_notifier_call;
+       register_modem_state_notifier(&ddata->notifier);
+
+       return 0;
+
+unregister_serdev:
+       motmdm_free_voice_serdev(ddata);
+       serdev_device_close(ddata->serdev);
+
+       return error;
+}
+
+static void motmdm_soc_remove(struct snd_soc_component *component)
+{
+       struct motmdm_driver_data *ddata = 
snd_soc_component_get_drvdata(component);
+
+       unregister_modem_state_notifier(&ddata->notifier);
+
+       motmdm_free_voice_serdev(ddata);
+}
+
+static struct snd_soc_component_driver soc_codec_dev_motmdm = {
+       .probe = motmdm_soc_probe,
+       .remove = motmdm_soc_remove,
+       .controls = motmdm_snd_controls,
+       .num_controls = ARRAY_SIZE(motmdm_snd_controls),
+       .idle_bias_on = 1,
+       .use_pmdown_time = 1,
+       .endianness = 1,
+       .non_legacy_dai_naming = 1,
+};
+
+static int motmdm_codec_probe(struct serdev_device *serdev)
+{
+       return devm_snd_soc_register_component(&serdev->dev,
+                                              &soc_codec_dev_motmdm,
+                                              motmdm_dai,
+                                              ARRAY_SIZE(motmdm_dai));
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id motmdm_of_match[] = {
+       { .compatible = "motorola,mapphone-mdm6600-codec" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, motmdm_of_match);
+#endif
+
+static struct serdev_device_driver motmdm_driver = {
+       .probe = motmdm_codec_probe,
+       .driver = {
+               .name = "mot-mdm6600-codec",
+               .of_match_table = of_match_ptr(motmdm_of_match),
+       },
+};
+module_serdev_device_driver(motmdm_driver);
+
+MODULE_ALIAS("platform:motmdm-codec");
+MODULE_DESCRIPTION("ASoC Motorola Mapphone MDM6600 codec driver");
+MODULE_AUTHOR("Tony Lindgren <t...@atomide.com>");
+MODULE_LICENSE("GPL v2");

Attachment: signature.asc
Description: PGP signature

Reply via email to