phone-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
@ 2021-01-07 22:45 Pavel Machek
  2021-01-28 18:34 ` Pavel Machek
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Pavel Machek @ 2021-01-07 22:45 UTC (permalink / raw)
  To: johan, kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 12927 bytes --]


Motorola is using a custom TS 27.010 based serial port line discipline
for various devices on the modem. These devices can be accessed on
dedicated channels using Linux kernel serdev-ngsm driver.

For the GNSS on these devices, we need to kick the GNSS device at a
desired rate. Otherwise the GNSS device stops sending data after a
few minutes. The rate we poll data defaults to 1000 ms, and can be
specified with a module option rate_ms between 1 to 16 seconds.

[Tony Lindgren did most of the work here, I just converted it to be
normal serdev.]

Signed-off-by: Pavel Machek <pavel@ucw.cz>

diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
index bd12e3d57baa..a7c449d8428c 100644
--- a/drivers/gnss/Kconfig
+++ b/drivers/gnss/Kconfig
@@ -13,6 +13,14 @@ menuconfig GNSS
 
 if GNSS
 
+config GNSS_MOTMDM
+	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
+	depends on SERIAL_DEV_N_GSM
+	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 451f11401ecc..f5afc2c22a3b 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 000000000000..bfd50e130631
--- /dev/null
+++ b/drivers/gnss/motmdm.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Modem TS 27.010 serdev GNSS driver
+ *
+ * Copyright (C) 2018 - 2020 Tony Lindgren <tony@atomide.com>
+ * Copyright (C) 2020 - 2021 Pavel Machek <pavel@ucw.cz>
+ *
+ * Based on drivers/gnss/sirf.c driver example:
+ * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
+ */
+
+#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/serdev-gsm.h>
+#include <linux/slab.h>
+
+#define MOTMDM_GNSS_TIMEOUT	1000
+#define MOTMDM_GNSS_RATE	1000
+
+/*
+ * Motorola MDM GNSS device communicates over a dedicated TS 27.010 channel
+ * using custom data packets. The packets look like AT commands embedded into
+ * a Motorola invented packet using format like "U1234AT+MPDSTART=0,1,100,0".
+ * But it's not an AT compatible serial interface, it's a packet interface
+ * using AT like commands.
+ */
+#define MOTMDM_GNSS_HEADER_LEN	5				/* U1234 */
+#define MOTMDM_GNSS_RESP_LEN	(MOTMDM_GNSS_HEADER_LEN + 4)	/* U1234+MPD */
+#define MOTMDM_GNSS_DATA_LEN	(MOTMDM_GNSS_RESP_LEN + 1)	/* U1234~+MPD */
+#define MOTMDM_GNSS_STATUS_LEN	(MOTMDM_GNSS_DATA_LEN + 7)	/* STATUS= */
+#define MOTMDM_GNSS_NMEA_LEN	(MOTMDM_GNSS_DATA_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 serdev_device *serdev;
+	struct delayed_work restart_work;
+	struct mutex mutex;	/* For modem commands */
+	ktime_t last_update;
+	int status;
+	unsigned char *buf;
+	size_t len;
+	wait_queue_head_t read_queue;
+	unsigned int parsed:1;
+};
+
+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)");
+
+/*
+ * Note that multiple commands can be sent in series with responses coming
+ * out-of-order. For GNSS, we don't need to care about the out-of-order
+ * responses, and can assume we have at most one command active at a time.
+ * For the commands, can use just a jiffies base packet ID and let the modem
+ * sort out the ID conflicts with the modem's unsolicited message ID
+ * numbering.
+ */
+int motmdm_gnss_send_command(struct motmdm_gnss_data *ddata,
+			     const u8 *buf, int len)
+{
+	struct gnss_device *gdev = ddata->gdev;
+	const int timeout_ms = 1000;
+	unsigned char cmd[128];
+	int ret, cmdlen;
+
+	cmdlen = len + MOTMDM_GNSS_HEADER_LEN + 1;
+	if (cmdlen > 128)
+		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, ":OK")) {
+		dev_err(&gdev->dev, "command %s error %s\n",
+			cmd, ddata->buf);
+		ret = -EPIPE;
+	}
+
+	ret = len;
+
+out_unlock:
+	mutex_unlock(&ddata->mutex);
+
+	return ret;
+}
+
+/*
+ * 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 gnss_device *gdev = ddata->gdev;
+	const unsigned char *cmd = "AT+MPDSTART=0,1,100,0";
+	int error;
+
+	ddata->last_update = ktime_get();
+
+	error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+	if (error < 0) {
+		/* Timeouts can happen, don't warn and try 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));
+	}
+}
+
+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);
+	const unsigned char *cmd = "AT+MPDSTOP";
+
+	cancel_delayed_work_sync(&ddata->restart_work);
+
+	return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+}
+
+static int motmdm_gnss_init(struct gnss_device *gdev)
+{
+	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+	const unsigned char *cmd = "AT+MPDINIT=1";
+	int error;
+
+	error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+	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);
+	const unsigned char *cmd = "AT+MPDINIT=0";
+	int error;
+
+	error = motmdm_gnss_stop(gdev);
+	if (error < 0)
+		return error;
+
+	return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+}
+
+static int motmdm_gnss_receive_data(struct serdev_device *serdev,
+					const unsigned char *buf, size_t len)
+{
+	struct motmdm_gnss_data *ddata = serdev_device_get_drvdata(serdev);
+	struct gnss_device *gdev = ddata->gdev;
+	const unsigned char *msg;
+	size_t msglen;
+	int error = 0;
+
+	if (len <= MOTMDM_GNSS_RESP_LEN)
+		return 0;
+
+	/* Handle U1234+MPD style command response */
+	if (buf[MOTMDM_GNSS_HEADER_LEN] != '~') {
+		msg = buf + MOTMDM_GNSS_RESP_LEN;
+		strncpy(ddata->buf, msg, len - MOTMDM_GNSS_RESP_LEN);
+		ddata->parsed = true;
+		wake_up(&ddata->read_queue);
+
+		return len;
+	}
+
+	if (len <= MOTMDM_GNSS_DATA_LEN)
+		return 0;
+
+	/* Handle U1234~+MPD style unsolicted message */
+	switch (buf[MOTMDM_GNSS_DATA_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);
+		WARN_ON(error);
+		break;
+	case 'S':	/* UNNNN~+MPDSTATUS=N,NN */
+		msg = buf + MOTMDM_GNSS_STATUS_LEN;
+		msglen = len - MOTMDM_GNSS_STATUS_LEN;
+
+		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':	/* UNNNN~+MPDXREQ=N for updated xtra2.bin needed */
+	default:
+		break;
+	}
+
+	return len;
+}
+
+static const struct serdev_device_ops gnss_serdev_ops = {
+	.receive_buf    = motmdm_gnss_receive_data,
+	.write_wakeup   = serdev_device_write_wakeup,
+};
+
+static int motmdm_gnss_open(struct gnss_device *gdev)
+{
+	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+	int error;
+
+	serdev_device_set_client_ops(ddata->serdev, &gnss_serdev_ops);
+
+	error = serdev_device_open(ddata->serdev);
+	if (error) {
+		return error;
+	}
+
+	error = motmdm_gnss_init(gdev);
+	if (error) {
+		return error;
+	}
+
+	return 0;
+}
+
+static void motmdm_gnss_close(struct gnss_device *gdev)
+{
+	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+	int error;
+
+	error = motmdm_gnss_finish(gdev);
+	if (error < 0)
+		dev_warn(&gdev->dev, "%s: close failed: %i\n",
+			 __func__, error);
+
+	serdev_device_close(ddata->serdev);
+}
+
+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);
+
+	return serdev_device_write(ddata->serdev, buf, count, MAX_SCHEDULE_TIMEOUT);
+	serdev_device_wait_until_sent(ddata->serdev, 0);
+
+	return count;
+}
+
+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 serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct motmdm_gnss_data *ddata;
+	struct gnss_device *gdev;
+	int ret;
+
+	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	ddata->serdev = serdev;
+	ddata->modem = dev->parent;
+	ddata->len = PAGE_SIZE;
+	mutex_init(&ddata->mutex);
+	INIT_DELAYED_WORK(&ddata->restart_work, motmdm_gnss_restart);
+	init_waitqueue_head(&ddata->read_queue);
+
+	ddata->buf = devm_kzalloc(dev, ddata->len, GFP_KERNEL);
+	if (!ddata->buf)
+		return -ENOMEM;
+
+	serdev_device_set_drvdata(serdev, 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)
+		gnss_put_device(ddata->gdev);
+
+	return ret;
+}
+
+static void motmdm_gnss_remove(struct serdev_device *serdev)
+{
+	struct motmdm_gnss_data *data = serdev_device_get_drvdata(serdev);
+
+	gnss_deregister_device(data->gdev);
+	gnss_put_device(data->gdev);
+};
+
+#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 serdev_device_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_serdev_device_driver(motmdm_gnss_driver);
+
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("Motorola Mapphone MDM6600 GNSS receiver driver");
+MODULE_LICENSE("GPL v2");


-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
  2021-01-07 22:45 [PATCH] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem Pavel Machek
@ 2021-01-28 18:34 ` Pavel Machek
       [not found] ` <YBQvvUitX4MtRrh+@hovoldconsulting.com>
  2021-01-29 22:42 ` [PATCHv2] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem Pavel Machek
  2 siblings, 0 replies; 10+ messages in thread
From: Pavel Machek @ 2021-01-28 18:34 UTC (permalink / raw)
  To: johan, kernel list, phone-devel, tony, Greg KH

[-- Attachment #1: Type: text/plain, Size: 13952 bytes --]

Hi!

> Motorola is using a custom TS 27.010 based serial port line discipline
> for various devices on the modem. These devices can be accessed on
> dedicated channels using Linux kernel serdev-ngsm driver.
> 
> For the GNSS on these devices, we need to kick the GNSS device at a
> desired rate. Otherwise the GNSS device stops sending data after a
> few minutes. The rate we poll data defaults to 1000 ms, and can be
> specified with a module option rate_ms between 1 to 16 seconds.
> 
> [Tony Lindgren did most of the work here, I just converted it to be
> normal serdev.]
> 
> Signed-off-by: Pavel Machek <pavel@ucw.cz>

20 days... ping...?
								Pavel

> diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
> index bd12e3d57baa..a7c449d8428c 100644
> --- a/drivers/gnss/Kconfig
> +++ b/drivers/gnss/Kconfig
> @@ -13,6 +13,14 @@ menuconfig GNSS
>  
>  if GNSS
>  
> +config GNSS_MOTMDM
> +	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
> +	depends on SERIAL_DEV_N_GSM
> +	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 451f11401ecc..f5afc2c22a3b 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 000000000000..bfd50e130631
> --- /dev/null
> +++ b/drivers/gnss/motmdm.c
> @@ -0,0 +1,409 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Motorola Modem TS 27.010 serdev GNSS driver
> + *
> + * Copyright (C) 2018 - 2020 Tony Lindgren <tony@atomide.com>
> + * Copyright (C) 2020 - 2021 Pavel Machek <pavel@ucw.cz>
> + *
> + * Based on drivers/gnss/sirf.c driver example:
> + * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
> + */
> +
> +#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/serdev-gsm.h>
> +#include <linux/slab.h>
> +
> +#define MOTMDM_GNSS_TIMEOUT	1000
> +#define MOTMDM_GNSS_RATE	1000
> +
> +/*
> + * Motorola MDM GNSS device communicates over a dedicated TS 27.010 channel
> + * using custom data packets. The packets look like AT commands embedded into
> + * a Motorola invented packet using format like "U1234AT+MPDSTART=0,1,100,0".
> + * But it's not an AT compatible serial interface, it's a packet interface
> + * using AT like commands.
> + */
> +#define MOTMDM_GNSS_HEADER_LEN	5				/* U1234 */
> +#define MOTMDM_GNSS_RESP_LEN	(MOTMDM_GNSS_HEADER_LEN + 4)	/* U1234+MPD */
> +#define MOTMDM_GNSS_DATA_LEN	(MOTMDM_GNSS_RESP_LEN + 1)	/* U1234~+MPD */
> +#define MOTMDM_GNSS_STATUS_LEN	(MOTMDM_GNSS_DATA_LEN + 7)	/* STATUS= */
> +#define MOTMDM_GNSS_NMEA_LEN	(MOTMDM_GNSS_DATA_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 serdev_device *serdev;
> +	struct delayed_work restart_work;
> +	struct mutex mutex;	/* For modem commands */
> +	ktime_t last_update;
> +	int status;
> +	unsigned char *buf;
> +	size_t len;
> +	wait_queue_head_t read_queue;
> +	unsigned int parsed:1;
> +};
> +
> +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)");
> +
> +/*
> + * Note that multiple commands can be sent in series with responses coming
> + * out-of-order. For GNSS, we don't need to care about the out-of-order
> + * responses, and can assume we have at most one command active at a time.
> + * For the commands, can use just a jiffies base packet ID and let the modem
> + * sort out the ID conflicts with the modem's unsolicited message ID
> + * numbering.
> + */
> +int motmdm_gnss_send_command(struct motmdm_gnss_data *ddata,
> +			     const u8 *buf, int len)
> +{
> +	struct gnss_device *gdev = ddata->gdev;
> +	const int timeout_ms = 1000;
> +	unsigned char cmd[128];
> +	int ret, cmdlen;
> +
> +	cmdlen = len + MOTMDM_GNSS_HEADER_LEN + 1;
> +	if (cmdlen > 128)
> +		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, ":OK")) {
> +		dev_err(&gdev->dev, "command %s error %s\n",
> +			cmd, ddata->buf);
> +		ret = -EPIPE;
> +	}
> +
> +	ret = len;
> +
> +out_unlock:
> +	mutex_unlock(&ddata->mutex);
> +
> +	return ret;
> +}
> +
> +/*
> + * 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 gnss_device *gdev = ddata->gdev;
> +	const unsigned char *cmd = "AT+MPDSTART=0,1,100,0";
> +	int error;
> +
> +	ddata->last_update = ktime_get();
> +
> +	error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
> +	if (error < 0) {
> +		/* Timeouts can happen, don't warn and try 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));
> +	}
> +}
> +
> +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);
> +	const unsigned char *cmd = "AT+MPDSTOP";
> +
> +	cancel_delayed_work_sync(&ddata->restart_work);
> +
> +	return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
> +}
> +
> +static int motmdm_gnss_init(struct gnss_device *gdev)
> +{
> +	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
> +	const unsigned char *cmd = "AT+MPDINIT=1";
> +	int error;
> +
> +	error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
> +	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);
> +	const unsigned char *cmd = "AT+MPDINIT=0";
> +	int error;
> +
> +	error = motmdm_gnss_stop(gdev);
> +	if (error < 0)
> +		return error;
> +
> +	return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
> +}
> +
> +static int motmdm_gnss_receive_data(struct serdev_device *serdev,
> +					const unsigned char *buf, size_t len)
> +{
> +	struct motmdm_gnss_data *ddata = serdev_device_get_drvdata(serdev);
> +	struct gnss_device *gdev = ddata->gdev;
> +	const unsigned char *msg;
> +	size_t msglen;
> +	int error = 0;
> +
> +	if (len <= MOTMDM_GNSS_RESP_LEN)
> +		return 0;
> +
> +	/* Handle U1234+MPD style command response */
> +	if (buf[MOTMDM_GNSS_HEADER_LEN] != '~') {
> +		msg = buf + MOTMDM_GNSS_RESP_LEN;
> +		strncpy(ddata->buf, msg, len - MOTMDM_GNSS_RESP_LEN);
> +		ddata->parsed = true;
> +		wake_up(&ddata->read_queue);
> +
> +		return len;
> +	}
> +
> +	if (len <= MOTMDM_GNSS_DATA_LEN)
> +		return 0;
> +
> +	/* Handle U1234~+MPD style unsolicted message */
> +	switch (buf[MOTMDM_GNSS_DATA_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);
> +		WARN_ON(error);
> +		break;
> +	case 'S':	/* UNNNN~+MPDSTATUS=N,NN */
> +		msg = buf + MOTMDM_GNSS_STATUS_LEN;
> +		msglen = len - MOTMDM_GNSS_STATUS_LEN;
> +
> +		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':	/* UNNNN~+MPDXREQ=N for updated xtra2.bin needed */
> +	default:
> +		break;
> +	}
> +
> +	return len;
> +}
> +
> +static const struct serdev_device_ops gnss_serdev_ops = {
> +	.receive_buf    = motmdm_gnss_receive_data,
> +	.write_wakeup   = serdev_device_write_wakeup,
> +};
> +
> +static int motmdm_gnss_open(struct gnss_device *gdev)
> +{
> +	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
> +	int error;
> +
> +	serdev_device_set_client_ops(ddata->serdev, &gnss_serdev_ops);
> +
> +	error = serdev_device_open(ddata->serdev);
> +	if (error) {
> +		return error;
> +	}
> +
> +	error = motmdm_gnss_init(gdev);
> +	if (error) {
> +		return error;
> +	}
> +
> +	return 0;
> +}
> +
> +static void motmdm_gnss_close(struct gnss_device *gdev)
> +{
> +	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
> +	int error;
> +
> +	error = motmdm_gnss_finish(gdev);
> +	if (error < 0)
> +		dev_warn(&gdev->dev, "%s: close failed: %i\n",
> +			 __func__, error);
> +
> +	serdev_device_close(ddata->serdev);
> +}
> +
> +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);
> +
> +	return serdev_device_write(ddata->serdev, buf, count, MAX_SCHEDULE_TIMEOUT);
> +	serdev_device_wait_until_sent(ddata->serdev, 0);
> +
> +	return count;
> +}
> +
> +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 serdev_device *serdev)
> +{
> +	struct device *dev = &serdev->dev;
> +	struct motmdm_gnss_data *ddata;
> +	struct gnss_device *gdev;
> +	int ret;
> +
> +	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
> +	if (!ddata)
> +		return -ENOMEM;
> +
> +	ddata->serdev = serdev;
> +	ddata->modem = dev->parent;
> +	ddata->len = PAGE_SIZE;
> +	mutex_init(&ddata->mutex);
> +	INIT_DELAYED_WORK(&ddata->restart_work, motmdm_gnss_restart);
> +	init_waitqueue_head(&ddata->read_queue);
> +
> +	ddata->buf = devm_kzalloc(dev, ddata->len, GFP_KERNEL);
> +	if (!ddata->buf)
> +		return -ENOMEM;
> +
> +	serdev_device_set_drvdata(serdev, 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)
> +		gnss_put_device(ddata->gdev);
> +
> +	return ret;
> +}
> +
> +static void motmdm_gnss_remove(struct serdev_device *serdev)
> +{
> +	struct motmdm_gnss_data *data = serdev_device_get_drvdata(serdev);
> +
> +	gnss_deregister_device(data->gdev);
> +	gnss_put_device(data->gdev);
> +};
> +
> +#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 serdev_device_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_serdev_device_driver(motmdm_gnss_driver);
> +
> +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
> +MODULE_DESCRIPTION("Motorola Mapphone MDM6600 GNSS receiver driver");
> +MODULE_LICENSE("GPL v2");
> 
> 
> -- 
> http://www.livejournal.com/~pavelmachek



-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
       [not found] ` <YBQvvUitX4MtRrh+@hovoldconsulting.com>
@ 2021-01-29 22:34   ` Pavel Machek
       [not found]   ` <20210131170639.GA21067@duo.ucw.cz>
  1 sibling, 0 replies; 10+ messages in thread
From: Pavel Machek @ 2021-01-29 22:34 UTC (permalink / raw)
  To: Johan Hovold; +Cc: kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 2973 bytes --]

Hi!


> > Motorola is using a custom TS 27.010 based serial port line discipline
> 
> s/serial port line discipline/multiplexer protocol/


> > diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
> > index bd12e3d57baa..a7c449d8428c 100644
> > --- a/drivers/gnss/Kconfig
> > +++ b/drivers/gnss/Kconfig
> > @@ -13,6 +13,14 @@ menuconfig GNSS
> >  
> >  if GNSS
> >  
> > +config GNSS_MOTMDM
> > +	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
> > +	depends on SERIAL_DEV_N_GSM
> 
> You need to post this driver together with the serdev-ngsm driver. This
> one cannot currently even be built without it, but we also need to see
> the greater picture here.
> 
> Does this even still need to be a build-time dependency?

Not any more, it is now normal serdev driver, that's why I posted it
separately.

> > +	  Say Y here if you have a Motorola modem using TS 27.010 line
> 
> s/line discipline/multiplexer protocol/

Ok.

> > +#define MOTMDM_GNSS_HEADER_LEN	5				/* U1234 */
> > +#define MOTMDM_GNSS_RESP_LEN	(MOTMDM_GNSS_HEADER_LEN + 4)	/* U1234+MPD */
> > +#define MOTMDM_GNSS_DATA_LEN	(MOTMDM_GNSS_RESP_LEN + 1)	/* U1234~+MPD */
> > +#define MOTMDM_GNSS_STATUS_LEN	(MOTMDM_GNSS_DATA_LEN + 7)	/* STATUS= */
> > +#define MOTMDM_GNSS_NMEA_LEN	(MOTMDM_GNSS_DATA_LEN + 8)	/* NMEA=NN, */
> 
> The comments are inconsistent; does the latter two have a "U1234"
> prefix?

It is U1234~+MPDSTATUS= and U1234~+MPDNMEA=NN, -- will fix. Hopefully
85 columns is okay with you here.

> > +		/*
> > +		 * 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);
> > +		WARN_ON(error);
> 
> The return value is not an "error" but the number of queued bytes.
> 
> So that WARN_ON(error) makes it look like you never even tested this?

Well, I did test it and it works. Unfortunately Droid 4 produces lot
of output during normal operation, including periodic WARNs, so I
missed that. Sorry about that.

> > +	error = serdev_device_open(ddata->serdev);
> > +	if (error) {
> > +		return error;
> > +	}
> 
> Nit: drop the brackets.

Ok.

> > +	error = motmdm_gnss_init(gdev);
> > +	if (error) {
> 
> You must close the port before returning.

Ok.

> > +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);
> > +
> > +	return serdev_device_write(ddata->serdev, buf, count, MAX_SCHEDULE_TIMEOUT);
> > +	serdev_device_wait_until_sent(ddata->serdev, 0);
> 
> This code is never reached.

Fixed.

I'll get new version out. I'll also get serdev/ngsm patch out for
context, but that one needs more work.

Best regards,

								Pavel

-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCHv2] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
  2021-01-07 22:45 [PATCH] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem Pavel Machek
  2021-01-28 18:34 ` Pavel Machek
       [not found] ` <YBQvvUitX4MtRrh+@hovoldconsulting.com>
@ 2021-01-29 22:42 ` Pavel Machek
  2021-02-28 20:46   ` Pavel Machek
  2 siblings, 1 reply; 10+ messages in thread
From: Pavel Machek @ 2021-01-29 22:42 UTC (permalink / raw)
  To: johan, kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 12904 bytes --]


Motorola is using a custom TS 27.010 based multiplexer protocol
for various devices on the modem. These devices can be accessed on
dedicated channels using Linux kernel serdev-ngsm driver.

For the GNSS on these devices, we need to kick the GNSS device at a
desired rate. Otherwise the GNSS device stops sending data after a
few minutes. The rate we poll data defaults to 1000 ms, and can be
specified with a module option rate_ms between 1 to 16 seconds.

[Tony Lindgren did most of the work here, I just converted it to be
normal serdev.]

Signed-off-by: Pavel Machek <pavel@ucw.cz>

diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
index bd12e3d57baa..9fac72eba726 100644
--- a/drivers/gnss/Kconfig
+++ b/drivers/gnss/Kconfig
@@ -13,6 +13,14 @@ menuconfig GNSS
 
 if GNSS
 
+config GNSS_MOTMDM
+	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
+	depends on SERIAL_DEV_BUS
+	help
+	  Say Y here if you have a Motorola modem using TS 27.010
+	  multiplexer protocol 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 451f11401ecc..f5afc2c22a3b 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 000000000000..00cddddab10b
--- /dev/null
+++ b/drivers/gnss/motmdm.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Modem TS 27.010 serdev GNSS driver
+ *
+ * Copyright (C) 2018 - 2020 Tony Lindgren <tony@atomide.com>
+ * Copyright (C) 2020 - 2021 Pavel Machek <pavel@ucw.cz>
+ *
+ * Based on drivers/gnss/sirf.c driver example:
+ * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
+ */
+
+#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/slab.h>
+#include <linux/serdev.h>
+
+#define MOTMDM_GNSS_TIMEOUT	1000
+#define MOTMDM_GNSS_RATE	1000
+
+/*
+ * Motorola MDM GNSS device communicates over a dedicated TS 27.010 channel
+ * using custom data packets. The packets look like AT commands embedded into
+ * a Motorola invented packet using format like "U1234AT+MPDSTART=0,1,100,0".
+ * But it's not an AT compatible serial interface, it's a packet interface
+ * using AT like commands.
+ */
+#define MOTMDM_GNSS_HEADER_LEN	5				/* U1234 */
+#define MOTMDM_GNSS_RESP_LEN	(MOTMDM_GNSS_HEADER_LEN + 4)	/* U1234+MPD */
+#define MOTMDM_GNSS_DATA_LEN	(MOTMDM_GNSS_RESP_LEN + 1)	/* U1234~+MPD */
+#define MOTMDM_GNSS_STATUS_LEN	(MOTMDM_GNSS_DATA_LEN + 7) /* U1234~+MPDSTATUS= */
+#define MOTMDM_GNSS_NMEA_LEN	(MOTMDM_GNSS_DATA_LEN + 8) /* U1234~+MPDNMEA=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 serdev_device *serdev;
+	struct delayed_work restart_work;
+	struct mutex mutex;	/* For modem commands */
+	ktime_t last_update;
+	int status;
+	unsigned char *buf;
+	size_t len;
+	wait_queue_head_t read_queue;
+	unsigned int parsed:1;
+};
+
+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)");
+
+/*
+ * Note that multiple commands can be sent in series with responses coming
+ * out-of-order. For GNSS, we don't need to care about the out-of-order
+ * responses, and can assume we have at most one command active at a time.
+ * For the commands, can use just a jiffies base packet ID and let the modem
+ * sort out the ID conflicts with the modem's unsolicited message ID
+ * numbering.
+ */
+int motmdm_gnss_send_command(struct motmdm_gnss_data *ddata,
+			     const u8 *buf, int len)
+{
+	struct gnss_device *gdev = ddata->gdev;
+	const int timeout_ms = 1000;
+	unsigned char cmd[128];
+	int ret, cmdlen;
+
+	cmdlen = len + MOTMDM_GNSS_HEADER_LEN + 1;
+	if (cmdlen > 128)
+		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, ":OK")) {
+		dev_err(&gdev->dev, "command %s error %s\n",
+			cmd, ddata->buf);
+		ret = -EPIPE;
+	}
+
+	ret = len;
+
+out_unlock:
+	mutex_unlock(&ddata->mutex);
+
+	return ret;
+}
+
+/*
+ * 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 gnss_device *gdev = ddata->gdev;
+	const unsigned char *cmd = "AT+MPDSTART=0,1,100,0";
+	int error;
+
+	ddata->last_update = ktime_get();
+
+	error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+	if (error < 0) {
+		/* Timeouts can happen, don't warn and try 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));
+	}
+}
+
+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);
+	const unsigned char *cmd = "AT+MPDSTOP";
+
+	cancel_delayed_work_sync(&ddata->restart_work);
+
+	return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+}
+
+static int motmdm_gnss_init(struct gnss_device *gdev)
+{
+	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+	const unsigned char *cmd = "AT+MPDINIT=1";
+	int error;
+
+	error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+	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);
+	const unsigned char *cmd = "AT+MPDINIT=0";
+	int error;
+
+	error = motmdm_gnss_stop(gdev);
+	if (error < 0)
+		return error;
+
+	return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+}
+
+static int motmdm_gnss_receive_data(struct serdev_device *serdev,
+					const unsigned char *buf, size_t len)
+{
+	struct motmdm_gnss_data *ddata = serdev_device_get_drvdata(serdev);
+	struct gnss_device *gdev = ddata->gdev;
+	const unsigned char *msg;
+	size_t msglen;
+	int ret = 0;
+
+	if (len <= MOTMDM_GNSS_RESP_LEN)
+		return 0;
+
+	/* Handle U1234+MPD style command response */
+	if (buf[MOTMDM_GNSS_HEADER_LEN] != '~') {
+		msg = buf + MOTMDM_GNSS_RESP_LEN;
+		strncpy(ddata->buf, msg, len - MOTMDM_GNSS_RESP_LEN);
+		ddata->parsed = true;
+		wake_up(&ddata->read_queue);
+
+		return len;
+	}
+
+	if (len <= MOTMDM_GNSS_DATA_LEN)
+		return 0;
+
+	/* Handle U1234~+MPD style unsolicted message */
+	switch (buf[MOTMDM_GNSS_DATA_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;
+
+		ret = gnss_insert_raw(gdev, msg, msglen);
+		WARN_ON(ret != msglen);
+		break;
+	case 'S':	/* UNNNN~+MPDSTATUS=N,NN */
+		msg = buf + MOTMDM_GNSS_STATUS_LEN;
+		msglen = len - MOTMDM_GNSS_STATUS_LEN;
+
+		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':	/* UNNNN~+MPDXREQ=N for updated xtra2.bin needed */
+	default:
+		break;
+	}
+
+	return len;
+}
+
+static const struct serdev_device_ops gnss_serdev_ops = {
+	.receive_buf    = motmdm_gnss_receive_data,
+	.write_wakeup   = serdev_device_write_wakeup,
+};
+
+static int motmdm_gnss_open(struct gnss_device *gdev)
+{
+	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+	int error;
+
+	serdev_device_set_client_ops(ddata->serdev, &gnss_serdev_ops);
+
+	error = serdev_device_open(ddata->serdev);
+	if (error)
+		return error;
+
+	error = motmdm_gnss_init(gdev);
+	if (error) {
+		serdev_device_close(ddata->serdev);
+		return error;
+	}
+
+	return 0;
+}
+
+static void motmdm_gnss_close(struct gnss_device *gdev)
+{
+	struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+	int error;
+
+	error = motmdm_gnss_finish(gdev);
+	if (error < 0)
+		dev_warn(&gdev->dev, "%s: close failed: %i\n",
+			 __func__, error);
+
+	serdev_device_close(ddata->serdev);
+}
+
+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);
+
+	return serdev_device_write(ddata->serdev, buf, count, MAX_SCHEDULE_TIMEOUT);
+}
+
+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 serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct motmdm_gnss_data *ddata;
+	struct gnss_device *gdev;
+	int ret;
+
+	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	ddata->serdev = serdev;
+	ddata->modem = dev->parent;
+	ddata->len = PAGE_SIZE;
+	mutex_init(&ddata->mutex);
+	INIT_DELAYED_WORK(&ddata->restart_work, motmdm_gnss_restart);
+	init_waitqueue_head(&ddata->read_queue);
+
+	ddata->buf = devm_kzalloc(dev, ddata->len, GFP_KERNEL);
+	if (!ddata->buf)
+		return -ENOMEM;
+
+	serdev_device_set_drvdata(serdev, 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)
+		gnss_put_device(ddata->gdev);
+
+	return ret;
+}
+
+static void motmdm_gnss_remove(struct serdev_device *serdev)
+{
+	struct motmdm_gnss_data *data = serdev_device_get_drvdata(serdev);
+
+	gnss_deregister_device(data->gdev);
+	gnss_put_device(data->gdev);
+};
+
+#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 serdev_device_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_serdev_device_driver(motmdm_gnss_driver);
+
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("Motorola Mapphone MDM6600 GNSS receiver driver");
+MODULE_LICENSE("GPL v2");


-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [RFC/context] add serdev interfaces to n_gsm
       [not found]   ` <20210131170639.GA21067@duo.ucw.cz>
@ 2021-02-10 21:28     ` Pavel Machek
  2021-02-11  9:04       ` Johan Hovold
  0 siblings, 1 reply; 10+ messages in thread
From: Pavel Machek @ 2021-02-10 21:28 UTC (permalink / raw)
  To: Johan Hovold, Greg KH; +Cc: kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 966 bytes --]

Hi!

> > > +config GNSS_MOTMDM
> > > +	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
> > > +	depends on SERIAL_DEV_N_GSM
> > 
> > You need to post this driver together with the serdev-ngsm driver. This
> > one cannot currently even be built without it, but we also need to see
> > the greater picture here.
> 
> Well, here it is, for greater picture. But it is not ready. Current
> problem I have is gsm_serdev_register_tty_port(). The way I do
> platform device registration causes oops on module unload. Help with
> that would be welcome

I would not mind comments on parent patch and some help here.

Basically I tried to work around limitation in 

int serdev_device_add(struct serdev_device *serdev)
{
...
       /* Only a single slave device is currently supported. */
       if (ctrl->serdev) {
...
                   return -EBUSY;

Best regards,
								Pavel 

-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC/context] add serdev interfaces to n_gsm
  2021-02-10 21:28     ` [RFC/context] add serdev interfaces to n_gsm Pavel Machek
@ 2021-02-11  9:04       ` Johan Hovold
  2021-02-19 22:06         ` Pavel Machek
  0 siblings, 1 reply; 10+ messages in thread
From: Johan Hovold @ 2021-02-11  9:04 UTC (permalink / raw)
  To: Pavel Machek; +Cc: Greg KH, kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 1770 bytes --]

On Wed, Feb 10, 2021 at 10:28:36PM +0100, Pavel Machek wrote:
> Hi!
> 
> > > > +config GNSS_MOTMDM
> > > > +	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
> > > > +	depends on SERIAL_DEV_N_GSM
> > > 
> > > You need to post this driver together with the serdev-ngsm driver. This
> > > one cannot currently even be built without it, but we also need to see
> > > the greater picture here.
> > 
> > Well, here it is, for greater picture. But it is not ready. Current
> > problem I have is gsm_serdev_register_tty_port(). The way I do
> > platform device registration causes oops on module unload. Help with
> > that would be welcome
> 
> I would not mind comments on parent patch and some help here.
> 
> Basically I tried to work around limitation in 
> 
> int serdev_device_add(struct serdev_device *serdev)
> {
> ...
>        /* Only a single slave device is currently supported. */
>        if (ctrl->serdev) {
> ...

I haven't really had time to look at the code yet, but trying to work
around the single-client (slave) assumption seems wrong. You still have
only one client per port even if the mux driver provides multiple
(virtual) ports.

But judging from a quick look it appears that you are indeed registering
one tty device per mux channel in gsm_serdev_register_tty_port() (as you
should) so perhaps that's not the issue here.

Do you have a stack trace from the oops? Are the client drivers holding
the ports open while you unload the parent driver? That sounds like
something which could go boom unless you pin the parent for example
(serdev doesn't support hangups).

Also, did you forget to post the gsm_tty_driver implementation? I don't
see a definition of that symbol in the patch.

Johan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC/context] add serdev interfaces to n_gsm
  2021-02-11  9:04       ` Johan Hovold
@ 2021-02-19 22:06         ` Pavel Machek
  0 siblings, 0 replies; 10+ messages in thread
From: Pavel Machek @ 2021-02-19 22:06 UTC (permalink / raw)
  To: Johan Hovold; +Cc: Greg KH, kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 2377 bytes --]

Hi!

> > > > > +config GNSS_MOTMDM
> > > > > +	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
> > > > > +	depends on SERIAL_DEV_N_GSM
> > > > 
> > > > You need to post this driver together with the serdev-ngsm driver. This
> > > > one cannot currently even be built without it, but we also need to see
> > > > the greater picture here.
> > > 
> > > Well, here it is, for greater picture. But it is not ready. Current
> > > problem I have is gsm_serdev_register_tty_port(). The way I do
> > > platform device registration causes oops on module unload. Help with
> > > that would be welcome
> > 
> > I would not mind comments on parent patch and some help here.
> > 
> > Basically I tried to work around limitation in 
> > 
> > int serdev_device_add(struct serdev_device *serdev)
> > {
> > ...
> >        /* Only a single slave device is currently supported. */
> >        if (ctrl->serdev) {
> > ...
> 
> I haven't really had time to look at the code yet, but trying to work
> around the single-client (slave) assumption seems wrong. You still have
> only one client per port even if the mux driver provides multiple
> (virtual) ports.

Correct.

But this limitation prevents me to registering multiple slave ports
with master serdev as their parent.

a) There's a physical line

b) Then there's serdev connected to it

c) Mux splits it to many virtual ports, but I still need something to
specify as their parent. serdev b) would be most suitable, but this
check prevents that.

> But judging from a quick look it appears that you are indeed registering
> one tty device per mux channel in gsm_serdev_register_tty_port() (as you
> should) so perhaps that's not the issue here.
> 
> Do you have a stack trace from the oops? Are the client drivers holding
> the ports open while you unload the parent driver? That sounds like
> something which could go boom unless you pin the parent for example
> (serdev doesn't support hangups).
> 
> Also, did you forget to post the gsm_tty_driver implementation? I don't
> see a definition of that symbol in the patch.

I'll gather the data in next debugging session.

But .... this way I have to provide fake platform device parent for
gsmtty1 and friends, and that just feels wrong.

Best regards,
								Pavel
-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCHv2] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
  2021-01-29 22:42 ` [PATCHv2] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem Pavel Machek
@ 2021-02-28 20:46   ` Pavel Machek
  2021-04-01  9:39     ` Johan Hovold
  0 siblings, 1 reply; 10+ messages in thread
From: Pavel Machek @ 2021-02-28 20:46 UTC (permalink / raw)
  To: johan, kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 36889 bytes --]

Hi!

> Motorola is using a custom TS 27.010 based multiplexer protocol
> for various devices on the modem. These devices can be accessed on
> dedicated channels using Linux kernel serdev-ngsm driver.
> 
> For the GNSS on these devices, we need to kick the GNSS device at a
> desired rate. Otherwise the GNSS device stops sending data after a
> few minutes. The rate we poll data defaults to 1000 ms, and can be
> specified with a module option rate_ms between 1 to 16 seconds.
> 
> [Tony Lindgren did most of the work here, I just converted it to be
> normal serdev.]

Could I get some comments on the gnss patch? It is fairly simple, and
I believe it is ready for merge.

Here's new version of the serdev multiplexing patch for reference. And
yes, I'd like you to review the design here, too. Yes,
gsm_serdev_register_tty_port() needs to be cleaned up, but with ifdefs
you can see alternative approach I tried to work around "controller
busy" problem.

Signed-off-by: Pavel Machek <pavel@ucw.cz>

Best regards,
								Pavel

diff --git a/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
new file mode 100644
index 000000000000..deec491ee821
--- /dev/null
+++ b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serdev/serdev-ngsm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic serdev-ngsm TS 27.010 driver
+
+maintainers:
+  - Tony Lindgren <tony@atomide.com>
+
+properties:
+  compatible:
+    enum:
+      - etsi,3gpp-ts27010-adaption1
+      - motorola,mapphone-mdm6600-serial
+
+  ttymask:
+    $ref: /schemas/types.yaml#/definitions/uint64
+    description: Mask of the TS 27.010 channel TTY interfaces to start (64 bit)
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: motorola,mapphone-mdm6600-serial
+    then:
+      properties:
+        phys:
+          $ref: /schemas/types.yaml#/definitions/phandle-array
+          description: USB PHY needed for shared GPIO PM wake-up pins
+          maxItems: 1
+
+        phy-names:
+          description: Name of the USB PHY
+          const: usb
+
+        compatible:
+          description: GNSS receiver
+          const: motorola,mapphone-mdm6600-gnss
+
+      required:
+        - phys
+        - phy-names
+
+required:
+  - compatible
+  - ttymask
+  - "#address-cells"
+  - "#size-cells"
+
+examples:
+  - |
+    modem {
+      compatible = "motorola,mapphone-mdm6600-serial";
+      ttymask = <0 0x00001fee>;
+      phys = <&fsusb1_phy>;
+      phy-names = "usb";
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      gnss@4 {
+         compatible = "motorola,mapphone-mdm6600-gnss";
+         reg = <4>;
+      };
+    };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 63996ab03521..c4792d06c30c 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -341,6 +341,8 @@ patternProperties:
     description: Espressif Systems Co. Ltd.
   "^est,.*":
     description: ESTeem Wireless Modems
+  "^etsi,.*":
+    description: ETSI
   "^ettus,.*":
     description: NI Ettus Research
   "^eukrea,.*":
diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
index bd12e3d57baa..9fac72eba726 100644
--- a/drivers/gnss/Kconfig
+++ b/drivers/gnss/Kconfig
@@ -13,6 +13,14 @@ menuconfig GNSS
 
 if GNSS
 
+config GNSS_MOTMDM
+	tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
+	depends on SERIAL_DEV_BUS
+	help
+	  Say Y here if you have a Motorola modem using TS 27.010
+	  multiplexer protocol 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 451f11401ecc..f5afc2c22a3b 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/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 35cf12147e39..7cea101ba8ba 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,14 +51,18 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/gsmmux.h>
+#include <linux/serdev-gsm.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
 
-static int debug;
+static int debug = 8; /* 13 is also useful */
 module_param(debug, int, 0600);
 
 /* Defaults: these are from the specification */
 
-#define T1	10		/* 100mS */
-#define T2	34		/* 333mS */
+#define T1	10		/* 100ms */
+#define T2	34		/* 333ms */
 #define N2	3		/* Retry 3 times */
 
 /* Use long timers for testing at low speed with debug on */
@@ -150,6 +155,7 @@ struct gsm_dlci {
 	/* Data handling callback */
 	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_operations *ops; /* serdev dlci ops, if used */
 	struct net_device *net; /* network interface, if created */
 };
 
@@ -198,6 +204,7 @@ enum gsm_mux_state {
  */
 
 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;
@@ -587,7 +594,8 @@ static void gsm_send(struct gsm_mux *gsm, int addr, int cr, int control)
 		WARN_ON(1);
 		return;
 	}
-	gsm->output(gsm, cbuf, len);
+	if (gsm->output)
+		gsm->output(gsm, cbuf, len);
 	gsm_print_packet("-->", addr, cr, control, NULL, 0);
 }
 
@@ -687,7 +695,7 @@ static void gsm_data_kick(struct gsm_mux *gsm, struct gsm_dlci *dlci)
 			print_hex_dump_bytes("gsm_data_kick: ",
 					     DUMP_PREFIX_OFFSET,
 					     gsm->txframe, len);
-		if (gsm->output(gsm, gsm->txframe, len) < 0)
+		if (gsm->output && gsm->output(gsm, gsm->txframe, len) < 0)
 			break;
 		/* FIXME: Can eliminate one SOF in many more cases */
 		gsm->tx_bytes -= msg->len;
@@ -1015,7 +1023,7 @@ static void gsm_control_reply(struct gsm_mux *gsm, int cmd, const u8 *data,
 static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,
 							u32 modem, int clen)
 {
-	int  mlines = 0;
+	int mlines = 0;
 	u8 brk = 0;
 	int fc;
 
@@ -2339,6 +2347,415 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
 	return 0;
 }
 
+#ifdef CONFIG_SERIAL_DEV_BUS
+/**
+ * gsm_serdev_set_config - set ts 27.010 config
+ * @gsd:	serdev-gsm instance
+ * @c:		ts 27.010 config data
+ */
+int gsm_serdev_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);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_set_config);
+
+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 >= 62)
+		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) {
+		dlci = ERR_PTR(-ENOMEM);
+		goto unlock;
+	}
+
+	gsm->dlci[line] = dlci;
+
+unlock:
+	mutex_unlock(&gsm->mutex);
+
+	return dlci;
+}
+
+static int gsd_dlci_receive_buf(struct gsm_serdev_dlci_operations *ops,
+				const unsigned char *buf,
+				size_t len)
+{
+	struct gsm_serdev *gsd = ops->gsd;
+	struct gsm_dlci *dlci;
+	struct tty_port *port;
+
+	dlci = gsd_dlci_get(gsd, ops->line, false);
+	if (IS_ERR(dlci))
+		return PTR_ERR(dlci);
+
+	port = &dlci->port;
+	tty_insert_flip_string(port, buf, len);
+	tty_flip_buffer_push(port);
+
+	return len;
+}
+
+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;
+	}
+}
+
+/**
+ * gsm_serdev_data_kick - indicate more data can be transmitted
+ * @gsd:	serdev-gsm instance
+ *
+ * See gsm_data_kick() for more information.
+ */
+void gsm_serdev_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, NULL);
+	spin_unlock_irqrestore(&gsm->tx_lock, flags);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_data_kick);
+
+/**
+ * gsm_serdev_register_dlci - register a ts 27.010 channel
+ * @gsd:	serdev-gsm instance
+ * @ops:	channel ops
+ */
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+			     struct gsm_serdev_dlci_operations *ops)
+{
+	struct gsm_dlci *dlci;
+	struct gsm_mux *gsm;
+	int retries;
+
+	if (!gsd || !gsd->gsm || !gsd->serdev)
+		return -ENODEV;
+
+	gsm = gsd->gsm;
+
+	if (!ops || !ops->line)
+		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);
+	ops->gsd = gsd;
+	dlci->ops = ops;
+	dlci->modem_rx = 0;
+	dlci->prev_data = dlci->data;
+	dlci->data = gsd_dlci_data; /* FIXME: do we want this? */
+	mutex_unlock(&dlci->mutex);
+
+	gsm_dlci_begin_open(dlci);
+
+	/*
+	 * Allow some time for dlci to move to DLCI_OPEN state. Otherwise
+	 * the serdev consumer driver can start sending data too early during
+	 * the setup, and the response will be missed by gms_queue() if we
+	 * still have DLCI_CLOSED state.
+	 */
+	for (retries = 10; retries > 0; retries--) {
+		if (dlci->state == DLCI_OPEN)
+			break;
+		msleep(100);
+	}
+
+	if (!retries)
+		dev_dbg(&gsd->serdev->dev, "dlci%i not currently active\n",
+			dlci->addr);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_register_dlci);
+
+/**
+ * gsm_serdev_unregister_dlci - unregister a ts 27.010 channel
+ * @gsd:	serdev-gsm instance
+ * @ops:	channel ops
+ */
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				struct gsm_serdev_dlci_operations *ops)
+{
+	struct gsm_mux *gsm;
+	struct gsm_dlci *dlci;
+
+	if (!gsd || !gsd->gsm || !gsd->serdev)
+		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->gsd = NULL;
+	dlci->ops = NULL;
+	mutex_unlock(&dlci->mutex);
+
+	gsm_dlci_begin_close(dlci);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_unregister_dlci);
+
+static int gsm_serdev_output(struct gsm_mux *gsm, u8 *data, int len)
+{
+	struct serdev_device *serdev = gsm->gsd->serdev;
+
+	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,
+};
+
+extern int motmdm_gnss_attach(struct device *dev, int line);
+
+int gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line)
+{
+	struct gsm_serdev_dlci_operations *ops;
+	unsigned int base;
+	int error;
+	struct device *dev;
+	struct device_node *node;
+	struct device *dev2 = NULL;
+	struct device *ndev;
+		
+	if (line < 1)
+		return -EINVAL;
+
+	ops = kzalloc(sizeof(*ops), GFP_KERNEL);
+	if (!ops)
+		return -ENOMEM;
+
+	ops->line = line;
+	ops->receive_buf = gsd_dlci_receive_buf;
+
+	error = gsm_serdev_register_dlci(gsd, ops);
+	if (error)
+		goto error;
+
+	base = mux_num_to_base(gsd->gsm);
+	printk("register_tty_port: have port: %p, %d+%d\n", &gsd->gsm->dlci[line]->port, base, ops->line);
+	dev = &gsd->serdev->dev;
+
+	for_each_available_child_of_node(dev->of_node, node) {
+		struct platform_device_info devinfo = {};
+		static int idx;
+		struct platform_device *pdev;
+		const char *c = of_get_property(node, "compatible", NULL);
+		int reg;
+		
+		dev_info(dev, "register_tty: child -- %pOF -- compatible %s\n", node, c);
+		
+		if (!c)
+			continue;
+#ifdef OLD		
+		if (strcmp(c, "gsmmux,port"))
+			continue;
+#endif
+		if (of_property_read_u32(node, "reg", &reg)) {
+			printk("no reg property\n");
+			continue;
+		}
+
+		if (reg != line)
+			continue;
+
+		printk("n_gsm: line %d reg is %d, compatible %s\n", line, reg, c);
+		/* Hmm, gnss does not work now? */
+		/* Should we pass the "master" serdev here? */
+#ifndef OLD
+		/* does not work, controller is "BUSY". We already have that in variable "dev" */
+		dev2 = dev;
+#else
+		devinfo.name = kasprintf(GFP_KERNEL, "gsm-mux-%d", idx++);
+		devinfo.parent = dev;
+
+		/* Thanks to core.c: serdev_device_add */
+		pdev = platform_device_register_full(&devinfo);
+		pdev->dev.of_node = node;
+
+		dev2 = &pdev->dev;
+		printk("device name is %s / %s\n", dev2->init_name, dev2->kobj.name);
+#endif
+		break;
+	}
+
+	printk("n_gsm: Registering device at line %d, device is %p\n", line, dev2);
+	ndev = tty_port_register_device_serdev(&gsd->gsm->dlci[line]->port,
+					       gsm_tty_driver, base + ops->line, dev2);
+	if (dev2)
+		printk("device name is now %s / %s\n", dev2->init_name, dev2->kobj.name);
+	
+	printk("register_tty_port: got %p\n", ndev);
+	return 0;
+error:
+	kfree(ops);
+	return error;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_register_tty_port);
+
+void gsm_serdev_unregister_tty_port(struct gsm_serdev *gsd, int line)
+{
+	struct gsm_dlci *dlci;
+	unsigned int base;
+
+	if (line < 1)
+		return;
+
+	dlci = gsd_dlci_get(gsd, line, false);
+	if (IS_ERR(dlci))
+		return;
+
+	printk("unregister_tty_port: line %d\n", line);
+
+	base = mux_num_to_base(gsd->gsm);
+	tty_port_unregister_device(&gsd->gsm->dlci[line]->port, gsm_tty_driver, base + line);
+	gsm_serdev_unregister_dlci(gsd, dlci->ops);
+	kfree(dlci->ops);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_unregister_tty_port);
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+	struct gsm_mux *gsm;
+	int error;
+
+	if (WARN(!gsd || !gsd->serdev,
+		 "serdev 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;
+	gsm->output = gsm_serdev_output;
+	gsd->gsm = gsm;
+	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
@@ -3193,7 +3610,7 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state)
 				    properly */
 		encode = 0x0F;
 	else if (state > 0) {
-		encode = state / 200;	/* mS to encoding */
+		encode = state / 200;	/* ms to encoding */
 		if (encode > 0x0F)
 			encode = 0x0F;	/* Best effort */
 	}
diff --git a/drivers/tty/serdev/Kconfig b/drivers/tty/serdev/Kconfig
index 46ae732bfc68..ee77c3b7329b 100644
--- a/drivers/tty/serdev/Kconfig
+++ b/drivers/tty/serdev/Kconfig
@@ -22,4 +22,14 @@ config SERIAL_DEV_CTRL_TTYPORT
 	depends on SERIAL_DEV_BUS != m
 	default y
 
+config SERIAL_DEV_N_GSM
+	tristate "Serial device TS 27.010 support"
+	depends on N_GSM
+	depends on SERIAL_DEV_CTRL_TTYPORT
+	help
+	  Select this if you want to use the TS 27.010 with a serial port with
+	  devices such as modems and GNSS devices.
+
+	  If unsure, say N.
+
 endif
diff --git a/drivers/tty/serdev/Makefile b/drivers/tty/serdev/Makefile
index 078417e5b068..b44889dc2bc5 100644
--- a/drivers/tty/serdev/Makefile
+++ b/drivers/tty/serdev/Makefile
@@ -4,3 +4,4 @@ serdev-objs := core.o
 obj-$(CONFIG_SERIAL_DEV_BUS) += serdev.o
 
 obj-$(CONFIG_SERIAL_DEV_CTRL_TTYPORT) += serdev-ttyport.o
+obj-$(CONFIG_SERIAL_DEV_N_GSM) += serdev-ngsm.o
diff --git a/drivers/tty/serdev/serdev-ngsm.c b/drivers/tty/serdev/serdev-ngsm.c
new file mode 100644
index 000000000000..488dd504ca23
--- /dev/null
+++ b/drivers/tty/serdev/serdev-ngsm.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic TS 27.010 serial line discipline serdev driver
+ * Copyright (C) 2020 Tony Lindgren <tony@atomide.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.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/serdev.h>
+#include <linux/serdev-gsm.h>
+
+#include <linux/phy/phy.h>
+
+#include <uapi/linux/gsmmux.h>
+
+#define TS27010_C_N2		3	/* TS 27.010 default value */
+#define TS27010_RESERVED_DLCI	(BIT_ULL(63) | BIT_ULL(62) | BIT_ULL(0))
+
+struct serdev_ngsm_cfg {
+	const struct gsm_config *gsm;
+	unsigned int init_retry_quirk:1;
+	unsigned int needs_usb_phy:1;
+	unsigned int aggressive_pm:1;
+	int (*init)(struct serdev_device *serdev); /* for device quirks */
+};
+
+struct serdev_ngsm {
+	struct device *dev;
+	struct gsm_serdev gsd;
+	struct phy *phy;
+	u32 baudrate;
+	DECLARE_BITMAP(ttymask, 64);
+	const struct serdev_ngsm_cfg *cfg;
+};
+
+static int serdev_ngsm_tty_init(struct serdev_ngsm *ddata)
+{
+	struct gsm_serdev *gsd = &ddata->gsd;
+	struct device *dev = ddata->dev;
+	int bit, err;
+
+#if 0
+	for_each_set_bit(bit, ddata->ttymask, 64) {
+		if (bit != 4) continue;
+		if (BIT_ULL(bit) & TS27010_RESERVED_DLCI)
+			continue;
+#endif
+		
+	bit = 4;
+	{
+		printk("Registering port %d\n", bit);
+		err = gsm_serdev_register_tty_port(gsd, bit);
+		if (err) {
+			/* FIXME: unregister already registered ports? */
+			dev_err(dev, "ngsm tty init failed for dlci%i: %i\n",
+				bit, err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static void serdev_ngsm_tty_exit(struct serdev_ngsm *ddata)
+{
+	struct gsm_serdev *gsd = &ddata->gsd;
+	int bit;
+
+#if 0
+	for_each_set_bit(bit, ddata->ttymask, 64) {
+		if (BIT_ULL(bit) & TS27010_RESERVED_DLCI)
+			continue;
+#endif
+		
+	bit = 4;
+	{
+		printk("Unregistering port %d\n", bit);
+		gsm_serdev_unregister_tty_port(gsd, bit);
+	}
+}
+
+static int serdev_ngsm_set_config(struct device *dev)
+{
+	struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+	struct gsm_serdev *gsd = &ddata->gsd;
+	struct gsm_config c;
+	int err, n2;
+
+	memcpy(&c, ddata->cfg->gsm, sizeof(c));
+
+	if (ddata->cfg->init_retry_quirk) {
+		n2 = c.n2;
+		c.n2 *= 10;
+		err = gsm_serdev_set_config(gsd, &c);
+		if (err)
+			return err;
+
+		msleep(5000);
+		c.n2 = n2;
+	}
+
+	err = gsm_serdev_set_config(gsd, &c);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+#if 1
+static int serdev_ngsm_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;
+}
+#endif
+
+static int serdev_ngsm_runtime_suspend(struct device *dev)
+{
+	struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+	int err;
+
+	if (ddata->cfg->needs_usb_phy) {
+		err = phy_pm_runtime_put(ddata->phy);
+		if (err < 0) {
+			dev_warn(dev, "%s: phy_pm_runtime_put: %i\n",
+				 __func__, err);
+
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int serdev_ngsm_runtime_resume(struct device *dev)
+{
+	struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+	int err;
+
+	if (ddata->cfg->needs_usb_phy) {
+		err = phy_pm_runtime_get_sync(ddata->phy);
+		if (err < 0) {
+			dev_warn(dev, "%s: phy_pm_runtime_get: %i\n",
+				 __func__, err);
+
+			return err;
+		}
+	}
+
+	gsm_serdev_data_kick(&ddata->gsd);
+
+	return 0;
+}
+
+static const struct dev_pm_ops serdev_ngsm_pm_ops = {
+	SET_RUNTIME_PM_OPS(serdev_ngsm_runtime_suspend,
+			   serdev_ngsm_runtime_resume,
+			   NULL)
+};
+/*
+ * At least 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 calls
+ * for phy_pm_runtime. Otherwise the modem won't respond to anything on the
+ * UART and will never idle either.
+ */
+static int serdev_ngsm_phy_init(struct device *dev)
+{
+	struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+	int err;
+
+	if (!ddata->cfg->needs_usb_phy)
+		return 0;
+
+	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: phy error: %i\n", __func__, err);
+
+		return err;
+	}
+
+	return 0;
+}
+
+/*
+ * Configure SoC 8250 device for 700 ms autosuspend delay, Values around 600 ms
+ * and shorter cause spurious wake-up events at least on Droid 4. Also keep the
+ * SoC 8250 device active during use because of the OOB GPIO wake-up signaling
+ * shared with USB PHY.
+ */
+static int motmdm_init(struct serdev_device *serdev)
+{
+	pm_runtime_set_autosuspend_delay(serdev->ctrl->dev.parent, 700);
+	pm_suspend_ignore_children(&serdev->ctrl->dev, false);
+
+	return 0;
+}
+
+static const struct gsm_config adaption1 = {
+	.i = 1,			/* 1 = UIH, 2 = UI */
+	.initiator = 1,
+	.encapsulation = 0,	/* basic mode */
+	.adaption = 1,
+	.mru = 1024,		/* from android TS 27010 driver */
+	.mtu = 1024,		/* from android TS 27010 driver */
+	.t1 = 10,		/* ack timer, default 10ms */
+	.t2 = 34,		/* response timer, default 34 */
+	.n2 = 3,		/* retransmissions, default 3 */
+};
+
+static const struct serdev_ngsm_cfg adaption1_cfg = {
+	.gsm = &adaption1,
+};
+
+static const struct serdev_ngsm_cfg motmdm_cfg = {
+	.gsm = &adaption1,
+	.init_retry_quirk = 1,
+	.needs_usb_phy = 1,
+	.aggressive_pm = 1,
+	.init = motmdm_init,
+};
+
+static const struct of_device_id serdev_ngsm_id_table[] = {
+	{
+		.compatible = "etsi,3gpp-ts27010-adaption1",
+		.data = &adaption1_cfg,
+	},
+	{
+		.compatible = "motorola,mapphone-mdm6600-serial",
+		.data = &motmdm_cfg,
+	},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, serdev_ngsm_id_table);
+
+static int serdev_ngsm_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	const struct of_device_id *match;
+	struct gsm_serdev *gsd;
+	struct serdev_ngsm *ddata;
+	u64 ttymask;
+	int err;
+
+	printk("serdev-ngsm: probe\n");
+	
+	match = of_match_device(of_match_ptr(serdev_ngsm_id_table), dev);
+	if (!match)
+		return -ENODEV;
+
+	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+	printk("serdev-ngsm: probe 2\n");
+
+	ddata->dev = dev;
+	ddata->cfg = match->data;
+
+	gsd = &ddata->gsd;
+	gsd->serdev = serdev;
+	gsd->output = serdev_ngsm_output; /* This is real-serial line to gsm direction; 
+					     we want to keep it */
+	serdev_device_set_drvdata(serdev, gsd);
+	gsm_serdev_set_drvdata(dev, ddata);
+
+	err = serdev_ngsm_phy_init(dev);
+	if (err)
+		return err;
+
+	err = of_property_read_u64(dev->of_node, "ttymask", &ttymask);
+	if (err) {
+		dev_err(dev, "invalid or missing ttymask: %i\n", err);
+
+		return err;
+	}
+
+	printk("serdev-ngsm: probe 3\n");
+
+	bitmap_from_u64(ddata->ttymask, ttymask);
+
+	pm_runtime_set_autosuspend_delay(dev, 200);
+	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;
+	}
+	printk("serdev-ngsm: probe 4\n");
+
+	err = gsm_serdev_register_device(gsd);
+	if (err)
+		goto err_disable;
+
+	err = serdev_device_open(gsd->serdev);
+	if (err)
+		goto err_disable;
+
+	/* Optional serial port configuration */
+	of_property_read_u32(dev->of_node->parent, "current-speed",
+			     &ddata->baudrate);
+	if (ddata->baudrate)
+		serdev_device_set_baudrate(gsd->serdev, ddata->baudrate);
+
+	if (of_get_property(dev->of_node->parent, "uart-has-rtscts", NULL)) {
+		serdev_device_set_rts(gsd->serdev, true);
+		serdev_device_set_flow_control(gsd->serdev, true);
+	}
+
+	err = serdev_ngsm_set_config(dev);
+	if (err)
+		goto err_close;
+
+	printk("serdev-ngsm: probe 5\n");
+	
+
+	err = serdev_ngsm_tty_init(ddata);
+	if (err)
+		goto err_tty;
+
+	if (ddata->cfg->init) {
+		err = ddata->cfg->init(serdev);
+		if (err)
+			goto err_tty;
+	}
+
+	err = of_platform_populate(dev->of_node, NULL, NULL, dev);
+	if (err)
+		goto err_tty;
+
+	/* Allow parent serdev device to idle when open, balanced in remove */
+	if (ddata->cfg->aggressive_pm)
+		pm_runtime_put(&serdev->ctrl->dev);
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return 0;
+
+err_tty:
+	serdev_ngsm_tty_exit(ddata);
+
+err_close:
+	serdev_device_close(serdev);
+
+err_disable:
+	pm_runtime_dont_use_autosuspend(dev);
+	pm_runtime_put_sync(dev);
+	pm_runtime_disable(dev);
+	gsm_serdev_unregister_device(gsd);
+
+	return err;
+}
+
+static void serdev_ngsm_remove(struct serdev_device *serdev)
+{
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+	struct device *dev = &serdev->dev;
+	struct serdev_ngsm *ddata;
+	int err;
+
+	ddata = gsm_serdev_get_drvdata(dev);
+
+	/* 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);
+
+	of_platform_depopulate(dev);
+	serdev_ngsm_tty_exit(ddata);
+	serdev_device_close(serdev);
+	gsm_serdev_unregister_device(gsd);
+
+	pm_runtime_dont_use_autosuspend(dev);
+	pm_runtime_put_sync(dev);
+	pm_runtime_disable(dev);
+}
+
+static struct serdev_device_driver serdev_ngsm_driver = {
+	.driver = {
+		.name = "serdev_ngsm",
+		.of_match_table = of_match_ptr(serdev_ngsm_id_table),
+		.pm = &serdev_ngsm_pm_ops,
+	},
+	.probe = serdev_ngsm_probe,
+	.remove = serdev_ngsm_remove,
+};
+
+module_serdev_device_driver(serdev_ngsm_driver);
+
+MODULE_DESCRIPTION("serdev n_gsm driver");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/printk.h b/include/linux/printk.h
index 34c1a7be3e01..ab5ea4b6011d 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -572,6 +572,13 @@ extern int hex_dump_to_buffer(const void *buf, size_t len, int rowsize,
 extern void print_hex_dump(const char *level, const char *prefix_str,
 			   int prefix_type, int rowsize, int groupsize,
 			   const void *buf, size_t len, bool ascii);
+#if defined(CONFIG_DYNAMIC_DEBUG)
+#define print_hex_dump_bytes(prefix_str, prefix_type, buf, len)	\
+	dynamic_hex_dump(prefix_str, prefix_type, 16, 1, buf, len, true)
+#else
+extern void print_hex_dump_bytes(const char *prefix_str, int prefix_type,
+				 const void *buf, size_t len);
+#endif /* defined(CONFIG_DYNAMIC_DEBUG) */
 #else
 static inline void print_hex_dump(const char *level, const char *prefix_str,
 				  int prefix_type, int rowsize, int groupsize,
@@ -604,19 +611,4 @@ static inline void print_hex_dump_debug(const char *prefix_str, int prefix_type,
 }
 #endif
 
-/**
- * print_hex_dump_bytes - shorthand form of print_hex_dump() with default params
- * @prefix_str: string to prefix each line with;
- *  caller supplies trailing spaces for alignment if desired
- * @prefix_type: controls whether prefix of an offset, address, or none
- *  is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE)
- * @buf: data blob to dump
- * @len: number of bytes in the @buf
- *
- * Calls print_hex_dump(), with log level of KERN_DEBUG,
- * rowsize of 16, groupsize of 1, and ASCII output included.
- */
-#define print_hex_dump_bytes(prefix_str, prefix_type, buf, len)	\
-	print_hex_dump_debug(prefix_str, prefix_type, 16, 1, buf, len, true)
-
 #endif
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
new file mode 100644
index 000000000000..5bdc8143b7df
--- /dev/null
+++ b/include/linux/serdev-gsm.h
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_SERDEV_GSM_H
+#define _LINUX_SERDEV_GSM_H
+
+#include <linux/device.h>
+#include <linux/serdev.h>
+#include <linux/types.h>
+
+struct gsm_serdev_dlci_operations;
+struct gsm_config;
+
+/**
+ * struct gsm_serdev - serdev-gsm instance
+ * @serdev:		serdev instance
+ * @gsm:		ts 27.010 n_gsm instance
+ * @drvdata:		serdev-gsm consumer driver data
+ * @output:		read data from ts 27.010 channel
+ *
+ * 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;
+	void *drvdata;
+	int (*output)(struct gsm_serdev *gsd, u8 *data, int len);
+};
+
+/**
+ * struct gsm_serdev_dlci_operations - serdev-gsm ts 27.010 channel data
+ * @gsd:		serdev-gsm instance
+ * @line:		ts 27.010 channel, control channel 0 is not available
+ * @receive_buf:	function to handle data received for the channel
+ * @drvdata:		dlci specific consumer driver data
+ */
+struct gsm_serdev_dlci_operations {
+	struct gsm_serdev *gsd;
+	int line;
+	int (*receive_buf)(struct gsm_serdev_dlci_operations *ops,
+			   const unsigned char *buf,
+			   size_t len);
+	void *drvdata;
+};
+
+#if IS_ENABLED(CONFIG_N_GSM) && IS_ENABLED(CONFIG_SERIAL_DEV_BUS)
+
+/* TS 27.010 channel specific functions for consumer drivers */
+#if IS_ENABLED(CONFIG_SERIAL_DEV_N_GSM)
+extern int
+serdev_ngsm_register_dlci(struct device *dev, struct gsm_serdev_dlci_operations *dlci);
+extern void serdev_ngsm_unregister_dlci(struct device *dev,
+					struct gsm_serdev_dlci_operations *dlci);
+extern int serdev_ngsm_write(struct device *dev, struct gsm_serdev_dlci_operations *ops,
+			     const u8 *buf, int len);
+extern struct gsm_serdev_dlci_operations *
+serdev_ngsm_get_dlci(struct device *dev, int line);
+#endif
+
+/* Interface for_gsm serdev support */
+extern int gsm_serdev_register_device(struct gsm_serdev *gsd);
+extern void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
+extern int gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line);
+extern void gsm_serdev_unregister_tty_port(struct gsm_serdev *gsd, int line);
+extern struct gsm_serdev_dlci_operations *
+gsm_serdev_tty_port_get_dlci(struct gsm_serdev *gsd, int line);
+
+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;
+}
+
+extern int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c);
+extern int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c);
+extern int
+gsm_serdev_register_dlci(struct gsm_serdev *gsd, struct gsm_serdev_dlci_operations *ops);
+extern void
+gsm_serdev_unregister_dlci(struct gsm_serdev *gsd, struct gsm_serdev_dlci_operations *ops);
+extern int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci_operations *ops,
+			    const u8 *buf, int len);
+extern void gsm_serdev_data_kick(struct gsm_serdev *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 int
+gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_tty_port(struct gsm_serdev *gsd, int line)
+{
+}
+
+static inline struct gsm_serdev_dlci_operations *
+gsm_serdev_tty_port_get_dlci(struct gsm_serdev *gsd, int line)
+{
+	return NULL;
+}
+
+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_operations *ops)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				struct gsm_serdev_dlci_operations *ops)
+{
+}
+
+static inline
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci_operations *ops,
+		     const u8 *buf, int len)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+}
+
+#endif	/* CONFIG_N_GSM && CONFIG_SERIAL_DEV_BUS */
+#endif	/* _LINUX_SERDEV_GSM_H */
diff --git a/lib/hexdump.c b/lib/hexdump.c
index 147133f8eb2f..b1d55b669ae2 100644
--- a/lib/hexdump.c
+++ b/lib/hexdump.c
@@ -270,4 +270,25 @@ void print_hex_dump(const char *level, const char *prefix_str, int prefix_type,
 }
 EXPORT_SYMBOL(print_hex_dump);
 
+#if !defined(CONFIG_DYNAMIC_DEBUG)
+/**
+ * print_hex_dump_bytes - shorthand form of print_hex_dump() with default params
+ * @prefix_str: string to prefix each line with;
+ *  caller supplies trailing spaces for alignment if desired
+ * @prefix_type: controls whether prefix of an offset, address, or none
+ *  is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE)
+ * @buf: data blob to dump
+ * @len: number of bytes in the @buf
+ *
+ * Calls print_hex_dump(), with log level of KERN_DEBUG,
+ * rowsize of 16, groupsize of 1, and ASCII output included.
+ */
+void print_hex_dump_bytes(const char *prefix_str, int prefix_type,
+			  const void *buf, size_t len)
+{
+	print_hex_dump(KERN_DEBUG, prefix_str, prefix_type, 16, 1,
+		       buf, len, true);
+}
+EXPORT_SYMBOL(print_hex_dump_bytes);
+#endif /* !defined(CONFIG_DYNAMIC_DEBUG) */
 #endif /* defined(CONFIG_PRINTK) */


-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCHv2] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
  2021-02-28 20:46   ` Pavel Machek
@ 2021-04-01  9:39     ` Johan Hovold
  2021-04-07 10:52       ` Pavel Machek
  0 siblings, 1 reply; 10+ messages in thread
From: Johan Hovold @ 2021-04-01  9:39 UTC (permalink / raw)
  To: Pavel Machek; +Cc: kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 5955 bytes --]

On Sun, Feb 28, 2021 at 09:46:01PM +0100, Pavel Machek wrote:
> Hi!
> 
> > Motorola is using a custom TS 27.010 based multiplexer protocol
> > for various devices on the modem. These devices can be accessed on
> > dedicated channels using Linux kernel serdev-ngsm driver.
> > 
> > For the GNSS on these devices, we need to kick the GNSS device at a
> > desired rate. Otherwise the GNSS device stops sending data after a
> > few minutes. The rate we poll data defaults to 1000 ms, and can be
> > specified with a module option rate_ms between 1 to 16 seconds.
> > 
> > [Tony Lindgren did most of the work here, I just converted it to be
> > normal serdev.]
> 
> Could I get some comments on the gnss patch? It is fairly simple, and
> I believe it is ready for merge.

Let's get the mux driver in shape first.

> Here's new version of the serdev multiplexing patch for reference. And
> yes, I'd like you to review the design here, too. Yes,
> gsm_serdev_register_tty_port() needs to be cleaned up, but with ifdefs
> you can see alternative approach I tried to work around "controller
> busy" problem.

As I said before, if you're trying to work around the one
client-per-port assumption of serdev, you're doing it wrong. You still
have one client per *virtual* port.

> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> Best regards,
> 								Pavel
> 
> diff --git a/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
> new file mode 100644
> index 000000000000..deec491ee821
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml

> +  ttymask:
> +    $ref: /schemas/types.yaml#/definitions/uint64
> +    description: Mask of the TS 27.010 channel TTY interfaces to start (64 bit)

How is this intended to be used? Looks too Linux-specific for a binding.

> +examples:
> +  - |
> +    modem {
> +      compatible = "motorola,mapphone-mdm6600-serial";
> +      ttymask = <0 0x00001fee>;
> +      phys = <&fsusb1_phy>;
> +      phy-names = "usb";
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +
> +      gnss@4 {
> +         compatible = "motorola,mapphone-mdm6600-gnss";
> +         reg = <4>;
> +      };
> +    };

So is this is probably the issue: you're skipping one level of the
topology and do not describe the virtual ports.

This should instead be something like below; I'm including one level
above for the full picture:

	serial@nnn {
		compatible = "ns16550a";
		reg = <nnn>;

		modem {
			compatible = "ts27010-mux, motorola,mapphone-mdm6600-serial";

			serial@4 {
				reg = <4>

				gnss {
					compatible = "motorola,mapphone-mdm6600-gnss";
				};
			};
		};
	};

Here serial@nnn is a normal serial port, modem is the modem (TS 27.010
mux) connected to the serial port, serial@4 is virtual port number four
of the mux, and gnss is the gnss device connected to the virtual port.

You should probably just replace the ttymask property with explicit
nodes for the virtual ports to enable. With no client devices described
in devicetree, these will then show up as regular tty devices under
Linux.

If you add a compatible property for the virtual ports (e.g.
"ts27010-port") you might be able to just use of_platform_populate()
instead of walking the child nodes in gsm_serdev_register_tty_port().

> +int gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line)
> +{
> +	struct gsm_serdev_dlci_operations *ops;
> +	unsigned int base;
> +	int error;
> +	struct device *dev;
> +	struct device_node *node;
> +	struct device *dev2 = NULL;
> +	struct device *ndev;
> +		
> +	if (line < 1)
> +		return -EINVAL;
> +
> +	ops = kzalloc(sizeof(*ops), GFP_KERNEL);
> +	if (!ops)
> +		return -ENOMEM;
> +
> +	ops->line = line;
> +	ops->receive_buf = gsd_dlci_receive_buf;
> +
> +	error = gsm_serdev_register_dlci(gsd, ops);
> +	if (error)
> +		goto error;
> +
> +	base = mux_num_to_base(gsd->gsm);
> +	printk("register_tty_port: have port: %p, %d+%d\n", &gsd->gsm->dlci[line]->port, base, ops->line);
> +	dev = &gsd->serdev->dev;
> +
> +	for_each_available_child_of_node(dev->of_node, node) {
> +		struct platform_device_info devinfo = {};
> +		static int idx;
> +		struct platform_device *pdev;
> +		const char *c = of_get_property(node, "compatible", NULL);
> +		int reg;
> +		
> +		dev_info(dev, "register_tty: child -- %pOF -- compatible %s\n", node, c);
> +		
> +		if (!c)
> +			continue;
> +#ifdef OLD		
> +		if (strcmp(c, "gsmmux,port"))
> +			continue;
> +#endif
> +		if (of_property_read_u32(node, "reg", &reg)) {
> +			printk("no reg property\n");
> +			continue;
> +		}
> +
> +		if (reg != line)
> +			continue;
> +
> +		printk("n_gsm: line %d reg is %d, compatible %s\n", line, reg, c);
> +		/* Hmm, gnss does not work now? */
> +		/* Should we pass the "master" serdev here? */
> +#ifndef OLD
> +		/* does not work, controller is "BUSY". We already have that in variable "dev" */
> +		dev2 = dev;
> +#else
> +		devinfo.name = kasprintf(GFP_KERNEL, "gsm-mux-%d", idx++);
> +		devinfo.parent = dev;
> +
> +		/* Thanks to core.c: serdev_device_add */
> +		pdev = platform_device_register_full(&devinfo);
> +		pdev->dev.of_node = node;
> +
> +		dev2 = &pdev->dev;
> +		printk("device name is %s / %s\n", dev2->init_name, dev2->kobj.name);
> +#endif
> +		break;
> +	}
> +
> +	printk("n_gsm: Registering device at line %d, device is %p\n", line, dev2);
> +	ndev = tty_port_register_device_serdev(&gsd->gsm->dlci[line]->port,
> +					       gsm_tty_driver, base + ops->line, dev2);
> +	if (dev2)
> +		printk("device name is now %s / %s\n", dev2->init_name, dev2->kobj.name);
> +	
> +	printk("register_tty_port: got %p\n", ndev);
> +	return 0;
> +error:
> +	kfree(ops);
> +	return error;
> +}
> +EXPORT_SYMBOL_GPL(gsm_serdev_register_tty_port);

Johan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCHv2] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
  2021-04-01  9:39     ` Johan Hovold
@ 2021-04-07 10:52       ` Pavel Machek
  0 siblings, 0 replies; 10+ messages in thread
From: Pavel Machek @ 2021-04-07 10:52 UTC (permalink / raw)
  To: Johan Hovold; +Cc: kernel list, phone-devel, tony

[-- Attachment #1: Type: text/plain, Size: 2639 bytes --]

Hi!

> > Could I get some comments on the gnss patch? It is fairly simple, and
> > I believe it is ready for merge.
> 
> Let's get the mux driver in shape first.

Well, gnss driver is now completely independent of that in the new
versions.

> > Here's new version of the serdev multiplexing patch for reference. And
> > yes, I'd like you to review the design here, too. Yes,
> > gsm_serdev_register_tty_port() needs to be cleaned up, but with ifdefs
> > you can see alternative approach I tried to work around "controller
> > busy" problem.
> 
> As I said before, if you're trying to work around the one
> client-per-port assumption of serdev, you're doing it wrong. You still
> have one client per *virtual* port.

Yes, I have one client per virtual port. But those virtual ports need
their parents.

We can have

serial@ -- motorola,mapphone-mdm6600-serial
(serdev here)
modem -- ts27010-mux
(here is the problem!)
serial@4 --                                      serial@1 -- 
(serdev here)                                    (serdev here)
gnss -- motorola,mapphone-mdm6600-gnss           modem voice -- something

and I believe we agree on that. But serial@1 and serial@4 still need
some kind of parent, and that's where I'm getting the controller busy
problem.

> > +++ b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
> 
> > +  ttymask:
> > +    $ref: /schemas/types.yaml#/definitions/uint64
> > +    description: Mask of the TS 27.010 channel TTY interfaces to start (64 bit)
> 
> How is this intended to be used? Looks too Linux-specific for a binding.

The mask says which virtual ports contain "something". We have kernel
clients for some, and we want to expose the rest of the used ones to
the userspace.

> So is this is probably the issue: you're skipping one level of the
> topology and do not describe the virtual ports.

Well, docs need to be updated.

> You should probably just replace the ttymask property with explicit
> nodes for the virtual ports to enable. With no client devices described
> in devicetree, these will then show up as regular tty devices under
> Linux.

Yes, that can be done.

> If you add a compatible property for the virtual ports (e.g.
> "ts27010-port") you might be able to just use of_platform_populate()
> instead of walking the child nodes in gsm_serdev_register_tty_port().

I have "gsmmux,port" for that. And yes, I can take a look at
of_platform_populate.

But as I explained above, there's still problem with the parent
devices.

Best regards,
								Pavel
-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2021-04-07 10:53 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-01-07 22:45 [PATCH] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem Pavel Machek
2021-01-28 18:34 ` Pavel Machek
     [not found] ` <YBQvvUitX4MtRrh+@hovoldconsulting.com>
2021-01-29 22:34   ` Pavel Machek
     [not found]   ` <20210131170639.GA21067@duo.ucw.cz>
2021-02-10 21:28     ` [RFC/context] add serdev interfaces to n_gsm Pavel Machek
2021-02-11  9:04       ` Johan Hovold
2021-02-19 22:06         ` Pavel Machek
2021-01-29 22:42 ` [PATCHv2] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem Pavel Machek
2021-02-28 20:46   ` Pavel Machek
2021-04-01  9:39     ` Johan Hovold
2021-04-07 10:52       ` Pavel Machek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).