aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTony Lindgren <tony@atomide.com>2019-03-03 21:28:39 -0800
committerTony Lindgren <tony@atomide.com>2019-03-03 21:28:39 -0800
commitdb250b51bdffe8b5d649506e13b0ba7be43df40a (patch)
tree04904fe8256e9c14590b8e3d8bb15ca7282c93a8
parentce0d9be1344641e5293ffa4a0ddf7dd0bbf6f568 (diff)
parent6695f4ec8a19f6fbfcfdccb9928ad31a614fab40 (diff)
downloadlinux-omap-droid4-pending-v5.0.tar.gz
Merge branch 'droid4-pending-mdm-v5.0' into droid4-pending-v5.0droid4-pending-v5.0
-rw-r--r--Documentation/devicetree/bindings/mfd/motorola-mdm.txt18
-rw-r--r--arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi4
-rw-r--r--arch/arm/boot/dts/omap4-droid4-xt894.dts62
-rw-r--r--arch/arm/configs/omap2plus_defconfig7
-rw-r--r--drivers/gnss/Kconfig8
-rw-r--r--drivers/gnss/Makefile3
-rw-r--r--drivers/gnss/motmdm.c393
-rw-r--r--drivers/mfd/Kconfig8
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/motorola-mdm.c1208
-rw-r--r--drivers/tty/n_gsm.c548
-rw-r--r--include/linux/mfd/motorola-mdm.h84
-rw-r--r--include/linux/serdev-gsm.h227
-rw-r--r--sound/soc/codecs/Kconfig8
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/cpcap.c119
-rw-r--r--sound/soc/codecs/motmdm.c514
-rw-r--r--sound/soc/ti/omap-mcbsp-priv.h2
-rw-r--r--sound/soc/ti/omap-mcbsp.c76
19 files changed, 3145 insertions, 147 deletions
diff --git a/Documentation/devicetree/bindings/mfd/motorola-mdm.txt b/Documentation/devicetree/bindings/mfd/motorola-mdm.txt
new file mode 100644
index 0000000000000..92cb0dd70fa85
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/motorola-mdm.txt
@@ -0,0 +1,18 @@
+Motorola MDM modem device tree binding
+
+Required properties:
+- compatible : Must be one of "motorola,mapphone-mdm6600-serdev"
+ or a generic "motorola,mdm6600"
+- phys : USB PHY with shared pins for UART power management
+- phy-names : Name for the USB PHY
+- reg : SPI chip select
+
+Example:
+
+&uart1 {
+ modem {
+ compatible = "motorola,mapphone-mdm6600-serdev";
+ phys = <&fsusb1_phy>;
+ phy-names = "usb";
+ };
+};
diff --git a/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi b/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi
index f57acf8f66b95..60e50caf8d702 100644
--- a/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi
+++ b/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi
@@ -70,12 +70,16 @@
cpcap_audio: audio-codec {
#sound-dai-cells = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
port@0 {
+ reg = <0>;
cpcap_audio_codec0: endpoint {
};
};
port@1 {
+ reg = <1>;
cpcap_audio_codec1: endpoint {
};
};
diff --git a/arch/arm/boot/dts/omap4-droid4-xt894.dts b/arch/arm/boot/dts/omap4-droid4-xt894.dts
index 552d8bdab5c91..8b8e1c76e15fd 100644
--- a/arch/arm/boot/dts/omap4-droid4-xt894.dts
+++ b/arch/arm/boot/dts/omap4-droid4-xt894.dts
@@ -672,6 +672,24 @@
pinctrl-0 = <&uart1_pins>;
interrupts-extended = <&wakeupgen GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH
&omap4_pmx_core 0xfc>;
+
+ modem {
+ compatible = "motorola,mapphone-mdm6600-serdev";
+ phys = <&fsusb1_phy>;
+ phy-names = "usb";
+
+ mot_mdm6600_audio: audio-codec {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #sound-dai-cells = <1>;
+
+ port@0 {
+ mot_mdm6600_audio_codec0: endpoint {
+ remote-endpoint = <&cpu_dai_mdm>;
+ };
+ };
+ };
+ };
};
&uart3 {
@@ -748,12 +766,18 @@
pinctrl-0 = <&mcbsp2_pins>;
status = "okay";
- mcbsp2_port: port {
- cpu_dai2: endpoint {
- dai-format = "i2s";
- remote-endpoint = <&cpcap_audio_codec0>;
- frame-master = <&cpcap_audio_codec0>;
- bitclock-master = <&cpcap_audio_codec0>;
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ mcbsp2_port: port@0 {
+ reg = <0>;
+ cpu_dai2: endpoint@0 {
+ dai-format = "i2s";
+ remote-endpoint = <&cpcap_audio_codec0>;
+ frame-master = <&cpcap_audio_codec0>;
+ bitclock-master = <&cpcap_audio_codec0>;
+ };
};
};
};
@@ -764,12 +788,26 @@
pinctrl-0 = <&mcbsp3_pins>;
status = "okay";
- mcbsp3_port: port {
- cpu_dai3: endpoint {
- dai-format = "dsp_a";
- frame-master = <&cpcap_audio_codec1>;
- bitclock-master = <&cpcap_audio_codec1>;
- remote-endpoint = <&cpcap_audio_codec1>;
+ ports {
+ mcbsp3_port: port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu_dai3: endpoint@0 {
+ reg = <0>;
+ dai-format = "dsp_a";
+ frame-master = <&cpcap_audio_codec1>;
+ bitclock-master = <&cpcap_audio_codec1>;
+ remote-endpoint = <&cpcap_audio_codec1>;
+ };
+
+ cpu_dai_mdm: endpoint@1 {
+ reg = <1>;
+ dai-format = "dsp_a";
+ frame-master = <&cpcap_audio_codec1>;
+ bitclock-master = <&cpcap_audio_codec1>;
+ remote-endpoint = <&mot_mdm6600_audio_codec0>;
+ };
};
};
};
diff --git a/arch/arm/configs/omap2plus_defconfig b/arch/arm/configs/omap2plus_defconfig
index 96e50ee1a2f26..1f25ffbed806f 100644
--- a/arch/arm/configs/omap2plus_defconfig
+++ b/arch/arm/configs/omap2plus_defconfig
@@ -123,6 +123,10 @@ CONFIG_DEVTMPFS_MOUNT=y
CONFIG_DMA_CMA=y
CONFIG_OMAP_OCP2SCP=y
CONFIG_CONNECTOR=m
+CONFIG_GNSS=m
+CONFIG_GNSS_MOTMDM=m
+CONFIG_GNSS_SIRF_SERIAL=m
+CONFIG_GNSS_UBX_SERIAL=m
CONFIG_MTD=y
CONFIG_MTD_CMDLINE_PARTS=y
CONFIG_MTD_BLOCK=y
@@ -239,6 +243,7 @@ CONFIG_INPUT_PALMAS_PWRBUTTON=m
CONFIG_INPUT_PWM_VIBRA=m
CONFIG_SERIO=m
# CONFIG_LEGACY_PTYS is not set
+CONFIG_N_GSM=m
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=32
@@ -305,6 +310,7 @@ CONFIG_MFD_TI_LP87565=y
CONFIG_MFD_TPS65218=y
CONFIG_MFD_TPS65910=y
CONFIG_TWL6040_CORE=y
+CONFIG_MFD_MOTMDM=m
CONFIG_REGULATOR_CPCAP=y
CONFIG_REGULATOR_GPIO=y
CONFIG_REGULATOR_LM363X=m
@@ -391,6 +397,7 @@ CONFIG_SND_SOC_OMAP3_PANDORA=m
CONFIG_SND_SOC_OMAP3_TWL4030=m
CONFIG_SND_SOC_CPCAP=m
CONFIG_SND_SOC_TLV320AIC23_I2C=m
+CONFIG_SND_SOC_MOTMDM=m
CONFIG_SND_SIMPLE_CARD=m
CONFIG_SND_AUDIO_GRAPH_CARD=m
CONFIG_HID_GENERIC=m
diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
index 6abc885145121..2ae99cc49c6d8 100644
--- a/drivers/gnss/Kconfig
+++ b/drivers/gnss/Kconfig
@@ -12,6 +12,14 @@ menuconfig GNSS
if GNSS
+config GNSS_MOTMDM
+ tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
+ depends on MFD_MOTMDM
+ ---help---
+ Say Y here if you have a Motorola modem using TS 27.010 line
+ discipline for GNSS such as a Motorola Mapphone series device
+ like Droid 4.
+
config GNSS_SERIAL
tristate
diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile
index 5cf0ebe0330a5..0f7b3da9ac650 100644
--- a/drivers/gnss/Makefile
+++ b/drivers/gnss/Makefile
@@ -6,6 +6,9 @@
obj-$(CONFIG_GNSS) += gnss.o
gnss-y := core.o
+obj-$(CONFIG_GNSS_MOTMDM) += gnss-motmdm.o
+gnss-motmdm-y := motmdm.o
+
obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o
gnss-serial-y := serial.o
diff --git a/drivers/gnss/motmdm.c b/drivers/gnss/motmdm.c
new file mode 100644
index 0000000000000..5ec2fa25df6f6
--- /dev/null
+++ b/drivers/gnss/motmdm.c
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Modem TS 27.010 serdev GNSS driver
+ *
+ * Copyright (C) 2018 Tony Lindgren <tony@atomide.com>
+ */
+
+#include <linux/errno.h>
+#include <linux/gnss.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/serdev.h>
+#include <linux/serdev-gsm.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include <linux/mfd/motorola-mdm.h>
+
+#define MOTMDM_GNSS_TIMEOUT 1000
+#define MOTMDM_GNSS_RATE 1000
+
+/*
+ * Motorola MDM GNSS device talks AT commands on a dedicated TS 27.010
+ * channel.
+ */
+#define MOTMDM_GNSS_MPD_LEN 4 /* +MPD */
+#define MOTMDM_GNSS_STATUS_LEN (MOTMDM_GNSS_MPD_LEN + 7) /* STATUS= */
+#define MOTMDM_GNSS_NMEA_LEN (MOTMDM_GNSS_MPD_LEN + 8) /* NMEA=NN, */
+
+enum motmdm_gnss_status {
+ MOTMDM_GNSS_UNKNOWN,
+ MOTMDM_GNSS_INITIALIZED,
+ MOTMDM_GNSS_DATA_OR_TIMEOUT,
+ MOTMDM_GNSS_STARTED,
+ MOTMDM_GNSS_STOPPED,
+};
+
+struct motmdm_gnss_data {
+ struct gnss_device *gdev;
+ struct device *modem;
+ struct motmdm_dlci mot_dlci;
+ struct delayed_work restart_work;
+ ktime_t last_update;
+ int status;
+ unsigned char *buf;
+ size_t len;
+ unsigned char *dbg;
+ size_t dbglen;
+};
+
+static unsigned int rate_ms = MOTMDM_GNSS_RATE;
+module_param(rate_ms, uint, 0644);
+MODULE_PARM_DESC(rate_ms, "GNSS refresh rate between 1000 and 16000 ms (default 1000 ms)");
+
+#ifdef DEBUG
+#define MOTMDM_GNSS_DEBUG_BUF_LEN 64
+static void motmdm_gnss_debug(struct motmdm_gnss_data *ddata,
+ const unsigned char *buf, size_t len,
+ const char *msg)
+{
+ struct gnss_device *gdev = ddata->gdev;
+
+ if (!ddata->dbg)
+ return;
+
+ memset(ddata->dbg, '\0', ddata->dbglen);
+ snprintf(ddata->dbg, min(ddata->dbglen - 1, len), buf);
+ dev_dbg(&gdev->dev, "%s: %s\n", msg, ddata->dbg);
+}
+#else
+#define MOTMDM_GNSS_DEBUG_BUF_LEN 0
+static void motmdm_gnss_debug(struct motmdm_gnss_data *ddata,
+ const unsigned char *buf, size_t len,
+ const char *msg)
+{
+}
+#endif
+
+/*
+ * Android uses AT+MPDSTART=0,1,100,0 which starts GNSS for a while
+ * and then GNSS needs to be kicked with an AT command based on a
+ * status message.
+ */
+static void motmdm_gnss_restart(struct work_struct *work)
+{
+ struct motmdm_gnss_data *ddata =
+ container_of(work, struct motmdm_gnss_data,
+ restart_work.work);
+ struct motmdm_dlci *mot_dlci = &ddata->mot_dlci;
+ struct gnss_device *gdev = ddata->gdev;
+ const unsigned char *cmd = "AT+MPDSTART=0,1,100,0";
+ int error;
+
+ ddata->last_update = ktime_get();
+
+ error = motmdm_send_command(ddata->modem, mot_dlci,
+ MOTMDM_GNSS_TIMEOUT,
+ cmd, strlen(cmd),
+ ddata->buf, ddata->len);
+ if (error < 0) {
+ /* Timeouts seem common.. Don't warn and request the data again */
+ if (error != -ETIMEDOUT)
+ dev_warn(&gdev->dev, "%s: could not start: %i\n",
+ __func__, error);
+
+ schedule_delayed_work(&ddata->restart_work,
+ msecs_to_jiffies(MOTMDM_GNSS_RATE));
+
+ return;
+ }
+}
+
+static void motmdm_gnss_start(struct gnss_device *gdev, int delay_ms)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ ktime_t now, next, delta;
+ int next_ms;
+
+ now = ktime_get();
+ next = ktime_add_ms(ddata->last_update, delay_ms);
+ delta = ktime_sub(next, now);
+ next_ms = ktime_to_ms(delta);
+
+ if (next_ms < 0)
+ next_ms = 0;
+ if (next_ms > delay_ms)
+ next_ms = delay_ms;
+
+ schedule_delayed_work(&ddata->restart_work, msecs_to_jiffies(next_ms));
+}
+
+static int motmdm_gnss_stop(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct motmdm_dlci *mot_dlci = &ddata->mot_dlci;
+ const unsigned char *cmd = "AT+MPDSTOP";
+
+ cancel_delayed_work_sync(&ddata->restart_work);
+
+ return motmdm_send_command(ddata->modem, mot_dlci,
+ MOTMDM_GNSS_TIMEOUT,
+ cmd, strlen(cmd),
+ ddata->buf, ddata->len);
+}
+
+static int motmdm_gnss_init(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct motmdm_dlci *mot_dlci = &ddata->mot_dlci;
+ const unsigned char *cmd = "AT+MPDINIT=1";
+ int error;
+
+ error = motmdm_send_command(ddata->modem, mot_dlci,
+ MOTMDM_GNSS_TIMEOUT,
+ cmd, strlen(cmd),
+ ddata->buf, ddata->len);
+ if (error < 0)
+ return error;
+
+ motmdm_gnss_start(gdev, 0);
+
+ return 0;
+}
+
+static int motmdm_gnss_finish(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct motmdm_dlci *mot_dlci = &ddata->mot_dlci;
+ const unsigned char *cmd = "AT+MPDINIT=0";
+ int error;
+
+ error = motmdm_gnss_stop(gdev);
+ if (error < 0)
+ return error;
+
+ return motmdm_send_command(ddata->modem, mot_dlci,
+ MOTMDM_GNSS_TIMEOUT,
+ cmd, strlen(cmd),
+ ddata->buf, ddata->len);
+}
+
+static int motmdm_gnss_receive_data(struct motmdm_dlci *mot_dlci,
+ const unsigned char *buf,
+ size_t len)
+{
+ struct gnss_device *gdev = mot_dlci->drvdata;
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ const unsigned char *msg;
+ size_t msglen;
+ int error = 0;
+
+ if (len <= MOTMDM_GNSS_MPD_LEN)
+ return 0;
+
+ switch (buf[MOTMDM_GNSS_MPD_LEN]) {
+ case 'N': /* UNNNN~+MPDNMEA=NN, */
+ msg = buf + MOTMDM_GNSS_NMEA_LEN;
+ msglen = len - MOTMDM_GNSS_NMEA_LEN;
+
+ /*
+ * Firmware bug: Strip out extra duplicate line break always
+ * in the data
+ */
+ msglen--;
+
+ /*
+ * Firmware bug: Strip out extra data based on an
+ * earlier line break in the data
+ */
+ if (msg[msglen - 5 - 1] == 0x0a)
+ msglen -= 5;
+
+ error = gnss_insert_raw(gdev, msg, msglen);
+ break;
+ case 'S': /* UNNNN~+MPDSTATUS=N,NN */
+ msg = buf + MOTMDM_GNSS_STATUS_LEN;
+ msglen = len - MOTMDM_GNSS_STATUS_LEN;
+ motmdm_gnss_debug(ddata, msg, msglen, "status");
+
+ switch (msg[0]) {
+ case '1':
+ ddata->status = MOTMDM_GNSS_INITIALIZED;
+ break;
+ case '2':
+ ddata->status = MOTMDM_GNSS_DATA_OR_TIMEOUT;
+ if (rate_ms < MOTMDM_GNSS_RATE)
+ rate_ms = MOTMDM_GNSS_RATE;
+ if (rate_ms > 16 * MOTMDM_GNSS_RATE)
+ rate_ms = 16 * MOTMDM_GNSS_RATE;
+ motmdm_gnss_start(gdev, rate_ms);
+ break;
+ case '3':
+ ddata->status = MOTMDM_GNSS_STARTED;
+ break;
+ case '4':
+ ddata->status = MOTMDM_GNSS_STOPPED;
+ break;
+ default:
+ ddata->status = MOTMDM_GNSS_UNKNOWN;
+ break;
+ }
+ break;
+ case 'X': /* What does UNNNN~+MPDXREQ=N mean? */
+ default:
+ motmdm_gnss_debug(ddata, buf, len, "unhandled");
+ break;
+ }
+
+ return 0;
+}
+
+static int motmdm_gnss_open(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct motmdm_dlci *mot_dlci = &ddata->mot_dlci;
+ int error;
+
+ mot_dlci->drvdata = gdev;
+ mot_dlci->line = MOTMDM_DLCI4;
+ mot_dlci->receive_data = motmdm_gnss_receive_data;
+
+ error = motmdm_register_dlci(ddata->modem, mot_dlci);
+ if (error)
+ return error;
+
+ error = motmdm_gnss_init(gdev);
+ if (error) {
+ motmdm_unregister_dlci(ddata->modem, mot_dlci);
+
+ return error;
+ }
+
+ return 0;
+}
+
+static void motmdm_gnss_close(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct motmdm_dlci *mot_dlci = &ddata->mot_dlci;
+ int error;
+
+ mot_dlci->receive_data = NULL;
+ error = motmdm_gnss_finish(gdev);
+ if (error < 0)
+ dev_warn(&gdev->dev, "%s: close failed: %i\n",
+ __func__, error);
+
+ motmdm_unregister_dlci(ddata->modem, mot_dlci);
+}
+
+static int motmdm_gnss_write_raw(struct gnss_device *gdev,
+ const unsigned char *buf,
+ size_t count)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct motmdm_dlci *mot_dlci = &ddata->mot_dlci;
+
+ return motmdm_send_command(ddata->modem, mot_dlci,
+ MOTMDM_GNSS_TIMEOUT,
+ buf, count, ddata->buf, ddata->len);
+}
+
+static const struct gnss_operations motmdm_gnss_ops = {
+ .open = motmdm_gnss_open,
+ .close = motmdm_gnss_close,
+ .write_raw = motmdm_gnss_write_raw,
+};
+
+static int motmdm_gnss_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct motmdm_gnss_data *ddata;
+ struct gnss_device *gdev;
+ int ret;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&ddata->restart_work, motmdm_gnss_restart);
+ ddata->modem = dev->parent;
+ ddata->len = PAGE_SIZE;
+ ddata->dbglen = MOTMDM_GNSS_DEBUG_BUF_LEN;
+
+ ddata->buf = devm_kzalloc(dev, ddata->len, GFP_KERNEL);
+ if (!ddata->buf)
+ return -ENOMEM;
+
+ if (ddata->dbglen) {
+ ddata->dbg = devm_kzalloc(dev, ddata->dbglen, GFP_KERNEL);
+ if (!ddata->dbg)
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, ddata);
+
+ gdev = gnss_allocate_device(dev);
+ if (!gdev)
+ return -ENOMEM;
+
+ gdev->type = GNSS_TYPE_NMEA;
+ gdev->ops = &motmdm_gnss_ops;
+ gnss_set_drvdata(gdev, ddata);
+ ddata->gdev = gdev;
+
+ ret = gnss_register_device(gdev);
+ if (ret)
+ goto err_put_device;
+
+ return 0;
+
+err_put_device:
+ gnss_put_device(ddata->gdev);
+
+ return ret;
+}
+
+static int motmdm_gnss_remove(struct platform_device *pdev)
+{
+ struct motmdm_gnss_data *data = platform_get_drvdata(pdev);
+
+ gnss_deregister_device(data->gdev);
+ gnss_put_device(data->gdev);
+
+ return 0;
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id motmdm_gnss_of_match[] = {
+ { .compatible = "motorola,mapphone-mdm6600-gnss" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, motmdm_gnss_of_match);
+#endif
+
+static struct platform_driver motmdm_gnss_driver = {
+ .driver = {
+ .name = "gnss-mot-mdm6600",
+ .of_match_table = of_match_ptr(motmdm_gnss_of_match),
+ },
+ .probe = motmdm_gnss_probe,
+ .remove = motmdm_gnss_remove,
+};
+module_platform_driver(motmdm_gnss_driver);
+
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("Motorola Mapphone MDM6600 GNSS receiver driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 76f9909cf3966..cdd6b39df12ca 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1908,6 +1908,14 @@ config MFD_VEXPRESS_SYSREG
System Registers are the platform configuration block
on the ARM Ltd. Versatile Express board.
+config MFD_MOTMDM
+ tristate "Motorola Modem TS 27.010 Serdev Driver"
+ depends on SERIAL_DEV_BUS && PHY_MAPPHONE_MDM6600
+ help
+ Select this for Motorola modems using TS 27.010 serial line
+ discipline such as MDM6600 modem found on Motorola Mapphone
+ series of devices such as Droid 4.
+
config RAVE_SP_CORE
tristate "RAVE SP MCU core driver"
depends on SERIAL_DEV_BUS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 12980a4ad4608..89c2d6d130a49 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -239,6 +239,7 @@ obj-$(CONFIG_MFD_STM32_LPTIMER) += stm32-lptimer.o
obj-$(CONFIG_MFD_STM32_TIMERS) += stm32-timers.o
obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o
obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o
+obj-$(CONFIG_MFD_MOTMDM) += motorola-mdm.o
obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o
obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
diff --git a/drivers/mfd/motorola-mdm.c b/drivers/mfd/motorola-mdm.c
new file mode 100644
index 0000000000000..119ac19c77581
--- /dev/null
+++ b/drivers/mfd/motorola-mdm.c
@@ -0,0 +1,1208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola TS 27.010 serial line discipline serdev driver
+ * Copyright (C) 2018 Tony Lindgren <tony@atomide.com>
+ */
+
+#include <linux/cdev.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/serdev.h>
+#include <linux/serdev-gsm.h>
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/motorola-mdm.h>
+#include <linux/phy/phy.h>
+
+#include <uapi/linux/gsmmux.h>
+
+#define MOTMDM_C_N2 3 /* TS27.010 default value */
+#define MOTMDM_DLCI_MIN 1
+#define MOTMDM_DLCI_MAX 12
+#define MOTMDM_DLCI_MASK 0x1ffe /* MOTMDM_DLCI1 to 12 */
+#define MOTMDM_ID_LEN 5 /* U + unsigned short */
+#define MOTMDM_CMD_LEN(x) (MOTMDM_ID_LEN + (x) + 1)
+#define MOTMDM_WRITE_BUF_SIZE 1024
+#define MOTMDM_READ_FIFO_SIZE 4096
+
+struct motmdm_cfg {
+ unsigned long cdevmask;
+ unsigned int aggressive_pm:1;
+ int modem_dlci;
+ int codec_dlci;
+};
+
+struct motmdm {
+ struct device *dev;
+ struct phy *phy;
+ struct gsm_serdev gsd;
+ const struct motmdm_cfg *cfg;
+ struct class *class;
+ struct list_head dlcis;
+ struct list_head cdevs;
+ dev_t dev_id;
+ u16 cmdid;
+};
+
+struct motmdm_response {
+ struct list_head node;
+ u16 id;
+ const unsigned char *cmd;
+ size_t cmdlen;
+ size_t reslen;
+ unsigned char *buf;
+ size_t len;
+ unsigned int handled:1;
+};
+
+struct motmdm_cdev {
+ struct motmdm *ddata;
+ struct list_head node;
+ struct motmdm_dlci *dlci;
+ struct device *dev;
+ struct cdev cdev;
+ struct rw_semaphore rwsem;
+ unsigned int count;
+ unsigned int disconnected:1;
+ struct mutex read_mutex; /* char dev write lock */
+ struct mutex write_mutex; /* char dev read lock */
+ char *write_buf;
+ size_t write_buf_sz;
+ size_t write_offset;
+};
+
+static const char * const motmdm_driver_name = "motmdm";
+
+static int motmdm_runtime_suspend(struct device *dev)
+{
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ int err;
+
+ if (IS_ERR_OR_NULL(ddata->phy))
+ return 0;
+
+ err = phy_pm_runtime_put(ddata->phy);
+ if (err < 0)
+ dev_warn(dev, "%s: phy_pm_runtime_put: %i\n",
+ __func__, err);
+
+ atomic_set(&ddata->gsd.asleep, 1);
+
+ return 0;
+}
+
+static int motmdm_runtime_resume(struct device *dev)
+{
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ int err;
+
+ if (IS_ERR_OR_NULL(ddata->phy))
+ return 0;
+
+ err = phy_pm_runtime_get_sync(ddata->phy);
+ if (err < 0)
+ dev_warn(dev, "%s: phy_pm_runtime_get: %i\n",
+ __func__, err);
+
+ atomic_set(&ddata->gsd.asleep, 0);
+ gsm_serdev_data_kick(&ddata->gsd);
+
+ return 0;
+}
+
+static const struct dev_pm_ops motmdm_pm_ops = {
+ SET_RUNTIME_PM_OPS(motmdm_runtime_suspend,
+ motmdm_runtime_resume,
+ NULL)
+};
+
+/*
+ * Motorola MDM6600 devices have GPIO wake pins shared between the USB PHY and
+ * the TS 27.010 interface. So for PM, we need to use the phy_pm_runtime
+ * runtime calls. Otherwise the modem won't respond to anything on the UART
+ * and will never idle either.
+ */
+static int motmdm_init_phy(struct device *dev)
+{
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ int err;
+
+ ddata->phy = devm_of_phy_get(dev, dev->of_node, NULL);
+ if (IS_ERR(ddata->phy)) {
+ err = PTR_ERR(ddata->phy);
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "%s: no phy: %i\n", __func__, err);
+
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Motorola MDM6600 devices add a custom packet numbering layer on top of the
+ * TS 27.010 DLCI channels. This is a layering violation as all the devices
+ * are on dedicated channels already. For some reason the packet numbering is
+ * not specific to each DLCI.. It is for all the DLCI instead. As both ends
+ * can increase packet IDs, conflicts are guaranteed but seem to be harmless.
+ * Who knows, maybe the packet IDs were specified by frustrated developers to
+ * debug buggy modem code reponding on a wrong DLCI. Valid packet numbers are
+ * from 0000 to 9999 decimal. We just parse the modem sent packet number to
+ * match the response to sent commands and don't use modem sent packet
+ * numbers it for new command packets we send out.
+ */
+static int motmdm_read_packet_id(struct gsm_serdev_dlci *gsm_dlci,
+ const unsigned char *buf,
+ size_t len)
+{
+ struct motmdm_dlci *mot_dlci =
+ container_of(gsm_dlci, struct motmdm_dlci,
+ gsm_dlci);
+ struct motmdm *ddata = mot_dlci->privdata;
+ unsigned char tmp[MOTMDM_ID_LEN];
+ int err;
+ u16 id;
+
+ if (WARN(!ddata, "%s no ddata?\n", __func__))
+ return 0;
+
+ if (len < MOTMDM_ID_LEN || buf[0] != 'U')
+ return -ECOMM;
+
+ snprintf(tmp, MOTMDM_ID_LEN, "%s", buf + 1);
+ err = kstrtou16(tmp, 10, &id);
+ if (err)
+ return -ECOMM;
+
+ return id;
+}
+
+/*
+ * For new packets, we just use jiffies based numbering and let the modem
+ * deal with any possible numbering conflicts across the DLCI.
+ */
+static int motmdm_new_packet_id(void)
+{
+ return jiffies % 10000;
+}
+
+/*
+ * The modem DLCI1 provides also modem status information. We just forward
+ * DLCI1 as a character device to userspace. However in a bit of a layering
+ * violation, we also need to parse the modem state from DLCI1 for modem
+ * state notifications.
+ *
+ * The notifications start with a '~' character and are separate from modem
+ * commands. So let's try to stick to just parsing the notifications here.
+ *
+ * This is needed for Alsa ASoC codec driver to reconfigure clocks and codec
+ * hardware with set_tdm_slot() for voice calls automatically. And it can be
+ * later on used for things like signal strength etc.
+ */
+static void motmdm_read_state(struct motmdm_dlci *mot_dlci,
+ const unsigned char *buf,
+ size_t len)
+{
+ struct motmdm *ddata = mot_dlci->privdata;
+ struct motmdm_dlci *tmp;
+ enum motmdm_state state = MOTMDM_STATE_IDLE;
+
+ switch (len) {
+ case 12 + 1:
+ if (buf[0] != '~')
+ break;
+ if (!strncmp(buf, "~+CIEV=1,1,0", 12))
+ state = MOTMDM_STATE_CONNECTING;
+ if (!strncmp(buf, "~+CIEV=1,4,0", 12))
+ state = MOTMDM_STATE_INCOMING;
+ else if (!strncmp(buf, "~+CIEV=1,2,0", 12))
+ state = MOTMDM_STATE_CONNECTED;
+ else if (!strncmp(buf, "~+CIEV=1,0,0", 12))
+ state = MOTMDM_STATE_DISCONNECTED;
+ break;
+ default:
+ return;
+ }
+
+ if (state == MOTMDM_STATE_IDLE)
+ return;
+
+ list_for_each_entry(tmp, &ddata->dlcis, node) {
+ if (!tmp->notify)
+ continue;
+
+ tmp->notify(tmp, state);
+ }
+}
+
+/* Fix line breaks for apps if needed and feed kfifo */
+static int motmdm_dlci_feed_kfifo(struct motmdm_dlci *mot_dlci,
+ const unsigned char *buf,
+ size_t len)
+{
+ size_t newlen = len;
+ int err;
+
+ if (len && buf[len - 1] == '\n') {
+ if (len > 1 && buf[len - 2] != '\r')
+ newlen--;
+ else if (len == 1)
+ newlen--;
+ }
+
+ err = kfifo_in(&mot_dlci->read_fifo, buf, newlen);
+ if (err != newlen)
+ return -ENOSPC;
+
+ if (newlen != len) {
+ err = kfifo_in(&mot_dlci->read_fifo, "\r\n", 2);
+ if (err != 2)
+ err = -ENOSPC;
+ else
+ newlen += err;
+ }
+
+ return newlen;
+}
+
+/*
+ * Read handling for Motorola custom layering on top of TS 27.010
+ */
+static int motmdm_dlci_receive_buf(struct gsm_serdev_dlci *gsm_dlci,
+ const unsigned char *buf,
+ size_t len)
+{
+ struct motmdm_dlci *mot_dlci =
+ container_of(gsm_dlci, struct motmdm_dlci,
+ gsm_dlci);
+ struct motmdm *ddata = mot_dlci->privdata;
+ const unsigned char *msg;
+ size_t msglen;
+ int id, err;
+
+ if (len < (MOTMDM_ID_LEN + 1) || buf[0] != 'U')
+ return 0;
+
+ id = motmdm_read_packet_id(gsm_dlci, buf, len);
+ if (id < 0)
+ return 0;
+
+ /* Strip out Motorola custom packet numbering */
+ msg = buf + MOTMDM_ID_LEN;
+ msglen = len - MOTMDM_ID_LEN;
+
+ if (mot_dlci->line == ddata->cfg->modem_dlci)
+ motmdm_read_state(mot_dlci, msg, msglen);
+
+ /* Motorola custom data or a command ack? */
+ if (buf[MOTMDM_ID_LEN] == '~' && mot_dlci->receive_data)
+ mot_dlci->receive_data(mot_dlci, msg + 1, msglen - 1);
+ else if (mot_dlci->handle_command)
+ mot_dlci->handle_command(mot_dlci, id, msg, msglen);
+
+ if (kfifo_initialized(&mot_dlci->read_fifo)) {
+ err = motmdm_dlci_feed_kfifo(mot_dlci, msg, msglen);
+ if (err < 0)
+ goto err_kfifo;
+ }
+
+ err = msglen;
+
+ wake_up_interruptible(&mot_dlci->read_queue);
+
+err_kfifo:
+
+ return err;
+}
+
+/*
+ * Helper for child device drivers to send a command to a DLCI and wait
+ * for result with a matching packet ID.
+ */
+static int motmdm_dlci_send_command(struct device *dev,
+ struct motmdm_dlci *mot_dlci,
+ unsigned long timeout_ms,
+ const unsigned char *cmd, size_t cmdlen,
+ unsigned char *rsp, size_t rsplen)
+{
+ struct motmdm_response *resp, *tmp;
+ struct list_head *pos, *q;
+ unsigned char *delim;
+ int err;
+
+ resp = kzalloc(sizeof(*resp), GFP_KERNEL);
+ if (!resp)
+ return -ENOMEM;
+
+ memset(rsp, 0, rsplen);
+
+ resp->cmd = cmd;
+ resp->cmdlen = cmdlen;
+ resp->buf = rsp;
+ resp->len = rsplen;
+ resp->id = motmdm_new_packet_id();
+
+ /* Firmware will return only the command without value */
+ if (cmd[cmdlen - 1] != '=') {
+ delim = strchr(cmd, '=');
+ if (delim)
+ resp->cmdlen -= strlen(delim);
+ }
+
+ list_add_tail(&resp->node, &mot_dlci->list);
+
+ err = mot_dlci->write(dev, mot_dlci, resp->id,
+ cmd, cmdlen);
+ if (err < 0)
+ goto unregister;
+
+ err = wait_event_timeout(mot_dlci->read_queue, resp->handled,
+ msecs_to_jiffies(timeout_ms));
+ if (err < 0)
+ goto unregister;
+
+ if (err == 0) {
+ err = -ETIMEDOUT;
+ goto unregister;
+ }
+
+ dev_dbg(dev, "%s: %s got %s\n", __func__, cmd, resp->buf);
+
+ err = resp->reslen;
+
+unregister:
+ list_for_each_safe(pos, q, &mot_dlci->list) {
+ tmp = list_entry(pos, struct motmdm_response, node);
+ if (tmp->id == resp->id)
+ list_del(pos);
+ }
+
+ kfree(resp);
+
+ return err;
+}
+
+/*
+ * Helper for child device drivers to parse the command response from a DLCI
+ */
+static int motmdm_dlci_handle_command(struct motmdm_dlci *mot_dlci, int id,
+ const unsigned char *buf, size_t len)
+{
+ struct motmdm_response *resp = NULL;
+ struct list_head *pos, *q;
+ int resp_start;
+
+ list_for_each_safe(pos, q, &mot_dlci->list) {
+ resp = list_entry(pos, struct motmdm_response, node);
+ if (resp->id == id)
+ break;
+ }
+
+ if (!resp || !resp->buf)
+ return -ENODEV;
+
+ /* Firmware leaves out AT from the commands */
+ resp_start = resp->cmdlen - 2;
+ if (len < resp_start)
+ return -EPIPE;
+
+ /* Only some firmware messages start with ':' */
+ if (buf[resp_start] == ':')
+ resp_start++;
+
+ resp->reslen = min3(len - resp_start, resp->len, len);
+ strncpy(resp->buf, buf + resp_start, resp->reslen);
+
+ /* Leave out trailing line break if there */
+ if (resp->reslen > 1 && resp->buf[resp->reslen - 1] == '\n') {
+ resp->buf[resp->reslen - 1] = '\0';
+ resp->reslen--;
+ }
+
+ resp->handled = true;
+
+ return 0;
+}
+
+/*
+ * Write handling for Motorola custom layering on top of TS 27.010
+ */
+static int motmdm_dlci_write(struct device *dev, struct motmdm_dlci *mot_dlci,
+ int cmdid, const unsigned char *buf, size_t count)
+{
+ struct motmdm *ddata;
+ struct gsm_serdev *gsd;
+ int err, cmdlen;
+ char *cmd;
+
+ if (!dev || !mot_dlci || !buf || !count)
+ return -EINVAL;
+
+ ddata = gsm_serdev_get_drvdata(dev);
+ if (!ddata)
+ return -ENODEV;
+
+ gsd = &ddata->gsd;
+
+ err = pm_runtime_get_sync(dev);
+ if ((err != -EINPROGRESS) && err < 0) {
+ pm_runtime_put_noidle(dev);
+
+ return err;
+ }
+
+ cmdlen = MOTMDM_CMD_LEN(count);
+ cmd = kmalloc(cmdlen, GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ switch (cmdid) {
+ case -ENOENT:
+ /* No ID number, just U for continuation messages */
+ snprintf(cmd, cmdlen, "U%s\r", buf);
+ break;
+ case 0 ... 9999:
+ /* Valid ID */
+ mot_dlci->id = cmdid;
+ snprintf(cmd, cmdlen, "U%04i%s\r", mot_dlci->id, buf);
+ break;
+ default:
+ /* Assign ID */
+ mot_dlci->id = motmdm_new_packet_id();
+ snprintf(cmd, cmdlen, "U%04i%s\r", mot_dlci->id, buf);
+ break;
+ }
+
+ err = gsm_serdev_write(gsd, &mot_dlci->gsm_dlci, cmd, cmdlen);
+ if (err == cmdlen)
+ err = count;
+
+ kfree(cmd);
+ pm_runtime_put(dev);
+
+ return err;
+}
+
+
+
+int motmdm_register_dlci(struct device *dev, struct motmdm_dlci *mot_dlci)
+{
+ struct motmdm *ddata;
+ struct gsm_serdev *gsd;
+ struct gsm_serdev_dlci *gsm_dlci;
+ int err;
+
+ if (!dev || !mot_dlci || !mot_dlci->line)
+ return -EINVAL;
+
+ err = pm_runtime_get_sync(dev);
+ if ((err != -EINPROGRESS) && err < 0) {
+ pm_runtime_put_noidle(dev);
+
+ return err;
+ }
+
+ ddata = gsm_serdev_get_drvdata(dev);
+ gsd = &ddata->gsd;
+ gsm_dlci = &mot_dlci->gsm_dlci;
+ mot_dlci->write = motmdm_dlci_write;
+ mot_dlci->send_command = motmdm_dlci_send_command;
+ mot_dlci->handle_command = motmdm_dlci_handle_command;
+ INIT_LIST_HEAD(&mot_dlci->list);
+ init_waitqueue_head(&mot_dlci->read_queue);
+ gsm_dlci->line = mot_dlci->line;
+ gsm_dlci->receive_buf = motmdm_dlci_receive_buf;
+
+ err = gsm_serdev_register_dlci(gsd, gsm_dlci);
+ if (err) {
+ dev_warn(dev, "error registering dlci%i: %i\n",
+ mot_dlci->line, err);
+ kfifo_free(&mot_dlci->read_fifo);
+ memset(gsm_dlci, 0, sizeof(*gsm_dlci));
+ } else {
+ mot_dlci->privdata = ddata;
+ }
+
+ list_add_tail(&mot_dlci->node, &ddata->dlcis);
+
+ pm_runtime_put(dev);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(motmdm_register_dlci);
+
+void motmdm_unregister_dlci(struct device *dev, struct motmdm_dlci *mot_dlci)
+{
+ struct motmdm *ddata;
+ struct gsm_serdev *gsd;
+ struct gsm_serdev_dlci *gsm_dlci;
+ struct list_head *pos, *q;
+ struct motmdm_dlci *tmp;
+ int err;
+
+ if (!dev || !mot_dlci || !mot_dlci->line)
+ return;
+
+ err = pm_runtime_get_sync(dev);
+ if ((err != -EINPROGRESS) && err < 0) {
+ pm_runtime_put_noidle(dev);
+ return;
+ }
+
+ ddata = gsm_serdev_get_drvdata(dev);
+
+ list_for_each_safe(pos, q, &ddata->dlcis) {
+ tmp = list_entry(pos, struct motmdm_dlci, node);
+ if (tmp == mot_dlci)
+ list_del(pos);
+ }
+
+ gsd = &ddata->gsd;
+ gsm_dlci = &mot_dlci->gsm_dlci;
+ gsm_serdev_unregister_dlci(gsd, gsm_dlci);
+ gsm_dlci->receive_buf = NULL;
+ mot_dlci->notify = NULL;
+ mot_dlci->privdata = NULL;
+
+ pm_runtime_put(dev);
+}
+EXPORT_SYMBOL_GPL(motmdm_unregister_dlci);
+
+/*
+ * Character devices for DLCI channels with no serdev drivers
+ */
+static int motmdm_cdev_open(struct inode *inode, struct file *file)
+{
+ struct motmdm_cdev *cdata;
+ int ret = 0;
+
+ cdata = container_of(inode->i_cdev, struct motmdm_cdev, cdev);
+ get_device(cdata->dev);
+ nonseekable_open(inode, file);
+ file->private_data = cdata;
+
+ down_write(&cdata->rwsem);
+ if (cdata->disconnected) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+
+unlock:
+ up_write(&cdata->rwsem);
+
+ if (ret)
+ put_device(cdata->dev);
+
+ return ret;
+}
+
+static int motmdm_cdev_release(struct inode *inode, struct file *file)
+{
+ struct motmdm_cdev *cdata = file->private_data;
+
+ down_write(&cdata->rwsem);
+ if (cdata->disconnected)
+ goto unlock;
+
+unlock:
+ up_write(&cdata->rwsem);
+
+ put_device(cdata->dev);
+
+ return 0;
+}
+
+static ssize_t motmdm_cdev_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct motmdm_cdev *cdata = file->private_data;
+ struct motmdm_dlci *mot_dlci = cdata->dlci;
+ unsigned int copied;
+ int err;
+
+ mutex_lock(&cdata->read_mutex);
+ while (kfifo_is_empty(&mot_dlci->read_fifo)) {
+ mutex_unlock(&cdata->read_mutex);
+
+ if (cdata->disconnected)
+ return 0;
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ err = wait_event_interruptible(mot_dlci->read_queue,
+ cdata->disconnected ||
+ !kfifo_is_empty(&mot_dlci->read_fifo));
+ if (err)
+ return -ERESTARTSYS;
+
+ mutex_lock(&cdata->read_mutex);
+ }
+
+ err = kfifo_to_user(&mot_dlci->read_fifo, buf, count, &copied);
+ if (err == 0)
+ err = copied;
+
+ mutex_unlock(&cdata->read_mutex);
+
+ return err;
+}
+
+static ssize_t motmdm_cdev_write_packet(struct motmdm_cdev *cdata, int cmdid)
+{
+ struct motmdm_dlci *mot_dlci = cdata->dlci;
+ struct motmdm *ddata = mot_dlci->privdata;
+
+ return mot_dlci->write(ddata->dev, mot_dlci, cmdid,
+ cdata->write_buf, cdata->write_offset - 1);
+}
+
+static ssize_t motmdm_cdev_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct motmdm_cdev *cdata = file->private_data;
+ size_t written = 0;
+ int err, flag = -ENOMSG;
+
+ if (cdata->disconnected)
+ return -EIO;
+
+ if (!count)
+ return 0;
+
+ err = mutex_lock_interruptible(&cdata->write_mutex);
+ if (err)
+ return -ERESTARTSYS;
+
+ for (;;) {
+ size_t n;
+ bool packet = false;
+
+ n = min(count, cdata->write_buf_sz -
+ cdata->write_offset - count);
+ if (copy_from_user(cdata->write_buf + cdata->write_offset,
+ buf, n)) {
+ err = -EFAULT;
+ goto out_unlock;
+ }
+
+ cdata->write_offset += n;
+ cdata->write_buf[cdata->write_offset] = '\0';
+ if (cdata->write_offset) {
+ u8 last = cdata->write_buf[cdata->write_offset - 1];
+
+ switch (last) {
+ case 0x1a: /* Continuation packets end with ^Z */
+ flag = -ENOENT;
+ /* Fallthrough */
+ case '\n':
+ case '\r':
+ packet = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ down_read(&cdata->rwsem);
+
+ if (cdata->disconnected) {
+ err = -EIO;
+ goto err_write;
+ }
+
+ if (packet) {
+ err = motmdm_cdev_write_packet(cdata, flag);
+ if (err < 0)
+ goto err_write;
+
+ cdata->write_offset = 0;
+ }
+
+ err = n;
+
+err_write:
+ up_read(&cdata->rwsem);
+
+ if (err < 0)
+ break;
+
+ written += err;
+ buf += err;
+
+ if (written == count)
+ break;
+ }
+
+ if (written)
+ err = written;
+
+out_unlock:
+ mutex_unlock(&cdata->write_mutex);
+
+ return err;
+}
+
+static __poll_t motmdm_cdev_poll(struct file *file, poll_table *wait)
+{
+ struct motmdm_cdev *cdata = file->private_data;
+ struct motmdm_dlci *mot_dlci = cdata->dlci;
+ __poll_t mask = 0;
+
+ poll_wait(file, &mot_dlci->read_queue, wait);
+
+ if (!kfifo_is_empty(&mot_dlci->read_fifo))
+ mask |= EPOLLIN | EPOLLRDNORM;
+ if (cdata->write_offset < cdata->write_buf_sz)
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ if (cdata->disconnected)
+ mask |= EPOLLHUP;
+
+ return mask;
+}
+
+static const struct file_operations motmdm_fops = {
+ .owner = THIS_MODULE,
+ .open = motmdm_cdev_open,
+ .release = motmdm_cdev_release,
+ .read = motmdm_cdev_read,
+ .write = motmdm_cdev_write,
+ .poll = motmdm_cdev_poll,
+ .llseek = no_llseek,
+};
+
+static int motmdm_cdev_init_one(struct motmdm *ddata, int index)
+{
+ struct motmdm_cdev *cdata;
+ struct motmdm_dlci *mot_dlci;
+ int err;
+
+ mot_dlci = kzalloc(sizeof(*mot_dlci), GFP_KERNEL);
+ if (!mot_dlci)
+ return -ENOMEM;
+
+ mot_dlci->drvdata = ddata;
+ mot_dlci->line = index;
+
+ err = kfifo_alloc(&mot_dlci->read_fifo, MOTMDM_READ_FIFO_SIZE,
+ GFP_KERNEL);
+ if (err)
+ goto err_free_dlci;
+
+ cdata = kzalloc(sizeof(*cdata), GFP_KERNEL);
+ if (!cdata)
+ goto err_free_kfifo;
+
+ cdata->dlci = mot_dlci;
+ init_rwsem(&cdata->rwsem);
+ mutex_init(&cdata->read_mutex);
+ mutex_init(&cdata->write_mutex);
+ cdata->write_buf_sz = MOTMDM_WRITE_BUF_SIZE;
+ cdata->write_buf = kzalloc(cdata->write_buf_sz, GFP_KERNEL);
+ if (!cdata->write_buf)
+ goto err_free_cdata;
+
+ err = motmdm_register_dlci(ddata->dev, cdata->dlci);
+ if (err)
+ goto err_free_write_buf;
+
+ cdata->dev = device_create(ddata->class, ddata->dev,
+ MKDEV(MAJOR(ddata->dev_id), index),
+ mot_dlci, "%s%i", motmdm_driver_name,
+ index);
+ if (IS_ERR(cdata->dev)) {
+ err = PTR_ERR(cdata->dev);
+ goto err_unregister;
+ }
+
+ cdev_init(&cdata->cdev, &motmdm_fops);
+ err = cdev_add(&cdata->cdev, MKDEV(MAJOR(ddata->dev_id), index),
+ MOTMDM_DLCI_MAX);
+ if (err)
+ goto err_device_destroy;
+
+ list_add_tail(&cdata->node, &ddata->cdevs);
+
+ return 0;
+
+err_device_destroy:
+ device_destroy(ddata->class, MKDEV(MAJOR(ddata->dev_id), index));
+err_unregister:
+ motmdm_unregister_dlci(ddata->dev, cdata->dlci);
+err_free_write_buf:
+ kfree(cdata->write_buf);
+err_free_cdata:
+ kfree(cdata);
+err_free_kfifo:
+ kfifo_free(&mot_dlci->read_fifo);
+err_free_dlci:
+ kfree(mot_dlci);
+
+ return err;
+}
+
+static void motmdm_cdev_cleanup(struct device *dev);
+
+static int motmdm_cdev_init(struct device *dev)
+{
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ int bit, err;
+
+ err = alloc_chrdev_region(&ddata->dev_id, 0, MOTMDM_DLCI_MAX,
+ motmdm_driver_name);
+ if (err)
+ return err;
+
+ ddata->class = class_create(THIS_MODULE, motmdm_driver_name);
+ if (IS_ERR(ddata->class)) {
+ err = PTR_ERR(ddata->class);
+ motmdm_cdev_cleanup(dev);
+
+ return err;
+ }
+
+ for_each_set_bit(bit, &ddata->cfg->cdevmask, BITS_PER_LONG) {
+ err = motmdm_cdev_init_one(ddata, bit);
+ if (err) {
+ motmdm_cdev_cleanup(dev);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void motmdm_cdev_free_one(struct motmdm_cdev *cdata)
+{
+ struct motmdm_dlci *mot_dlci = cdata->dlci;
+ struct motmdm *ddata = mot_dlci->privdata;
+
+ down_write(&cdata->rwsem);
+ cdata->disconnected = true;
+ if (cdata->count)
+ wake_up_interruptible(&mot_dlci->read_queue);
+ up_write(&cdata->rwsem);
+
+ cdev_del(&cdata->cdev);
+ device_destroy(ddata->class,
+ MKDEV(MAJOR(ddata->dev_id), mot_dlci->line));
+ kfree(cdata->write_buf);
+
+ motmdm_unregister_dlci(ddata->dev, cdata->dlci);
+ kfifo_free(&mot_dlci->read_fifo);
+ kfree(cdata->dlci);
+ kfree(cdata);
+}
+
+static void motmdm_cdev_cleanup(struct device *dev)
+{
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ struct motmdm_cdev *cdata, *tmp;
+
+ list_for_each_entry_safe(cdata, tmp, &ddata->cdevs, node) {
+ motmdm_cdev_free_one(cdata);
+ list_del(&cdata->node);
+ }
+
+ class_destroy(ddata->class);
+
+ unregister_chrdev_region(ddata->dev_id, MOTMDM_DLCI_MAX);
+}
+
+static int motmdm_check_revision(struct device *dev)
+{
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ struct motmdm_dlci *mot_dlci;
+ const unsigned char *cmd = "AT+VERSION=";
+ unsigned char *buf;
+ int retries = 3, err;
+
+ mot_dlci = kzalloc(sizeof(*mot_dlci), GFP_KERNEL);
+ if (!mot_dlci)
+ return -ENOMEM;
+
+ mot_dlci->drvdata = ddata;
+ mot_dlci->line = MOTMDM_DLCI6;
+
+ buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf) {
+ err = -ENOMEM;
+ goto free_dlci;
+ }
+
+ err = motmdm_register_dlci(dev, mot_dlci);
+ if (err)
+ goto free_buf;
+
+ while (retries--) {
+ err = motmdm_dlci_send_command(dev, mot_dlci, 1000,
+ cmd, strlen(cmd),
+ buf, PAGE_SIZE);
+ if (err >= 0) {
+ msleep(100);
+ break;
+ }
+
+ msleep(500);
+ }
+
+ if (err < 0) {
+ dev_err(dev, "Could not connect: %i\n", err);
+ } else if (!strncmp(buf, "ERROR", 5)) {
+ dev_err(dev, "Firmware error: %s\n", buf);
+ err = -ENODEV;
+ } else {
+ dev_info(dev, "Firmware: %s\n", buf);
+ err = 0;
+ }
+
+ motmdm_unregister_dlci(dev, mot_dlci);
+
+free_buf:
+ kfree(buf);
+free_dlci:
+ kfree(mot_dlci);
+
+ return err;
+}
+
+static int motmdm_set_config(struct device *dev, int retransmissions)
+{
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ struct gsm_serdev *gsd = &ddata->gsd;
+ struct gsm_config c;
+ int err;
+
+ err = gsm_serdev_get_config(gsd, &c);
+ if (err)
+ return err;
+
+ c.i = 1; /* 1 = UIH, 2 = UI */
+ c.initiator = 1;
+ c.encapsulation = 0; /* basic mode */
+ c.adaption = 1;
+ c.mru = 1024; /* from android TS 27010 driver */
+ c.mtu = 1024; /* from android TS 27010 driver */
+ c.t1 = 10; /* ack timer, default 10ms */
+ c.t2 = 34; /* response timer, default 34 */
+ c.n2 = retransmissions; /* retransmissions, default 3 */
+
+ err = gsm_serdev_set_config(gsd, &c);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int motmdm_output(struct gsm_serdev *gsd, u8 *data, int len)
+{
+ struct serdev_device *serdev = gsd->serdev;
+ struct device *dev = &serdev->dev;
+ int err;
+
+ err = pm_runtime_get(dev);
+ if ((err != -EINPROGRESS) && err < 0) {
+ pm_runtime_put_noidle(dev);
+
+ return err;
+ }
+
+ serdev_device_write_buf(serdev, data, len);
+
+ pm_runtime_put(dev);
+
+ return len;
+}
+
+static const struct motmdm_cfg mapphone_mdm6600_data = {
+ .cdevmask = MOTMDM_DLCI_MASK & ~(BIT(MOTMDM_DLCI2) | BIT(MOTMDM_DLCI4)),
+ .aggressive_pm = true,
+ .modem_dlci = MOTMDM_DLCI1,
+ .codec_dlci = MOTMDM_DLCI2,
+};
+
+static const struct of_device_id motmdm_id_table[] = {
+ {
+ .compatible = "motorola,mapphone-mdm6600-serdev",
+ .data = &mapphone_mdm6600_data,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, motmdm_id_table);
+
+static const struct mfd_cell motmdm_mfd_devices[] = {
+ {
+ .name = "mot-mdm6600-codec",
+ },
+ {
+ .name = "gnss-mot-mdm6600",
+ .of_compatible = "motorola,mapphone-mdm6600-gnss",
+ },
+};
+
+static int motmdm_probe(struct serdev_device *serdev)
+{
+ struct device *dev = &serdev->dev;
+ const struct of_device_id *match;
+ struct gsm_serdev *gsd;
+ struct motmdm *ddata;
+ int err;
+
+ match = of_match_device(of_match_ptr(motmdm_id_table), dev);
+ if (!match)
+ return -ENODEV;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->dev = dev;
+ ddata->cfg = match->data;
+
+ INIT_LIST_HEAD(&ddata->dlcis);
+ INIT_LIST_HEAD(&ddata->cdevs);
+ gsd = &ddata->gsd;
+ gsd->serdev = serdev;
+ gsd->output = motmdm_output;
+ serdev_device_set_drvdata(serdev, gsd);
+ gsm_serdev_set_drvdata(dev, ddata);
+
+ err = motmdm_init_phy(dev);
+ if (err)
+ return err;
+
+ pm_runtime_set_autosuspend_delay(dev, 50);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_enable(dev);
+ err = pm_runtime_get_sync(dev);
+ if (err < 0) {
+ pm_runtime_put_noidle(dev);
+
+ return err;
+ }
+
+ err = gsm_serdev_register_device(gsd);
+ if (err)
+ goto disable;
+
+ err = serdev_device_open(gsd->serdev);
+ if (err)
+ goto disable;
+
+ serdev_device_set_baudrate(gsd->serdev, 115200);
+ serdev_device_set_rts(gsd->serdev, true);
+ serdev_device_set_flow_control(gsd->serdev, true);
+
+ /*
+ * Getting dlci0 connected quirk: We set initial retransmissions
+ * value high to get n_gsm to send SABM packets. Then after about
+ * three seconds we'll get a "reassembly overrun" error from firmware
+ * on dlci0 followed by DM(P) packets and then we're connected in ADM
+ * mode. Note we will set the retransmissions back to default value
+ * later on.
+ */
+ err = motmdm_set_config(dev, MOTMDM_C_N2 * 10);
+ if (err)
+ goto close;
+
+ msleep(3000);
+
+ err = motmdm_check_revision(dev);
+ if (err)
+ goto close;
+
+ msleep(500);
+
+ err = motmdm_cdev_init(dev);
+ if (err)
+ goto close;
+
+ pm_runtime_put_sync(dev);
+
+ err = devm_mfd_add_devices(dev, 0, motmdm_mfd_devices,
+ ARRAY_SIZE(motmdm_mfd_devices),
+ NULL, 0, NULL);
+ if (err)
+ goto cdev_cleanup;
+
+ /* Set initial retransmissions back to default value */
+ err = motmdm_set_config(dev, MOTMDM_C_N2);
+ if (err)
+ goto cdev_cleanup;
+
+ if (ddata->cfg->aggressive_pm) {
+ pm_runtime_set_autosuspend_delay(&serdev->ctrl->dev, 50);
+ pm_runtime_put(&serdev->ctrl->dev);
+ }
+
+ return 0;
+
+cdev_cleanup:
+ motmdm_cdev_cleanup(dev);
+
+close:
+ serdev_device_close(serdev);
+
+disable:
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+ gsm_serdev_unregister_device(gsd);
+
+ return err;
+}
+
+static void motmdm_remove(struct serdev_device *serdev)
+{
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+ struct device *dev = &serdev->dev;
+ struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+ int err;
+
+ /* Balance the put done in probe for UART */
+ if (ddata->cfg->aggressive_pm)
+ pm_runtime_get(&serdev->ctrl->dev);
+
+ err = pm_runtime_get_sync(dev);
+ if (err < 0)
+ dev_warn(dev, "%s: PM runtime: %i\n", __func__, err);
+
+ motmdm_cdev_cleanup(dev);
+ serdev_device_close(serdev);
+ gsm_serdev_unregister_device(gsd);
+
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+}
+
+static struct serdev_device_driver motmdm_driver = {
+ .driver = {
+ .name = "motmdm",
+ .of_match_table = of_match_ptr(motmdm_id_table),
+ .pm = &motmdm_pm_ops,
+ },
+ .probe = motmdm_probe,
+ .remove = motmdm_remove,
+};
+
+module_serdev_device_driver(motmdm_driver);
+
+MODULE_ALIAS("platform:motorola-mdm");
+MODULE_DESCRIPTION("Motorola Modem TS 27.010 serdev driver");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 6f7da9a9d76f0..348b1db721359 100644
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -39,6 +39,7 @@
#include <linux/file.h>
#include <linux/uaccess.h>
#include <linux/module.h>
+#include <linux/serdev.h>
#include <linux/timer.h>
#include <linux/tty_flip.h>
#include <linux/tty_driver.h>
@@ -50,6 +51,7 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/gsmmux.h>
+#include <linux/serdev-gsm.h>
static int debug;
module_param(debug, int, 0600);
@@ -143,8 +145,9 @@ struct gsm_dlci {
struct sk_buff *skb; /* Frame being sent */
struct sk_buff_head skb_list; /* Queued frames */
/* Data handling callback */
- void (*data)(struct gsm_dlci *dlci, u8 *data, int len);
- void (*prev_data)(struct gsm_dlci *dlci, u8 *data, int len);
+ void (*data)(struct gsm_dlci *dlci, const u8 *data, int len);
+ void (*prev_data)(struct gsm_dlci *dlci, const u8 *data, int len);
+ struct gsm_serdev_dlci *ops; /* serdev dlci ops, if used */
struct net_device *net; /* network interface, if created */
};
@@ -179,6 +182,7 @@ struct gsm_control {
*/
struct gsm_mux {
+ struct gsm_serdev *gsd; /* Serial device bus data */
struct tty_struct *tty; /* The tty our ldisc is bound to */
spinlock_t lock;
struct mutex mutex;
@@ -988,7 +992,7 @@ static void gsm_dlci_data_kick(struct gsm_dlci *dlci)
* Encode up and queue a UI/UIH frame containing our response.
*/
-static void gsm_control_reply(struct gsm_mux *gsm, int cmd, u8 *data,
+static void gsm_control_reply(struct gsm_mux *gsm, int cmd, const u8 *data,
int dlen)
{
struct gsm_msg *msg;
@@ -1073,14 +1077,14 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,
* and if need be stuff a break message down the tty.
*/
-static void gsm_control_modem(struct gsm_mux *gsm, u8 *data, int clen)
+static void gsm_control_modem(struct gsm_mux *gsm, const u8 *data, int clen)
{
unsigned int addr = 0;
unsigned int modem = 0;
unsigned int brk = 0;
struct gsm_dlci *dlci;
int len = clen;
- u8 *dp = data;
+ const u8 *dp = data;
struct tty_struct *tty;
while (gsm_read_ea(&addr, *dp++) == 0) {
@@ -1134,13 +1138,13 @@ static void gsm_control_modem(struct gsm_mux *gsm, u8 *data, int clen)
* this into the uplink tty if present
*/
-static void gsm_control_rls(struct gsm_mux *gsm, u8 *data, int clen)
+static void gsm_control_rls(struct gsm_mux *gsm, const u8 *data, int clen)
{
struct tty_port *port;
unsigned int addr = 0;
u8 bits;
int len = clen;
- u8 *dp = data;
+ const u8 *dp = data;
while (gsm_read_ea(&addr, *dp++) == 0) {
len--;
@@ -1189,7 +1193,7 @@ static void gsm_dlci_begin_close(struct gsm_dlci *dlci);
*/
static void gsm_control_message(struct gsm_mux *gsm, unsigned int command,
- u8 *data, int clen)
+ const u8 *data, int clen)
{
u8 buf[1];
unsigned long flags;
@@ -1261,7 +1265,7 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command,
*/
static void gsm_control_response(struct gsm_mux *gsm, unsigned int command,
- u8 *data, int clen)
+ const u8 *data, int clen)
{
struct gsm_control *ctrl;
unsigned long flags;
@@ -1553,7 +1557,7 @@ static void gsm_dlci_begin_close(struct gsm_dlci *dlci)
* open we shovel the bits down it, if not we drop them.
*/
-static void gsm_dlci_data(struct gsm_dlci *dlci, u8 *data, int clen)
+static void gsm_dlci_data(struct gsm_dlci *dlci, const u8 *data, int clen)
{
/* krefs .. */
struct tty_port *port = &dlci->port;
@@ -1603,7 +1607,7 @@ static void gsm_dlci_data(struct gsm_dlci *dlci, u8 *data, int clen)
* and we divide up the work accordingly.
*/
-static void gsm_dlci_command(struct gsm_dlci *dlci, u8 *data, int len)
+static void gsm_dlci_command(struct gsm_dlci *dlci, const u8 *data, int len)
{
/* See what command is involved */
unsigned int command = 0;
@@ -2214,6 +2218,424 @@ static struct gsm_mux *gsm_alloc_mux(void)
return gsm;
}
+static void gsm_copy_config_values(struct gsm_mux *gsm,
+ struct gsm_config *c)
+{
+ memset(c, 0, sizeof(*c));
+ c->adaption = gsm->adaption;
+ c->encapsulation = gsm->encoding;
+ c->initiator = gsm->initiator;
+ c->t1 = gsm->t1;
+ c->t2 = gsm->t2;
+ c->t3 = 0; /* Not supported */
+ c->n2 = gsm->n2;
+ if (gsm->ftype == UIH)
+ c->i = 1;
+ else
+ c->i = 2;
+ pr_debug("Ftype %d i %d\n", gsm->ftype, c->i);
+ c->mru = gsm->mru;
+ c->mtu = gsm->mtu;
+ c->k = 0;
+}
+
+static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
+{
+ int need_close = 0;
+ int need_restart = 0;
+
+ /* Stuff we don't support yet - UI or I frame transport, windowing */
+ if ((c->adaption != 1 && c->adaption != 2) || c->k)
+ return -EOPNOTSUPP;
+ /* Check the MRU/MTU range looks sane */
+ if (c->mru > MAX_MRU || c->mtu > MAX_MTU || c->mru < 8 || c->mtu < 8)
+ return -EINVAL;
+ if (c->n2 < 3)
+ return -EINVAL;
+ if (c->encapsulation > 1) /* Basic, advanced, no I */
+ return -EINVAL;
+ if (c->initiator > 1)
+ return -EINVAL;
+ if (c->i == 0 || c->i > 2) /* UIH and UI only */
+ return -EINVAL;
+ /*
+ * See what is needed for reconfiguration
+ */
+
+ /* Timing fields */
+ if (c->t1 != 0 && c->t1 != gsm->t1)
+ need_restart = 1;
+ if (c->t2 != 0 && c->t2 != gsm->t2)
+ need_restart = 1;
+ if (c->encapsulation != gsm->encoding)
+ need_restart = 1;
+ if (c->adaption != gsm->adaption)
+ need_restart = 1;
+ /* Requires care */
+ if (c->initiator != gsm->initiator)
+ need_close = 1;
+ if (c->mru != gsm->mru)
+ need_restart = 1;
+ if (c->mtu != gsm->mtu)
+ need_restart = 1;
+
+ /*
+ * Close down what is needed, restart and initiate the new
+ * configuration
+ */
+
+ if (need_close || need_restart) {
+ int ret;
+
+ ret = gsm_disconnect(gsm);
+
+ if (ret)
+ return ret;
+ }
+ if (need_restart)
+ gsm_cleanup_mux(gsm);
+
+ gsm->initiator = c->initiator;
+ gsm->mru = c->mru;
+ gsm->mtu = c->mtu;
+ gsm->encoding = c->encapsulation;
+ gsm->adaption = c->adaption;
+ gsm->n2 = c->n2;
+
+ if (c->i == 1)
+ gsm->ftype = UIH;
+ else if (c->i == 2)
+ gsm->ftype = UI;
+
+ if (c->t1)
+ gsm->t1 = c->t1;
+ if (c->t2)
+ gsm->t2 = c->t2;
+
+ /*
+ * FIXME: We need to separate activation/deactivation from adding
+ * and removing from the mux array
+ */
+ if (need_restart)
+ gsm_activate_mux(gsm);
+ if (gsm->initiator && need_close)
+ gsm_dlci_begin_open(gsm->dlci[0]);
+ return 0;
+}
+
+#ifdef CONFIG_SERIAL_DEV_BUS
+static int gsd_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ struct gsm_mux *gsm;
+
+ if (!gsd || !gsd->gsm)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ if (!c)
+ return -EINVAL;
+
+ gsm_copy_config_values(gsm, c);
+
+ return 0;
+}
+
+static int gsd_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ struct gsm_mux *gsm;
+
+ if (!gsd || !gsd->serdev || !gsd->gsm)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ if (!c)
+ return -EINVAL;
+
+ return gsm_config(gsm, c);
+}
+
+static struct gsm_dlci *gsd_dlci_get(struct gsm_serdev *gsd, int line,
+ bool allocate)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+
+ if (!gsd || !gsd->gsm)
+ return ERR_PTR(-ENODEV);
+
+ gsm = gsd->gsm;
+
+ if (line < 1 || line >= 63)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&gsm->mutex);
+
+ if (gsm->dlci[line]) {
+ dlci = gsm->dlci[line];
+ goto unlock;
+ } else if (!allocate) {
+ dlci = ERR_PTR(-ENODEV);
+ goto unlock;
+ }
+
+ dlci = gsm_dlci_alloc(gsm, line);
+ if (!dlci) {
+ gsm = ERR_PTR(-ENOMEM);
+ goto unlock;
+ }
+
+ gsm->dlci[line] = dlci;
+
+unlock:
+ mutex_unlock(&gsm->mutex);
+
+ return dlci;
+}
+
+static void gsd_dlci_data(struct gsm_dlci *dlci, const u8 *buf, int len)
+{
+ struct gsm_mux *gsm = dlci->gsm;
+ struct gsm_serdev *gsd = gsm->gsd;
+
+ if (!gsd || !dlci->ops)
+ return;
+
+ switch (dlci->adaption) {
+ case 0:
+ case 1:
+ if (dlci->ops->receive_buf)
+ dlci->ops->receive_buf(dlci->ops, buf, len);
+ break;
+ default:
+ pr_warn("dlci%i adaption %i not yet implemented\n",
+ dlci->addr, dlci->adaption);
+ break;
+ }
+}
+
+static int gsd_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *sd,
+ const u8 *buf, int len)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+ struct gsm_msg *msg;
+ int h, size, total_size = 0;
+ u8 *dp;
+
+ if (!gsd || !gsd->gsm)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ dlci = gsd_dlci_get(gsd, sd->line, false);
+ if (IS_ERR(dlci))
+ return PTR_ERR(dlci);
+
+ h = dlci->adaption - 1;
+
+ if (len > gsm->mtu)
+ len = gsm->mtu;
+
+ size = len + h;
+
+ msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+ if (!msg)
+ return -ENOMEM;
+
+ dp = msg->data;
+ switch (dlci->adaption) {
+ case 1:
+ break;
+ case 2:
+ *dp++ = gsm_encode_modem(dlci);
+ break;
+ }
+ memcpy(dp, buf, len);
+ __gsm_data_queue(dlci, msg);
+ total_size += size;
+
+ return total_size;
+}
+
+static void gsd_data_kick(struct gsm_serdev *gsd)
+{
+ struct gsm_mux *gsm;
+ unsigned long flags;
+
+ if (!gsd || !gsd->gsm)
+ return;
+
+ gsm = gsd->gsm;
+
+ spin_lock_irqsave(&gsm->tx_lock, flags);
+ gsm_data_kick(gsm);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
+}
+
+static int gsd_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ struct gsm_dlci *dlci;
+ struct gsm_mux *gsm;
+
+ if (!gsd || !gsd->gsm || !gsd->serdev)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ if (!ops || !ops->line || !ops->receive_buf)
+ return -EINVAL;
+
+ dlci = gsd_dlci_get(gsd, ops->line, true);
+ if (IS_ERR(dlci))
+ return PTR_ERR(dlci);
+
+ if (dlci->state == DLCI_OPENING || dlci->state == DLCI_OPEN ||
+ dlci->state == DLCI_CLOSING)
+ return -EBUSY;
+
+ mutex_lock(&dlci->mutex);
+ dlci->ops = ops;
+ dlci->modem_rx = 0;
+ dlci->prev_data = dlci->data;
+ dlci->data = gsd_dlci_data;
+ mutex_unlock(&dlci->mutex);
+
+ gsm_dlci_begin_open(dlci);
+
+ return 0;
+}
+
+static void gsd_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+
+ if (!gsd || !gsd->gsm || !gsd->serdev || !gsd->unregister_dlci)
+ return;
+
+ gsm = gsd->gsm;
+
+ if (!ops || !ops->line)
+ return;
+
+ dlci = gsd_dlci_get(gsd, ops->line, false);
+ if (IS_ERR(dlci))
+ return;
+
+ mutex_lock(&dlci->mutex);
+ gsm_destroy_network(dlci);
+ dlci->data = dlci->prev_data;
+ dlci->ops = NULL;
+ mutex_unlock(&dlci->mutex);
+
+ gsm_dlci_begin_close(dlci);
+}
+
+static int gsm_serdev_output(struct gsm_mux *gsm, u8 *data, int len)
+{
+ struct gsm_serdev *gsd = gsm->gsd;
+ struct serdev_device *serdev = gsm->gsd->serdev;
+ bool asleep;
+
+ asleep = atomic_read(&gsd->asleep);
+ if (asleep)
+ return -ENOSPC;
+
+ if (gsm->gsd->output)
+ return gsm->gsd->output(gsm->gsd, data, len);
+ else
+ return serdev_device_write_buf(serdev, data, len);
+}
+
+static int gsd_receive_buf(struct serdev_device *serdev, const u8 *data,
+ size_t count)
+{
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+ struct gsm_mux *gsm;
+ const unsigned char *dp;
+ int i;
+
+ if (WARN_ON(!gsd))
+ return 0;
+
+ gsm = gsd->gsm;
+
+ if (debug & 4)
+ print_hex_dump_bytes("gsd_receive_buf: ",
+ DUMP_PREFIX_OFFSET,
+ data, count);
+
+ for (i = count, dp = data; i; i--, dp++)
+ gsm->receive(gsm, *dp);
+
+ return count;
+}
+
+static void gsd_write_wakeup(struct serdev_device *serdev)
+{
+ serdev_device_write_wakeup(serdev);
+}
+
+static struct serdev_device_ops gsd_client_ops = {
+ .receive_buf = gsd_receive_buf,
+ .write_wakeup = gsd_write_wakeup,
+};
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+ struct gsm_mux *gsm;
+ int error;
+
+ if (WARN(!gsd || !gsd->serdev || !gsd->output,
+ "serdev and output must be initialized\n"))
+ return -EINVAL;
+
+ serdev_device_set_client_ops(gsd->serdev, &gsd_client_ops);
+
+ gsm = gsm_alloc_mux();
+ if (!gsm)
+ return -ENOMEM;
+
+ gsm->encoding = 1;
+ gsm->tty = NULL;
+ gsm->gsd = gsd;
+ atomic_set(&gsd->asleep, 0);
+ gsd->gsm = gsm;
+ gsd->get_config = gsd_get_config;
+ gsd->set_config = gsd_set_config;
+ gsd->register_dlci = gsd_register_dlci;
+ gsd->unregister_dlci = gsd_unregister_dlci;
+ gsm->output = gsm_serdev_output;
+ gsd->write = gsd_write;
+ gsd->kick = gsd_data_kick;
+ mux_get(gsd->gsm);
+
+ error = gsm_activate_mux(gsd->gsm);
+ if (error) {
+ gsm_cleanup_mux(gsd->gsm);
+ mux_put(gsd->gsm);
+
+ return error;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_register_device);
+
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd)
+{
+ gsm_cleanup_mux(gsd->gsm);
+ mux_put(gsd->gsm);
+ gsd->gsm = NULL;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_unregister_device);
+
+#endif /* CONFIG_SERIAL_DEV_BUS */
+
/**
* gsmld_output - write to link
* @gsm: our mux
@@ -2495,89 +2917,6 @@ static __poll_t gsmld_poll(struct tty_struct *tty, struct file *file,
return mask;
}
-static int gsmld_config(struct tty_struct *tty, struct gsm_mux *gsm,
- struct gsm_config *c)
-{
- int need_close = 0;
- int need_restart = 0;
-
- /* Stuff we don't support yet - UI or I frame transport, windowing */
- if ((c->adaption != 1 && c->adaption != 2) || c->k)
- return -EOPNOTSUPP;
- /* Check the MRU/MTU range looks sane */
- if (c->mru > MAX_MRU || c->mtu > MAX_MTU || c->mru < 8 || c->mtu < 8)
- return -EINVAL;
- if (c->n2 < 3)
- return -EINVAL;
- if (c->encapsulation > 1) /* Basic, advanced, no I */
- return -EINVAL;
- if (c->initiator > 1)
- return -EINVAL;
- if (c->i == 0 || c->i > 2) /* UIH and UI only */
- return -EINVAL;
- /*
- * See what is needed for reconfiguration
- */
-
- /* Timing fields */
- if (c->t1 != 0 && c->t1 != gsm->t1)
- need_restart = 1;
- if (c->t2 != 0 && c->t2 != gsm->t2)
- need_restart = 1;
- if (c->encapsulation != gsm->encoding)
- need_restart = 1;
- if (c->adaption != gsm->adaption)
- need_restart = 1;
- /* Requires care */
- if (c->initiator != gsm->initiator)
- need_close = 1;
- if (c->mru != gsm->mru)
- need_restart = 1;
- if (c->mtu != gsm->mtu)
- need_restart = 1;
-
- /*
- * Close down what is needed, restart and initiate the new
- * configuration
- */
-
- if (need_close || need_restart) {
- int ret;
-
- ret = gsm_disconnect(gsm);
-
- if (ret)
- return ret;
- }
- if (need_restart)
- gsm_cleanup_mux(gsm);
-
- gsm->initiator = c->initiator;
- gsm->mru = c->mru;
- gsm->mtu = c->mtu;
- gsm->encoding = c->encapsulation;
- gsm->adaption = c->adaption;
- gsm->n2 = c->n2;
-
- if (c->i == 1)
- gsm->ftype = UIH;
- else if (c->i == 2)
- gsm->ftype = UI;
-
- if (c->t1)
- gsm->t1 = c->t1;
- if (c->t2)
- gsm->t2 = c->t2;
-
- /* FIXME: We need to separate activation/deactivation from adding
- and removing from the mux array */
- if (need_restart)
- gsm_activate_mux(gsm);
- if (gsm->initiator && need_close)
- gsm_dlci_begin_open(gsm->dlci[0]);
- return 0;
-}
-
static int gsmld_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
@@ -2586,29 +2925,14 @@ static int gsmld_ioctl(struct tty_struct *tty, struct file *file,
switch (cmd) {
case GSMIOC_GETCONF:
- memset(&c, 0, sizeof(c));
- c.adaption = gsm->adaption;
- c.encapsulation = gsm->encoding;
- c.initiator = gsm->initiator;
- c.t1 = gsm->t1;
- c.t2 = gsm->t2;
- c.t3 = 0; /* Not supported */
- c.n2 = gsm->n2;
- if (gsm->ftype == UIH)
- c.i = 1;
- else
- c.i = 2;
- pr_debug("Ftype %d i %d\n", gsm->ftype, c.i);
- c.mru = gsm->mru;
- c.mtu = gsm->mtu;
- c.k = 0;
+ gsm_copy_config_values(gsm, &c);
if (copy_to_user((void *)arg, &c, sizeof(c)))
return -EFAULT;
return 0;
case GSMIOC_SETCONF:
if (copy_from_user(&c, (void *)arg, sizeof(c)))
return -EFAULT;
- return gsmld_config(tty, gsm, &c);
+ return gsm_config(gsm, &c);
default:
return n_tty_ioctl_helper(tty, file, cmd, arg);
}
@@ -2695,7 +3019,7 @@ static void gsm_mux_net_tx_timeout(struct net_device *net)
}
static void gsm_mux_rx_netchar(struct gsm_dlci *dlci,
- unsigned char *in_buf, int size)
+ const unsigned char *in_buf, int size)
{
struct net_device *net = dlci->net;
struct sk_buff *skb;
diff --git a/include/linux/mfd/motorola-mdm.h b/include/linux/mfd/motorola-mdm.h
new file mode 100644
index 0000000000000..aae61050cd34a
--- /dev/null
+++ b/include/linux/mfd/motorola-mdm.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+struct gsm_serdev_dlci;
+struct kfifo;
+struct motmdm_response;
+
+enum motmdm_dlci_nr {
+ MOTMDM_DLCI1 = 1,
+ MOTMDM_DLCI2,
+ MOTMDM_DLCI3,
+ MOTMDM_DLCI4,
+ MOTMDM_DLCI5,
+ MOTMDM_DLCI6,
+ MOTMDM_DLCI7,
+ MOTMDM_DLCI8,
+ MOTMDM_DLCI9,
+ MOTMDM_DLCI10,
+ MOTMDM_DLCI11,
+ MOTMDM_DLCI12,
+ MOTMDM_DLCI13,
+ MOTMDM_DLCI14,
+ MOTMDM_DLCI15,
+};
+
+enum motmdm_state {
+ MOTMDM_STATE_IDLE = 0,
+ MOTMDM_STATE_DIAL = 1,
+ MOTMDM_STATE_ANSWERING = 2,
+ MOTMDM_STATE_CONNECTING = 3,
+ MOTMDM_STATE_INCOMING = 4,
+ MOTMDM_STATE_CONNECTED = 5,
+ MOTMDM_STATE_HANGING_UP = 6,
+ MOTMDM_STATE_DISCONNECTED = 7,
+};
+
+struct motmdm_dlci {
+ struct gsm_serdev_dlci gsm_dlci;
+ struct list_head node;
+ wait_queue_head_t read_queue;
+ struct kfifo read_fifo;
+ int line;
+ u16 id;
+ int (*send_command)(struct device *dev, struct motmdm_dlci *mot_dlci,
+ unsigned long timeout_ms, const unsigned char *cmd,
+ size_t cmdlen,
+ unsigned char *rsp, size_t rsplen);
+ int (*handle_command)(struct motmdm_dlci *mot_dlci, int id,
+ const unsigned char *buf, size_t len);
+ int (*receive_data)(struct motmdm_dlci *mot_dlci,
+ const unsigned char *buf,
+ size_t len);
+ int (*write)(struct device *dev, struct motmdm_dlci *mot_dlci,
+ int cmdid, const unsigned char *buf, size_t count);
+ int (*notify)(struct motmdm_dlci *mot_dlci, enum motmdm_state);
+ struct list_head list;
+ void *privdata; /* Do not use, internal data */
+ void *drvdata; /* Available for consumer drivers */
+};
+
+int motmdm_register_dlci(struct device *dev, struct motmdm_dlci *mot_dlci);
+void motmdm_unregister_dlci(struct device *dev, struct motmdm_dlci *mot_dlci);
+
+static inline
+int motmdm_send_command(struct device *dev, struct motmdm_dlci *mot_dlci,
+ unsigned long timeout_ms, const unsigned char *cmd,
+ size_t cmdlen, unsigned char *rsp, size_t rsplen)
+{
+ if (mot_dlci && mot_dlci->send_command)
+ return mot_dlci->send_command(dev, mot_dlci,
+ timeout_ms, cmd, cmdlen,
+ rsp, rsplen);
+ else
+ return -EINVAL;
+}
+
+static inline
+int motmdm_write(struct device *dev, struct motmdm_dlci *mot_dlci,
+ const unsigned char *buf, size_t count)
+{
+ if (mot_dlci && mot_dlci->write)
+ return mot_dlci->write(dev, mot_dlci, -1, buf, count);
+ else
+ return -EINVAL;
+}
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
new file mode 100644
index 0000000000000..93499cf3ea195
--- /dev/null
+++ b/include/linux/serdev-gsm.h
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+struct serdev_device;
+struct gsm_mux;
+struct gsm_config;
+struct gsm_dlci;
+struct gsm_serdev_dlci;
+
+/**
+ * struct gsm_serdev - serdev-gsm instance
+ * @serdev: serdev instance
+ * @gsm: ts 27.010 n_gsm instance
+ * @asleep: device is in idle state
+ * @drvdata: serdev-gsm consumer driver data
+ * @get_config: get ts 27.010 config
+ * @set_config: set ts 27.010 config
+ * @register_dlci: register ts 27.010 channel
+ * @unregister_dlci: unregister ts 27.010 channel
+ * @output: read data from ts 27.010 channel
+ * @write: write data to a ts 27.010 channel
+ * @kick: indicate more data is ready
+ *
+ * Currently only serdev and output must be initialized, the rest are
+ * are initialized by gsm_serdev_register_dlci().
+ */
+struct gsm_serdev {
+ struct serdev_device *serdev;
+ struct gsm_mux *gsm;
+ atomic_t asleep;
+ void *drvdata;
+ int (*get_config)(struct gsm_serdev *gsd, struct gsm_config *c);
+ int (*set_config)(struct gsm_serdev *gsd, struct gsm_config *c);
+ int (*register_dlci)(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops);
+ void (*unregister_dlci)(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops);
+ int (*output)(struct gsm_serdev *gsd, u8 *data, int len);
+ int (*write)(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len);
+ void (*kick)(struct gsm_serdev *gsd);
+};
+
+/**
+ * struct gsm_serdev_dlci - serdev-gsm ts 27.010 channel data
+ * @line: ts 27.010 channel, control channel 0 is not available
+ * @receive_buf: function to handle data received for the channel
+ */
+struct gsm_serdev_dlci {
+ int line;
+ int (*receive_buf)(struct gsm_serdev_dlci *ops,
+ const unsigned char *buf,
+ size_t len);
+};
+
+#ifdef CONFIG_SERIAL_DEV_BUS
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd);
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+ struct serdev_device *serdev = to_serdev_device(dev);
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+ if (gsd)
+ return gsd->drvdata;
+
+ return NULL;
+}
+
+static inline void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+ struct serdev_device *serdev = to_serdev_device(dev);
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+ if (gsd)
+ gsd->drvdata = drvdata;
+}
+
+/**
+ * gsm_serdev_get_config - read ts 27.010 config
+ * @gsd: serdev-gsm instance
+ * @c: ts 27.010 config data
+ *
+ * See gsm_copy_config_values() for more information.
+ */
+static inline
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ return gsd->get_config(gsd, c);
+}
+
+/**
+ * gsm_serdev_set_config - set ts 27.010 config
+ * @gsd: serdev-gsm instance
+ * @c: ts 27.010 config data
+ *
+ * See gsm_config() for more information.
+ */
+static inline
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ if (gsd && gsd->set_config)
+ return gsd->set_config(gsd, c);
+
+ return -ENODEV;
+}
+
+/**
+ * gsm_serdev_register_dlci - register a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ */
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ if (gsd && gsd->register_dlci)
+ return gsd->register_dlci(gsd, ops);
+
+ return -ENODEV;
+}
+
+/**
+ * gsm_serdev_unregister_dlci - unregister a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ */
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ if (gsd && gsd->unregister_dlci)
+ gsd->unregister_dlci(gsd, ops);
+}
+
+/**
+ * gsm_serdev_write - write data to a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ * @buf: write buffer
+ * @len: buffer length
+ */
+static inline
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len)
+{
+ if (gsd && gsd->write)
+ return gsd->write(gsd, ops, buf, len);
+
+ return -ENODEV;
+}
+
+/**
+ * gsm_serdev_data_kick - indicate more data can be trasmitted
+ * @gsd: serdev-gsm instance
+ *
+ * See gsm_data_kick() for more information.
+ */
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+ if (gsd && gsd->kick)
+ gsd->kick(gsd);
+}
+
+#else /* CONFIG_SERIAL_DEV_BUS */
+
+static inline
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd)
+{
+}
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+ return NULL;
+}
+
+static inline
+void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+}
+
+static inline
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ return -ENODEV;
+}
+
+static inline
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ return -ENODEV;
+}
+
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+}
+
+static inline
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+}
+
+#endif /* CONFIG_SERIAL_DEV_BUS */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 62bdb7e333b84..b2e9158e780cc 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -692,6 +692,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 MFD_MOTMDM
+ help
+ Enable support for Motorola TS 27.010 line discipline 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 66f55d1856207..9c32d32e47757 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -103,6 +103,7 @@ 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-msm8916-analog-objs := msm8916-wcd-analog.o
snd-soc-msm8916-digital-objs := msm8916-wcd-digital.o
snd-soc-mt6351-objs := mt6351.o
@@ -369,6 +370,7 @@ 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_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/cpcap.c b/sound/soc/codecs/cpcap.c
index d7f05b384f1fb..d0d5ace9c4225 100644
--- a/sound/soc/codecs/cpcap.c
+++ b/sound/soc/codecs/cpcap.c
@@ -16,6 +16,14 @@
#include <sound/soc.h>
#include <sound/tlv.h>
+/* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */
+#define CPCAP_BIT_AUDIO_LOW_PWR 6
+#define CPCAP_BIT_AUD_LOWPWR_SPEED 5
+#define CPCAP_BIT_VAUDIOPRISTBY 4
+#define CPCAP_BIT_VAUDIO_MODE1 2
+#define CPCAP_BIT_VAUDIO_MODE0 1
+#define CPCAP_BIT_V_AUDIO_EN 0
+
/* Register 513 CPCAP_REG_CC --- CODEC */
#define CPCAP_BIT_CDC_CLK2 15
#define CPCAP_BIT_CDC_CLK1 14
@@ -251,6 +259,8 @@ struct cpcap_audio {
int codec_clk_id;
int codec_freq;
int codec_format;
+
+ unsigned int voice_call:1;
};
static int cpcap_st_workaround(struct snd_soc_dapm_widget *w,
@@ -1370,6 +1380,114 @@ static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
return 0;
}
+/*
+ * Configure voice call if cpcap->voice_call is set.
+ *
+ * We can configure most with snd_soc_dai_set_sysclk(), snd_soc_dai_set_fmt()
+ * and snd_soc_dai_set_tdm_slot(). This function configures the rest of the
+ * cpcap related hardware piceses as CPU is not involved in the voice call.
+ */
+static int cpcap_voice_call(struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component);
+ int mask, err;
+
+ /* Maybe enable modem to codec VAUDIO_MODE1? */
+ mask = BIT(CPCAP_BIT_VAUDIO_MODE1);
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC,
+ mask, cpcap->voice_call ? mask : 0);
+ if (err)
+ return err;
+
+ /* Maybe clear MIC1_MUX? */
+ mask = BIT(CPCAP_BIT_MIC1_MUX);
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI,
+ mask, cpcap->voice_call ? 0 : mask);
+ if (err)
+ return err;
+
+ /* Maybe set MIC2_MUX? */
+ mask = BIT(CPCAP_BIT_MB_ON1L) | BIT(CPCAP_BIT_MB_ON1R) |
+ BIT(CPCAP_BIT_MIC2_MUX) | BIT(CPCAP_BIT_MIC2_PGA_EN);
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI,
+ mask, cpcap->voice_call ? mask : 0);
+ if (err)
+ return err;
+
+ /* Maybe enable LDSP? */
+ mask = BIT(CPCAP_BIT_A2_LDSP_L_EN) | BIT(CPCAP_BIT_A2_LDSP_R_EN);
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA,
+ mask, cpcap->voice_call ? mask : 0);
+ if (err)
+ return err;
+
+ /* Maybe enable CPCAP_BIT_PGA_CDC_EN for call? */
+ mask = BIT(CPCAP_BIT_PGA_CDC_EN);
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA,
+ mask, cpcap->voice_call ? mask : 0);
+ if (err)
+ return err;
+
+ /* Maybe unmute voice? */
+ err = snd_soc_dai_digital_mute(dai, !cpcap->voice_call,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (err)
+ return err;
+
+ /* Maybe enable modem to codec mic CDC and HPF? */
+ mask = BIT(CPCAP_BIT_MIC2_CDC_EN) | BIT(CPCAP_BIT_CDC_EN_RX) |
+ BIT(CPCAP_BIT_AUDOHPF_1) | BIT(CPCAP_BIT_AUDOHPF_0) |
+ BIT(CPCAP_BIT_AUDIHPF_1) | BIT(CPCAP_BIT_AUDIHPF_0);
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC,
+ mask, cpcap->voice_call ? mask : 0);
+ if (err)
+ return err;
+
+ /* Maybe enable modem to codec CDC? */
+ mask = BIT(CPCAP_BIT_CDC_CLK_EN);
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI,
+ mask, cpcap->voice_call ? mask : 0);
+
+ return err;
+}
+
+static int cpcap_voice_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ struct snd_soc_component *component = dai->component;
+ struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component);
+ int err, ts_mask, mask;
+
+ /* Modem to codec audio with no CPU involved? */
+ if (tx_mask == 0 && rx_mask == 1 && slot_width == 8)
+ cpcap->voice_call = true;
+ else
+ cpcap->voice_call = false;
+
+ ts_mask = 0x7 << CPCAP_BIT_MIC2_TIMESLOT0;
+ ts_mask |= 0x7 << CPCAP_BIT_MIC1_RX_TIMESLOT0;
+
+ mask = (tx_mask & 0x7) << CPCAP_BIT_MIC2_TIMESLOT0;
+ mask |= (rx_mask & 0x7) << CPCAP_BIT_MIC1_RX_TIMESLOT0;
+
+ err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI,
+ ts_mask, mask);
+ if (err)
+ return err;
+
+ err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, slot_width * 1000);
+ if (err)
+ return err;
+
+ err = cpcap_voice_call(dai);
+ if (err)
+ return err;
+
+ return 0;
+}
+
static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_component *component = dai->component;
@@ -1391,6 +1509,7 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = {
.hw_params = cpcap_voice_hw_params,
.set_sysclk = cpcap_voice_set_dai_sysclk,
.set_fmt = cpcap_voice_set_dai_fmt,
+ .set_tdm_slot = cpcap_voice_set_tdm_slot,
.digital_mute = cpcap_voice_set_mute,
};
diff --git a/sound/soc/codecs/motmdm.c b/sound/soc/codecs/motmdm.c
new file mode 100644
index 0000000000000..b3371811d8872
--- /dev/null
+++ b/sound/soc/codecs/motmdm.c
@@ -0,0 +1,514 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Mapphone MDM6600 voice call audio support
+ * Copyright 2018 Tony Lindgren <tony@atomide.com>
+ */
+
+#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 <linux/serdev-gsm.h>
+
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include <linux/mfd/motorola-mdm.h>
+
+struct motmdm_driver_data {
+ struct snd_soc_component *component;
+ struct snd_soc_dai *master_dai;
+ struct device *modem;
+ struct motmdm_dlci mot_dlci;
+ struct regmap *regmap;
+ unsigned char *buf;
+ size_t len;
+ unsigned int enabled:1;
+ spinlock_t lock; /* enable/disabled lock */
+};
+
+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);
+
+
+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->modem, &ddata->mot_dlci, 1000,
+ cmd, strlen(cmd),
+ ddata->buf, ddata->len);
+ if (error < 0) {
+ dev_err(component->dev, "%s: %s failed with %i\n",
+ __func__, cmd, error);
+
+ return error;
+ }
+
+ error = kstrtouint(ddata->buf, 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->modem, &ddata->mot_dlci, 1000,
+ cmd, strlen(cmd),
+ ddata->buf, ddata->len);
+ 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 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),
+};
+
+/* REVISIT: Use this to reconfigure cpcap codec? */
+static const struct snd_soc_dapm_widget motmdm_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("RX"),
+ SND_SOC_DAPM_OUTPUT("TX"),
+};
+
+static const struct snd_soc_dapm_route motmdm_intercon[] = {
+ { "Capture", NULL, "RX" },
+ { "TX", NULL, "Playback" },
+};
+
+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_soc_notify(struct motmdm_dlci *mot_dlci,
+ enum motmdm_state state)
+{
+ struct motmdm_driver_data *ddata = mot_dlci->drvdata;
+ unsigned long flags;
+ bool enable, notify = false;
+
+ switch (state) {
+ case MOTMDM_STATE_DIAL:
+ case MOTMDM_STATE_ANSWERING:
+ case MOTMDM_STATE_CONNECTING:
+ case MOTMDM_STATE_CONNECTED:
+ enable = true;
+ break;
+ case MOTMDM_STATE_HANGING_UP:
+ case MOTMDM_STATE_DISCONNECTED:
+ enable = false;
+ break;
+ default:
+ return 0;
+ }
+
+ spin_lock_irqsave(&ddata->lock, flags);
+ if (ddata->enabled != enable) {
+ ddata->enabled = enable;
+ notify = true;
+ }
+ spin_unlock_irqrestore(&ddata->lock, flags);
+
+ if (!notify)
+ return 0;
+
+ if (enable)
+ motmdm_enable_primary_dai(ddata->component);
+ else
+ motmdm_disable_primary_dai(ddata->component);
+
+ return 0;
+}
+
+static int motmdm_soc_probe(struct snd_soc_component *component)
+{
+ struct motmdm_driver_data *ddata;
+ struct motmdm_dlci *mot_dlci;
+ const unsigned char *cmd = "AT+CMUT=0";
+ int error;
+
+ ddata = devm_kzalloc(component->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->component = component;
+ ddata->modem = component->dev->parent;
+ mot_dlci = &ddata->mot_dlci;
+ ddata->len = PAGE_SIZE;
+ spin_lock_init(&ddata->lock);
+ snd_soc_component_set_drvdata(component, ddata);
+
+ 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;
+ }
+
+ mot_dlci->drvdata = ddata;
+ mot_dlci->line = MOTMDM_DLCI2;
+ mot_dlci->notify = motmdm_soc_notify;
+
+ error = motmdm_register_dlci(ddata->modem, mot_dlci);
+ if (error)
+ return error;
+
+ error = motmdm_parse_tdm(component);
+ if (error) {
+ return error;
+ }
+
+ regcache_sync(ddata->regmap);
+
+ error = motmdm_send_command(ddata->modem, mot_dlci, 1000,
+ cmd, strlen(cmd),
+ ddata->buf, ddata->len);
+ if (error < 0) {
+ motmdm_unregister_dlci(ddata->modem, mot_dlci);
+
+ return error;
+ }
+
+ return motmdm_disable_primary_dai(ddata->component);
+}
+
+static void motmdm_soc_remove(struct snd_soc_component *component)
+{
+ struct motmdm_driver_data *ddata;
+ struct motmdm_dlci *mot_dlci;
+
+ ddata = snd_soc_component_get_drvdata(component);
+ mot_dlci = &ddata->mot_dlci;
+
+ motmdm_unregister_dlci(ddata->modem, mot_dlci);
+}
+
+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),
+ .dapm_widgets = motmdm_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(motmdm_dapm_widgets),
+ .dapm_routes = motmdm_intercon,
+ .num_dapm_routes = ARRAY_SIZE(motmdm_intercon),
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+static int motmdm_codec_probe(struct platform_device *pdev)
+{
+ struct device_node *codec_node =
+ of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec");
+
+ pdev->dev.of_node = codec_node;
+
+ return devm_snd_soc_register_component(&pdev->dev,
+ &soc_codec_dev_motmdm,
+ motmdm_dai,
+ ARRAY_SIZE(motmdm_dai));
+}
+
+static struct platform_driver motmdm_driver = {
+ .probe = motmdm_codec_probe,
+ .driver = {
+ .name = "mot-mdm6600-codec",
+ },
+};
+module_platform_driver(motmdm_driver);
+
+MODULE_ALIAS("platform:motmdm-codec");
+MODULE_DESCRIPTION("ASoC Motorola Mapphone MDM6600 codec driver");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/ti/omap-mcbsp-priv.h b/sound/soc/ti/omap-mcbsp-priv.h
index 7865cda4bf0ad..9464f5d358221 100644
--- a/sound/soc/ti/omap-mcbsp-priv.h
+++ b/sound/soc/ti/omap-mcbsp-priv.h
@@ -262,6 +262,8 @@ struct omap_mcbsp {
struct omap_mcbsp_platform_data *pdata;
struct omap_mcbsp_st_data *st_data;
struct omap_mcbsp_reg_cfg cfg_regs;
+ struct snd_soc_dai_driver *dais;
+ int dai_count;
struct snd_dmaengine_dai_dma_data dma_data[2];
unsigned int dma_req[2];
int dma_op_mode;
diff --git a/sound/soc/ti/omap-mcbsp.c b/sound/soc/ti/omap-mcbsp.c
index a395598f1f208..d655f41bf30c1 100644
--- a/sound/soc/ti/omap-mcbsp.c
+++ b/sound/soc/ti/omap-mcbsp.c
@@ -28,6 +28,7 @@
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/of_graph.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -1318,23 +1319,53 @@ static int omap_mcbsp_remove(struct snd_soc_dai *dai)
return 0;
}
-static struct snd_soc_dai_driver omap_mcbsp_dai = {
- .probe = omap_mcbsp_probe,
- .remove = omap_mcbsp_remove,
- .playback = {
- .channels_min = 1,
- .channels_max = 16,
- .rates = OMAP_MCBSP_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
- },
- .capture = {
- .channels_min = 1,
- .channels_max = 16,
- .rates = OMAP_MCBSP_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
- },
- .ops = &mcbsp_dai_ops,
-};
+static int omap_mcbsp_init_dais(struct omap_mcbsp *mcbsp)
+{
+ struct device_node *np = mcbsp->dev->of_node;
+ int i;
+
+ if (np)
+ mcbsp->dai_count = of_graph_get_endpoint_count(np);
+
+ if (!mcbsp->dai_count)
+ mcbsp->dai_count = 1;
+
+ mcbsp->dais = devm_kcalloc(mcbsp->dev, mcbsp->dai_count,
+ sizeof(*mcbsp->dais), GFP_KERNEL);
+ if (!mcbsp->dais)
+ return -ENOMEM;
+
+ for (i = 0; i < mcbsp->dai_count; i++) {
+ struct snd_soc_dai_driver *dai = &mcbsp->dais[i];
+
+ dai->name = devm_kasprintf(mcbsp->dev, GFP_KERNEL, "%s-dai%i",
+ dev_name(mcbsp->dev), i);
+
+ if (i == 0) {
+ dai->probe = omap_mcbsp_probe;
+ dai->remove = omap_mcbsp_remove;
+ dai->ops = &mcbsp_dai_ops;
+ }
+ dai->playback.channels_min = 1;
+ dai->playback.channels_max = 16;
+ dai->playback.rates = OMAP_MCBSP_RATES;
+ if (mcbsp->pdata->reg_size == 2)
+ dai->playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ else
+ dai->playback.formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE;
+ dai->capture.channels_min = 1;
+ dai->capture.channels_max = 16;
+ dai->capture.rates = OMAP_MCBSP_RATES;
+ if (mcbsp->pdata->reg_size == 2)
+ dai->capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ else
+ dai->capture.formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE;
+ }
+
+ return 0;
+}
static const struct snd_soc_component_driver omap_mcbsp_component = {
.name = "omap-mcbsp",
@@ -1423,18 +1454,17 @@ static int asoc_mcbsp_probe(struct platform_device *pdev)
mcbsp->dev = &pdev->dev;
platform_set_drvdata(pdev, mcbsp);
- ret = omap_mcbsp_init(pdev);
+ ret = omap_mcbsp_init_dais(mcbsp);
if (ret)
return ret;
- if (mcbsp->pdata->reg_size == 2) {
- omap_mcbsp_dai.playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
- omap_mcbsp_dai.capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
- }
+ ret = omap_mcbsp_init(pdev);
+ if (ret)
+ return ret;
ret = devm_snd_soc_register_component(&pdev->dev,
&omap_mcbsp_component,
- &omap_mcbsp_dai, 1);
+ mcbsp->dais, mcbsp->dai_count);
if (ret)
return ret;