Linux-OMAP Archive on lore.kernel.org
 help / color / Atom feed
* [PATCHv5 0/4] n_gsm serdev support and protocol driver for droid4 modem
@ 2020-03-19 17:37 Tony Lindgren
  2020-03-19 17:37 ` [PATCH 1/4] tty: n_gsm: Add support for serdev drivers Tony Lindgren
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Tony Lindgren @ 2020-03-19 17:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring
  Cc: Alan Cox, Lee Jones, Jiri Slaby, Johan Hovold, Merlijn Wajer,
	Pavel Machek, Peter Hurley, Sebastian Reichel, linux-serial,
	devicetree, linux-kernel, linux-omap

Hi all,

Here's v4 set of n_gsm serdev support patches, and the related protocol
driver for the modem found on Motorola Mapphone phones and tablets
like droid4.

This series only adds basic character device support for the serdev
driver. Other serdev consumer drivers for specific devices will be
posted separately.

The patches are against v5.6-rc series.

Regards,

Tony

Changes since v4:
- Use drivers/tty/serdev/protocol directory for the driver instead of
  drivers/mfd as discussed on the lists for v3 set of patches
- Fix remove to call kfree only after removing device from the list

Changes since v3:
- Update list of folks in Cc, looks like I sent v3 only to Lee and lkml
- Init privdata before motmdm_register_dlci calls gsm_serdev_register_dlci
- Update binding based on Rob's comments for license and "allOf"

Changes since v2:
- Drop useless send_command indirection, use static motmdm_send_command

Changes since v1:

- Simplified usage and got rid of few pointless inline functions
- Added consumer MFD driver, devicetree binding, and dts changes


Tony Lindgren (4):
  tty: n_gsm: Add support for serdev drivers
  serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for
    droid4
  dt-bindings: mfd: motmdm: Add binding for motorola-mdm
  ARM: dts: omap4-droid4: Enable basic modem support

 .../serdev/motorola,mapphone-mdm6600.yaml     |   34 +
 .../boot/dts/motorola-mapphone-common.dtsi    |    6 +
 drivers/tty/n_gsm.c                           |  372 +++++
 drivers/tty/serdev/Kconfig                    |    3 +
 drivers/tty/serdev/Makefile                   |    2 +
 drivers/tty/serdev/protocol/Kconfig           |   14 +
 drivers/tty/serdev/protocol/Makefile          |    3 +
 .../tty/serdev/protocol/serdev-ngsm-motmdm.c  | 1200 +++++++++++++++++
 include/linux/serdev-gsm.h                    |  168 +++
 9 files changed, 1802 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml
 create mode 100644 drivers/tty/serdev/protocol/Kconfig
 create mode 100644 drivers/tty/serdev/protocol/Makefile
 create mode 100644 drivers/tty/serdev/protocol/serdev-ngsm-motmdm.c
 create mode 100644 include/linux/serdev-gsm.h

-- 
2.25.1

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

* [PATCH 1/4] tty: n_gsm: Add support for serdev drivers
  2020-03-19 17:37 [PATCHv5 0/4] n_gsm serdev support and protocol driver for droid4 modem Tony Lindgren
@ 2020-03-19 17:37 ` Tony Lindgren
  2020-03-22 22:42   ` Droid 4 in -next -- still no backlight was " Pavel Machek
  2020-03-19 17:37 ` [PATCH 2/4] serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for droid4 Tony Lindgren
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 14+ messages in thread
From: Tony Lindgren @ 2020-03-19 17:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring
  Cc: Alan Cox, Lee Jones, Jiri Slaby, Johan Hovold, Merlijn Wajer,
	Pavel Machek, Peter Hurley, Sebastian Reichel, linux-serial,
	devicetree, linux-kernel, linux-omap

We can make use of serdev drivers to do simple device drivers for
TS 27.010 chanels, and we can handle vendor specific protocols on top
of TS 27.010 with serdev drivers.

So far this has been tested with Motorola droid4 where there is a custom
packet numbering protocol on top of TS 27.010 for the MDM6600 modem.

I initially though about adding the serdev support into a separate file,
but that will take some refactoring of n_gsm.c. And I'd like to have
things working first. Then later on we might want to consider splitting
n_gsm.c into three pieces for core, tty and serdev parts. And then maybe
the serdev related parts can be just moved to live under something like
drivers/tty/serdev/protocol/ngsm.c.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/tty/n_gsm.c        | 372 +++++++++++++++++++++++++++++++++++++
 include/linux/serdev-gsm.h | 168 +++++++++++++++++
 2 files changed, 540 insertions(+)
 create mode 100644 include/linux/serdev-gsm.h

diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -39,6 +39,7 @@
 #include <linux/file.h>
 #include <linux/uaccess.h>
 #include <linux/module.h>
+#include <linux/serdev.h>
 #include <linux/timer.h>
 #include <linux/tty_flip.h>
 #include <linux/tty_driver.h>
@@ -50,6 +51,7 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/gsmmux.h>
+#include <linux/serdev-gsm.h>
 
 static int debug;
 module_param(debug, int, 0600);
@@ -145,6 +147,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 *ops; /* serdev dlci ops, if used */
 	struct net_device *net; /* network interface, if created */
 };
 
@@ -179,6 +182,7 @@ struct gsm_control {
  */
 
 struct gsm_mux {
+	struct gsm_serdev *gsd;		/* Serial device bus data */
 	struct tty_struct *tty;		/* The tty our ldisc is bound to */
 	spinlock_t lock;
 	struct mutex mutex;
@@ -2326,6 +2330,374 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
 	return 0;
 }
 
+#ifdef CONFIG_SERIAL_DEV_BUS
+
+/**
+ * gsm_serdev_get_config - read ts 27.010 config
+ * @gsd:	serdev-gsm instance
+ * @c:		ts 27.010 config data
+ *
+ * See gsm_copy_config_values() for more information.
+ */
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	struct gsm_mux *gsm;
+
+	if (!gsd || !gsd->gsm)
+		return -ENODEV;
+
+	gsm = gsd->gsm;
+
+	if (!c)
+		return -EINVAL;
+
+	gsm_copy_config_values(gsm, c);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_get_config);
+
+/**
+ * gsm_serdev_set_config - set ts 27.010 config
+ * @gsd:	serdev-gsm instance
+ * @c:		ts 27.010 config data
+ *
+ * See gsm_config() for more information.
+ */
+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 >= 63)
+		return ERR_PTR(-EINVAL);
+
+	mutex_lock(&gsm->mutex);
+
+	if (gsm->dlci[line]) {
+		dlci = gsm->dlci[line];
+		goto unlock;
+	} else if (!allocate) {
+		dlci = ERR_PTR(-ENODEV);
+		goto unlock;
+	}
+
+	dlci = gsm_dlci_alloc(gsm, line);
+	if (!dlci) {
+		gsm = ERR_PTR(-ENOMEM);
+		goto unlock;
+	}
+
+	gsm->dlci[line] = dlci;
+
+unlock:
+	mutex_unlock(&gsm->mutex);
+
+	return dlci;
+}
+
+static void gsd_dlci_data(struct gsm_dlci *dlci, const u8 *buf, int len)
+{
+	struct gsm_mux *gsm = dlci->gsm;
+	struct gsm_serdev *gsd = gsm->gsd;
+
+	if (!gsd || !dlci->ops)
+		return;
+
+	switch (dlci->adaption) {
+	case 0:
+	case 1:
+		if (dlci->ops->receive_buf)
+			dlci->ops->receive_buf(dlci->ops, buf, len);
+		break;
+	default:
+		pr_warn("dlci%i adaption %i not yet implemented\n",
+			dlci->addr, dlci->adaption);
+		break;
+	}
+}
+
+/**
+ * gsm_serdev_write - write data to a ts 27.010 channel
+ * @gsd:	serdev-gsm instance
+ * @ops:	channel ops
+ * @buf:	write buffer
+ * @len:	buffer length
+ */
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+		     const u8 *buf, int len)
+{
+	struct gsm_mux *gsm;
+	struct gsm_dlci *dlci;
+	struct gsm_msg *msg;
+	int h, size, total_size = 0;
+	u8 *dp;
+
+	if (!gsd || !gsd->gsm)
+		return -ENODEV;
+
+	gsm = gsd->gsm;
+
+	dlci = gsd_dlci_get(gsd, ops->line, false);
+	if (IS_ERR(dlci))
+		return PTR_ERR(dlci);
+
+	h = dlci->adaption - 1;
+
+	if (len > gsm->mtu)
+		len = gsm->mtu;
+
+	size = len + h;
+
+	msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+	if (!msg)
+		return -ENOMEM;
+
+	dp = msg->data;
+	switch (dlci->adaption) {
+	case 1:
+		break;
+	case 2:
+		*dp++ = gsm_encode_modem(dlci);
+		break;
+	}
+	memcpy(dp, buf, len);
+	gsm_data_queue(dlci, msg);
+	total_size += size;
+
+	return total_size;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_write);
+
+/**
+ * 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);
+	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 *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 || !ops->receive_buf)
+		return -EINVAL;
+
+	dlci = gsd_dlci_get(gsd, ops->line, true);
+	if (IS_ERR(dlci))
+		return PTR_ERR(dlci);
+
+	if (dlci->state == DLCI_OPENING || dlci->state == DLCI_OPEN ||
+	    dlci->state == DLCI_CLOSING)
+		return -EBUSY;
+
+	mutex_lock(&dlci->mutex);
+	dlci->ops = ops;
+	dlci->modem_rx = 0;
+	dlci->prev_data = dlci->data;
+	dlci->data = gsd_dlci_data;
+	mutex_unlock(&dlci->mutex);
+
+	gsm_dlci_begin_open(dlci);
+
+	/*
+	 * 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 *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 = 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 gsm_serdev *gsd = gsm->gsd;
+	struct serdev_device *serdev = gsm->gsd->serdev;
+	bool asleep;
+
+	asleep = atomic_read(&gsd->asleep);
+	if (asleep)
+		return -ENOSPC;
+
+	if (gsm->gsd->output)
+		return gsm->gsd->output(gsm->gsd, data, len);
+	else
+		return serdev_device_write_buf(serdev, data, len);
+}
+
+static int gsd_receive_buf(struct serdev_device *serdev, const u8 *data,
+			   size_t count)
+{
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+	struct gsm_mux *gsm;
+	const unsigned char *dp;
+	int i;
+
+	if (WARN_ON(!gsd))
+		return 0;
+
+	gsm = gsd->gsm;
+
+	if (debug & 4)
+		print_hex_dump_bytes("gsd_receive_buf: ",
+				     DUMP_PREFIX_OFFSET,
+				     data, count);
+
+	for (i = count, dp = data; i; i--, dp++)
+		gsm->receive(gsm, *dp);
+
+	return count;
+}
+
+static void gsd_write_wakeup(struct serdev_device *serdev)
+{
+	serdev_device_write_wakeup(serdev);
+}
+
+static struct serdev_device_ops gsd_client_ops = {
+	.receive_buf = gsd_receive_buf,
+	.write_wakeup = gsd_write_wakeup,
+};
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+	struct gsm_mux *gsm;
+	int error;
+
+	if (WARN(!gsd || !gsd->serdev || !gsd->output,
+		 "serdev and output must be initialized\n"))
+		return -EINVAL;
+
+	serdev_device_set_client_ops(gsd->serdev, &gsd_client_ops);
+
+	gsm = gsm_alloc_mux();
+	if (!gsm)
+		return -ENOMEM;
+
+	gsm->encoding = 1;
+	gsm->tty = NULL;
+	gsm->gsd = gsd;
+	gsm->output = gsm_serdev_output;
+	atomic_set(&gsd->asleep, 0);
+	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
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
new file mode 100644
--- /dev/null
+++ b/include/linux/serdev-gsm.h
@@ -0,0 +1,168 @@
+/* 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;
+struct gsm_config;
+
+/**
+ * struct gsm_serdev - serdev-gsm instance
+ * @serdev:		serdev instance
+ * @gsm:		ts 27.010 n_gsm instance
+ * @asleep:		device is in idle state
+ * @drvdata:		serdev-gsm consumer driver data
+ * @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;
+	atomic_t asleep;
+	void *drvdata;
+	int (*output)(struct gsm_serdev *gsd, u8 *data, int len);
+};
+
+/**
+ * struct gsm_serdev_dlci - serdev-gsm ts 27.010 channel data
+ * @line:		ts 27.010 channel, control channel 0 is not available
+ * @receive_buf:	function to handle data received for the channel
+ */
+struct gsm_serdev_dlci {
+	int line;
+	int (*receive_buf)(struct gsm_serdev_dlci *ops,
+			   const unsigned char *buf,
+			   size_t len);
+};
+
+#if IS_ENABLED(CONFIG_N_GSM) && IS_ENABLED(CONFIG_SERIAL_DEV_BUS)
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd);
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+	if (gsd)
+		return gsd->drvdata;
+
+	return NULL;
+}
+
+static inline void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+	if (gsd)
+		gsd->drvdata = drvdata;
+}
+
+static inline void gsm_serdev_suspend(struct gsm_serdev *gsd)
+{
+	if (!gsd)
+		return;
+
+	atomic_set(&gsd->asleep, 1);
+}
+
+static inline int gsm_serdev_resume(struct gsm_serdev *gsd)
+{
+	if (!gsd)
+		return -EINVAL;
+
+	atomic_set(&gsd->asleep, 0);
+
+	return 0;
+}
+
+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 *ops);
+extern void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				       struct gsm_serdev_dlci *ops);
+extern int gsm_serdev_write(struct gsm_serdev *gsd,
+			    struct gsm_serdev_dlci *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 void *gsm_serdev_get_drvdata(struct device *dev)
+{
+	return NULL;
+}
+
+static inline
+void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+}
+
+static inline void gsm_serdev_suspend(struct gsm_serdev *gsd)
+{
+}
+
+static inline int gsm_serdev_resume(struct gsm_serdev *gsd)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+			     struct gsm_serdev_dlci *ops)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				struct gsm_serdev_dlci *ops)
+{
+}
+
+static inline
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+		     const u8 *buf, int len)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+}
+
+#endif	/* CONFIG_N_GSM && CONFIG_SERIAL_DEV_BUS */
+#endif	/* _LINUX_SERDEV_GSM_H */
-- 
2.25.1

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

* [PATCH 2/4] serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for droid4
  2020-03-19 17:37 [PATCHv5 0/4] n_gsm serdev support and protocol driver for droid4 modem Tony Lindgren
  2020-03-19 17:37 ` [PATCH 1/4] tty: n_gsm: Add support for serdev drivers Tony Lindgren
@ 2020-03-19 17:37 ` Tony Lindgren
  2020-03-22 22:09   ` Pavel Machek
  2020-03-19 17:37 ` [PATCH 3/4] dt-bindings: mfd: motmdm: Add binding for motorola-mdm Tony Lindgren
  2020-03-19 17:37 ` [PATCH 4/4] ARM: dts: omap4-droid4: Enable basic modem support Tony Lindgren
  3 siblings, 1 reply; 14+ messages in thread
From: Tony Lindgren @ 2020-03-19 17:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring
  Cc: Alan Cox, Lee Jones, Jiri Slaby, Johan Hovold, Merlijn Wajer,
	Pavel Machek, Peter Hurley, Sebastian Reichel, linux-serial,
	devicetree, linux-kernel, linux-omap

Many Motorola phones are controlling the modem using a custom variant
of TS 27.010 serial line discipline. Devices on these modems have a
dedicated TS 27.010 channel for features like audio mixer, GNSS, voice
modem, SIM card reader and so on.

This serdev protocol driver allows using various devices on the modem.
In order to do that, we need to take care of the following three things:

1. Provide /dev/motmdm* character devices for apps to use for talking
   to the various devices on the modem

2. Handle Motorola custom protocol over TS 27.010 to make the channels
   usable for userspace

3. Coordinate PM runtime with the USB PHY because of shared GPIO pins
   with the USB PHY

With this patch, folks with droid4 can place a voice call with just:

$ printf "ATD%s,0\r" "${phone_number}" > /dev/motmdm1
D:OK
~+CIEV=1,1,0
...
$ printf "ATH\r" > /dev/motmdm1
H:OK

Also SMS can be sent with this patch using /dev/motmdm3 for sending,
and /dev/motmdm9 for receiving messages, and /dev/motmdm10 can be
used for SIM access.

Note that the audio mixer needs additional patches though. I will be
sending those as a separate series of patches.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/tty/serdev/Kconfig                    |    3 +
 drivers/tty/serdev/Makefile                   |    2 +
 drivers/tty/serdev/protocol/Kconfig           |   14 +
 drivers/tty/serdev/protocol/Makefile          |    3 +
 .../tty/serdev/protocol/serdev-ngsm-motmdm.c  | 1200 +++++++++++++++++
 5 files changed, 1222 insertions(+)
 create mode 100644 drivers/tty/serdev/protocol/Kconfig
 create mode 100644 drivers/tty/serdev/protocol/Makefile
 create mode 100644 drivers/tty/serdev/protocol/serdev-ngsm-motmdm.c

diff --git a/drivers/tty/serdev/Kconfig b/drivers/tty/serdev/Kconfig
--- a/drivers/tty/serdev/Kconfig
+++ b/drivers/tty/serdev/Kconfig
@@ -22,4 +22,7 @@ config SERIAL_DEV_CTRL_TTYPORT
 	depends on SERIAL_DEV_BUS != m
 	default y
 
+source "drivers/tty/serdev/protocol/Kconfig"
+
 endif
+
diff --git a/drivers/tty/serdev/Makefile b/drivers/tty/serdev/Makefile
--- a/drivers/tty/serdev/Makefile
+++ b/drivers/tty/serdev/Makefile
@@ -4,3 +4,5 @@ serdev-objs := core.o
 obj-$(CONFIG_SERIAL_DEV_BUS) += serdev.o
 
 obj-$(CONFIG_SERIAL_DEV_CTRL_TTYPORT) += serdev-ttyport.o
+
+obj-y += protocol/
diff --git a/drivers/tty/serdev/protocol/Kconfig b/drivers/tty/serdev/protocol/Kconfig
new file mode 100644
--- /dev/null
+++ b/drivers/tty/serdev/protocol/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Serial bus device driver protocol specific configuration
+#
+
+config SERIAL_DEV_NGSM_MOTMDM
+	tristate "Motorola Modem TS 27.010 Serdev Protocol Driver"
+	depends on N_GSM && PHY_MAPPHONE_MDM6600
+	help
+	  Select this for Motorola modems using TS 27.010 serial line
+	  discipline such as MDM6600 modem found on Motorola Mapphone
+	  devices like Droid4
+
+	  If unsure, say N.
diff --git a/drivers/tty/serdev/protocol/Makefile b/drivers/tty/serdev/protocol/Makefile
new file mode 100644
--- /dev/null
+++ b/drivers/tty/serdev/protocol/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_SERIAL_DEV_NGSM_MOTMDM) += serdev-ngsm-motmdm.o
diff --git a/drivers/tty/serdev/protocol/serdev-ngsm-motmdm.c b/drivers/tty/serdev/protocol/serdev-ngsm-motmdm.c
new file mode 100644
--- /dev/null
+++ b/drivers/tty/serdev/protocol/serdev-ngsm-motmdm.c
@@ -0,0 +1,1200 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola TS 27.010 serial line discipline serdev driver
+ * Copyright (C) 2018 - 2020 Tony Lindgren <tony@atomide.com>
+ */
+
+#include <linux/cdev.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/serdev.h>
+#include <linux/serdev-gsm.h>
+
+#include <linux/mfd/core.h>
+#include <linux/phy/phy.h>
+
+#include <uapi/linux/gsmmux.h>
+
+#define MOTMDM_C_N2		3	/* TS27.010 default value */
+#define MOTMDM_DLCI_MIN		1
+#define MOTMDM_DLCI_MAX		12
+#define MOTMDM_DLCI_MASK	0x1ffe	/* DLCI from 1 to 12 */
+#define MOTMDM_ID_LEN		5	/* U + unsigned short */
+#define MOTMDM_CMD_LEN(x)	(MOTMDM_ID_LEN + (x) + 1)
+#define MOTMDM_WRITE_BUF_SIZE	1024
+#define MOTMDM_READ_FIFO_SIZE	4096
+
+struct motmdm_cfg {
+	unsigned long cdevmask;
+	unsigned int aggressive_pm:1;
+	int modem_dlci;
+	int codec_dlci;
+};
+
+struct motmdm {
+	struct device *dev;
+	struct phy *phy;
+	struct gsm_serdev gsd;
+	const struct motmdm_cfg *cfg;
+	struct class *class;
+	struct list_head dlcis;
+	struct list_head cdevs;
+	dev_t dev_id;
+	u16 cmdid;
+};
+
+struct motmdm_response {
+	struct list_head node;
+	u16 id;
+	const unsigned char *cmd;
+	size_t cmdlen;
+	size_t reslen;
+	unsigned char *buf;
+	size_t len;
+	unsigned int handled:1;
+};
+
+enum motmdm_dlci_nr {
+	MOTMDM_DLCI1 = 1,
+	MOTMDM_DLCI2,
+	MOTMDM_DLCI3,
+	MOTMDM_DLCI4,
+	MOTMDM_DLCI5,
+	MOTMDM_DLCI6,
+	MOTMDM_DLCI7,
+	MOTMDM_DLCI8,
+	MOTMDM_DLCI9,
+	MOTMDM_DLCI10,
+	MOTMDM_DLCI11,
+	MOTMDM_DLCI12,
+	MOTMDM_DLCI13,
+	MOTMDM_DLCI14,
+	MOTMDM_DLCI15,
+};
+
+struct motmdm_dlci {
+	struct gsm_serdev_dlci gsm_dlci;
+	struct list_head node;
+	wait_queue_head_t read_queue;
+	struct kfifo read_fifo;
+	int line;
+	u16 id;
+	struct list_head list;
+	void *privdata;		/* Do not use, internal data */
+	void *drvdata;		/* Available for consumer drivers */
+};
+
+struct motmdm_cdev {
+	struct motmdm *ddata;
+	struct list_head node;
+	struct motmdm_dlci *dlci;
+	struct device *dev;
+	struct cdev cdev;
+	struct rw_semaphore rwsem;
+	unsigned int count;
+	unsigned int disconnected:1;
+	struct mutex read_mutex;	/* char dev write lock */
+	struct mutex write_mutex;	/* char dev read lock */
+	char *write_buf;
+	size_t write_buf_sz;
+	size_t write_offset;
+};
+
+static const char * const motmdm_driver_name = "motmdm";
+
+static int motmdm_runtime_suspend(struct device *dev)
+{
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	int err;
+
+	if (IS_ERR_OR_NULL(ddata->phy))
+		return 0;
+
+	err = phy_pm_runtime_put(ddata->phy);
+	if (err < 0)
+		dev_warn(dev, "%s: phy_pm_runtime_put: %i\n",
+			 __func__, err);
+
+	gsm_serdev_resume(&ddata->gsd);
+
+	return 0;
+}
+
+static int motmdm_runtime_resume(struct device *dev)
+{
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	int err;
+
+	if (IS_ERR_OR_NULL(ddata->phy))
+		return 0;
+
+	err = phy_pm_runtime_get_sync(ddata->phy);
+	if (err < 0) {
+		dev_warn(dev, "%s: phy_pm_runtime_get: %i\n",
+			 __func__, err);
+
+		return err;
+	}
+
+	err = gsm_serdev_resume(&ddata->gsd);
+	if (err < 0) {
+		phy_pm_runtime_put(ddata->phy);
+
+		return err;
+	}
+
+	gsm_serdev_data_kick(&ddata->gsd);
+
+	return 0;
+}
+
+static const struct dev_pm_ops motmdm_pm_ops = {
+	SET_RUNTIME_PM_OPS(motmdm_runtime_suspend,
+			   motmdm_runtime_resume,
+			   NULL)
+};
+
+/*
+ * Motorola MDM6600 devices have GPIO wake pins shared between the USB PHY and
+ * the TS 27.010 interface. So for PM, we need to use the phy_pm_runtime
+ * related calls. Otherwise the modem won't respond to anything on the UART
+ * and will never idle either.
+ */
+static int motmdm_init_phy(struct device *dev)
+{
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	int err;
+
+	ddata->phy = devm_of_phy_get(dev, dev->of_node, NULL);
+	if (IS_ERR(ddata->phy)) {
+		err = PTR_ERR(ddata->phy);
+		if (err != -EPROBE_DEFER)
+			dev_err(dev, "%s: no phy: %i\n", __func__, err);
+
+		return err;
+	}
+
+	return 0;
+}
+
+/*
+ * Motorola MDM6600 devices add a custom packet numbering layer on top of the
+ * TS 27.010 DLCI channels. This is a layering violation as all the devices
+ * are on dedicated channels already. For some reason the packet numbering is
+ * not specific to each DLCI.. It is for all the DLCI instead. As both ends
+ * can increase packet IDs, conflicts are guaranteed but seem to be harmless.
+ * Who knows, maybe the packet IDs were added by frustrated developers to
+ * debug buggy modem code reponding on a wrong DLCI. Valid packet numbers are
+ * from 0000 to 9999 decimal. We just parse the modem sent packet number to
+ * match the response to sent commands and don't use modem sent packet
+ * numbers for new command packets we send out.
+ */
+static int motmdm_read_packet_id(struct gsm_serdev_dlci *gsm_dlci,
+				 const unsigned char *buf,
+				 size_t len)
+{
+	struct motmdm_dlci *mot_dlci =
+		container_of(gsm_dlci, struct motmdm_dlci,
+			     gsm_dlci);
+	struct motmdm *ddata = mot_dlci->privdata;
+	unsigned char tmp[MOTMDM_ID_LEN];
+	int err;
+	u16 id;
+
+	if (WARN(!ddata, "%s no ddata?\n", __func__))
+		return 0;
+
+	if (len < MOTMDM_ID_LEN || buf[0] != 'U')
+		return -ECOMM;
+
+	snprintf(tmp, MOTMDM_ID_LEN, "%s", buf + 1);
+	err = kstrtou16(tmp, 10, &id);
+	if (err)
+		return -ECOMM;
+
+	return id;
+}
+
+/*
+ * For new packets, we just use jiffies based numbering and let the modem
+ * deal with any possible numbering conflicts across the DLCI.
+ */
+static int motmdm_new_packet_id(void)
+{
+	return jiffies % 10000;
+}
+
+/* Fix line breaks for apps if needed and feed kfifo */
+static int motmdm_dlci_feed_kfifo(struct motmdm_dlci *mot_dlci,
+				  const unsigned char *buf,
+				  size_t len)
+{
+	size_t newlen = len;
+	int err;
+
+	if (len && buf[len - 1] == '\n') {
+		if (len > 1 && buf[len - 2] != '\r')
+			newlen--;
+		else if (len == 1)
+			newlen--;
+	}
+
+	err = kfifo_in(&mot_dlci->read_fifo, buf, newlen);
+	if (err != newlen)
+		return -ENOSPC;
+
+	if (newlen != len) {
+		err = kfifo_in(&mot_dlci->read_fifo, "\r\n", 2);
+		if (err != 2)
+			err = -ENOSPC;
+		else
+			newlen += err;
+	}
+
+	return newlen;
+}
+
+/*
+ * Helper to parse the command response from modem
+ */
+static int motmdm_dlci_handle_command(struct motmdm_dlci *mot_dlci, int id,
+				      const unsigned char *buf, size_t len)
+{
+	struct motmdm_response *resp = NULL;
+	struct list_head *pos, *q;
+	int resp_start;
+
+	list_for_each_safe(pos, q, &mot_dlci->list) {
+		resp = list_entry(pos, struct motmdm_response, node);
+		if (resp->id == id)
+			break;
+	}
+
+	if (!resp || !resp->buf)
+		return -ENODEV;
+
+	/* Firmware leaves out AT from the commands */
+	resp_start = resp->cmdlen - 2;
+	if (len < resp_start)
+		return -EPIPE;
+
+	/* Only some firmware messages start with ':' */
+	if (buf[resp_start] == ':')
+		resp_start++;
+
+	resp->reslen = min3(len - resp_start, resp->len, len);
+	strncpy(resp->buf, buf + resp_start, resp->reslen);
+
+	/* Leave out trailing line break if there */
+	if (resp->reslen > 1 && resp->buf[resp->reslen - 1] == '\n') {
+		resp->buf[resp->reslen - 1] = '\0';
+		resp->reslen--;
+	}
+
+	resp->handled = true;
+
+	return 0;
+}
+
+/*
+ * Read handling for Motorola custom layering on top of TS 27.010
+ */
+static int motmdm_dlci_receive_buf(struct gsm_serdev_dlci *gsm_dlci,
+				   const unsigned char *buf,
+				   size_t len)
+{
+	struct motmdm_dlci *mot_dlci =
+		container_of(gsm_dlci, struct motmdm_dlci,
+			     gsm_dlci);
+	const unsigned char *msg;
+	size_t msglen;
+	int id, err;
+
+	if (len < (MOTMDM_ID_LEN + 1) || buf[0] != 'U')
+		return 0;
+
+	id = motmdm_read_packet_id(gsm_dlci, buf, len);
+	if (id < 0)
+		return 0;
+
+	/* Strip out Motorola custom packet numbering */
+	msg = buf + MOTMDM_ID_LEN;
+	msglen = len - MOTMDM_ID_LEN;
+
+	motmdm_dlci_handle_command(mot_dlci, id, msg, msglen);
+
+	if (kfifo_initialized(&mot_dlci->read_fifo)) {
+		err = motmdm_dlci_feed_kfifo(mot_dlci, msg, msglen);
+		if (err < 0)
+			goto err_kfifo;
+	}
+
+	err = msglen;
+
+	wake_up(&mot_dlci->read_queue);
+
+err_kfifo:
+
+	return err;
+}
+
+/*
+ * Write handling for Motorola custom layering on top of TS 27.010
+ */
+static int motmdm_write(struct device *dev, struct motmdm_dlci *mot_dlci,
+			int cmdid, const unsigned char *buf, size_t count)
+{
+	struct motmdm *ddata;
+	struct gsm_serdev *gsd;
+	int err, cmdlen;
+	char *cmd;
+
+	if (!dev || !mot_dlci || !buf || !count)
+		return -EINVAL;
+
+	ddata = gsm_serdev_get_drvdata(dev);
+	if (!ddata)
+		return -ENODEV;
+
+	gsd = &ddata->gsd;
+
+	err = pm_runtime_get_sync(dev);
+	if ((err != -EINPROGRESS) && err < 0) {
+		pm_runtime_put_noidle(dev);
+
+		return err;
+	}
+
+	cmdlen = MOTMDM_CMD_LEN(count);
+	cmd = kmalloc(cmdlen, GFP_KERNEL);
+	if (!cmd)
+		return -ENOMEM;
+
+	switch (cmdid) {
+	case -ENOENT:
+		/* No ID number, just U for continuation messages */
+		snprintf(cmd, cmdlen, "U%s\r", buf);
+		break;
+	case 0 ... 9999:
+		/* Valid ID */
+		mot_dlci->id = cmdid;
+		snprintf(cmd, cmdlen, "U%04i%s\r", mot_dlci->id, buf);
+		break;
+	default:
+		/* Assign ID */
+		mot_dlci->id = motmdm_new_packet_id();
+		snprintf(cmd, cmdlen, "U%04i%s\r", mot_dlci->id, buf);
+		break;
+	}
+
+	err = gsm_serdev_write(gsd, &mot_dlci->gsm_dlci, cmd, cmdlen);
+	if (err == cmdlen)
+		err = count;
+
+	kfree(cmd);
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return err;
+}
+
+/*
+ * Helper for child device drivers to send a command to a DLCI and wait
+ * for result with a matching packet ID.
+ */
+static int motmdm_send_command(struct device *dev,
+			       struct motmdm_dlci *mot_dlci,
+			       unsigned long timeout_ms,
+			       const unsigned char *cmd, size_t cmdlen,
+			       unsigned char *rsp, size_t rsplen)
+{
+	struct motmdm_response *resp, *tmp;
+	struct list_head *pos, *q;
+	unsigned char *delim;
+	int err;
+
+	resp = kzalloc(sizeof(*resp), GFP_KERNEL);
+	if (!resp)
+		return -ENOMEM;
+
+	memset(rsp, 0, rsplen);
+
+	resp->cmd = cmd;
+	resp->cmdlen = cmdlen;
+	resp->buf = rsp;
+	resp->len = rsplen;
+	resp->id = motmdm_new_packet_id();
+
+	/* Firmware will return only the command without value */
+	if (cmd[cmdlen - 1] != '=') {
+		delim = strchr(cmd, '=');
+		if (delim)
+			resp->cmdlen -= strlen(delim);
+	}
+
+	list_add_tail(&resp->node, &mot_dlci->list);
+
+	err = motmdm_write(dev, mot_dlci, resp->id, cmd, cmdlen);
+	if (err < 0)
+		goto unregister;
+
+	err = wait_event_timeout(mot_dlci->read_queue, resp->handled,
+				 msecs_to_jiffies(timeout_ms));
+	if (err < 0)
+		goto unregister;
+
+	if (err == 0) {
+		err = -ETIMEDOUT;
+		goto unregister;
+	}
+
+	dev_dbg(dev, "%s: %s got %s\n", __func__, cmd, resp->buf);
+
+	err = resp->reslen;
+
+unregister:
+	list_for_each_safe(pos, q, &mot_dlci->list) {
+		tmp = list_entry(pos, struct motmdm_response, node);
+		if (tmp->id == resp->id)
+			list_del(pos);
+	}
+
+	kfree(resp);
+
+	return err;
+}
+
+static int motmdm_register_dlci(struct device *dev,
+				struct motmdm_dlci *mot_dlci)
+{
+	struct motmdm *ddata;
+	struct gsm_serdev *gsd;
+	struct gsm_serdev_dlci *gsm_dlci;
+	int err;
+
+	if (!dev || !mot_dlci || !mot_dlci->line)
+		return -EINVAL;
+
+	err = pm_runtime_get_sync(dev);
+	if ((err != -EINPROGRESS) && err < 0) {
+		pm_runtime_put_noidle(dev);
+
+		return err;
+	}
+
+	ddata = gsm_serdev_get_drvdata(dev);
+	gsd = &ddata->gsd;
+	INIT_LIST_HEAD(&mot_dlci->list);
+	init_waitqueue_head(&mot_dlci->read_queue);
+	mot_dlci->privdata = ddata;
+
+	gsm_dlci = &mot_dlci->gsm_dlci;
+	gsm_dlci->line = mot_dlci->line;
+	gsm_dlci->receive_buf = motmdm_dlci_receive_buf;
+
+	err = gsm_serdev_register_dlci(gsd, gsm_dlci);
+	if (err) {
+		dev_warn(dev, "error registering dlci%i: %i\n",
+			 mot_dlci->line, err);
+		kfifo_free(&mot_dlci->read_fifo);
+		memset(gsm_dlci, 0, sizeof(*gsm_dlci));
+		goto out_idle;
+	}
+
+	list_add_tail(&mot_dlci->node, &ddata->dlcis);
+
+out_idle:
+	pm_runtime_put(dev);
+
+	return err;
+}
+
+static void motmdm_unregister_dlci(struct device *dev,
+				   struct motmdm_dlci *mot_dlci)
+{
+	struct motmdm *ddata;
+	struct gsm_serdev *gsd;
+	struct gsm_serdev_dlci *gsm_dlci;
+	struct list_head *pos, *q;
+	struct motmdm_dlci *tmp;
+	int err;
+
+	if (!dev || !mot_dlci || !mot_dlci->line)
+		return;
+
+	err = pm_runtime_get_sync(dev);
+	if ((err != -EINPROGRESS) && err < 0) {
+		pm_runtime_put_noidle(dev);
+		return;
+	}
+
+	ddata = gsm_serdev_get_drvdata(dev);
+
+	list_for_each_safe(pos, q, &ddata->dlcis) {
+		tmp = list_entry(pos, struct motmdm_dlci, node);
+		if (tmp == mot_dlci)
+			list_del(pos);
+	}
+
+	gsd = &ddata->gsd;
+	gsm_dlci = &mot_dlci->gsm_dlci;
+	gsm_serdev_unregister_dlci(gsd, gsm_dlci);
+	gsm_dlci->receive_buf = NULL;
+	mot_dlci->privdata = NULL;
+
+	pm_runtime_put(dev);
+}
+
+/*
+ * Character devices for DLCI channels with no serdev drivers
+ */
+static int motmdm_cdev_open(struct inode *inode, struct file *file)
+{
+	struct motmdm_cdev *cdata;
+	int ret = 0;
+
+	cdata = container_of(inode->i_cdev, struct motmdm_cdev, cdev);
+	get_device(cdata->dev);
+	nonseekable_open(inode, file);
+	file->private_data = cdata;
+
+	down_write(&cdata->rwsem);
+	if (cdata->disconnected) {
+		ret = -ENODEV;
+		goto unlock;
+	}
+
+unlock:
+	up_write(&cdata->rwsem);
+
+	if (ret)
+		put_device(cdata->dev);
+
+	return ret;
+}
+
+static int motmdm_cdev_release(struct inode *inode, struct file *file)
+{
+	struct motmdm_cdev *cdata = file->private_data;
+
+	down_write(&cdata->rwsem);
+	if (cdata->disconnected)
+		goto unlock;
+
+unlock:
+	up_write(&cdata->rwsem);
+
+	put_device(cdata->dev);
+
+	return 0;
+}
+
+static ssize_t motmdm_cdev_read(struct file *file, char __user *buf,
+				size_t count, loff_t *pos)
+{
+	struct motmdm_cdev *cdata = file->private_data;
+	struct motmdm_dlci *mot_dlci = cdata->dlci;
+	unsigned int copied;
+	int err;
+
+	mutex_lock(&cdata->read_mutex);
+	while (kfifo_is_empty(&mot_dlci->read_fifo)) {
+		mutex_unlock(&cdata->read_mutex);
+
+		if (cdata->disconnected)
+			return 0;
+
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		err = wait_event_interruptible(mot_dlci->read_queue,
+				cdata->disconnected ||
+				!kfifo_is_empty(&mot_dlci->read_fifo));
+		if (err)
+			return -ERESTARTSYS;
+
+		mutex_lock(&cdata->read_mutex);
+	}
+
+	err = kfifo_to_user(&mot_dlci->read_fifo, buf, count, &copied);
+	if (err == 0)
+		err = copied;
+
+	mutex_unlock(&cdata->read_mutex);
+
+	return err;
+}
+
+static ssize_t motmdm_cdev_write_packet(struct motmdm_cdev *cdata, int cmdid)
+{
+	struct motmdm_dlci *mot_dlci = cdata->dlci;
+	struct motmdm *ddata = mot_dlci->privdata;
+
+	return motmdm_write(ddata->dev, mot_dlci, cmdid,
+			    cdata->write_buf, cdata->write_offset - 1);
+}
+
+static ssize_t motmdm_cdev_write(struct file *file, const char __user *buf,
+				 size_t count, loff_t *pos)
+{
+	struct motmdm_cdev *cdata = file->private_data;
+	size_t written = 0;
+	int err, flag = -ENOMSG;
+
+	if (cdata->disconnected)
+		return -EIO;
+
+	if (!count)
+		return 0;
+
+	err = mutex_lock_interruptible(&cdata->write_mutex);
+	if (err)
+		return -ERESTARTSYS;
+
+	for (;;) {
+		size_t n;
+		bool packet = false;
+
+		n = min(count, cdata->write_buf_sz -
+			cdata->write_offset - count);
+		if (copy_from_user(cdata->write_buf + cdata->write_offset,
+				   buf, n)) {
+			err = -EFAULT;
+			goto out_unlock;
+		}
+
+		cdata->write_offset += n;
+		cdata->write_buf[cdata->write_offset] = '\0';
+		if (cdata->write_offset) {
+			u8 last = cdata->write_buf[cdata->write_offset - 1];
+
+			switch (last) {
+			case 0x1a:	/* Continuation packets end with ^Z */
+				flag = -ENOENT;
+				/* Fallthrough */
+			case '\n':
+			case '\r':
+				packet = true;
+				break;
+			default:
+				break;
+			}
+		}
+
+		down_read(&cdata->rwsem);
+
+		if (cdata->disconnected) {
+			err = -EIO;
+			goto err_write;
+		}
+
+		if (packet) {
+			err = motmdm_cdev_write_packet(cdata, flag);
+			if (err < 0)
+				goto err_write;
+
+			cdata->write_offset = 0;
+		}
+
+		err = n;
+
+err_write:
+		up_read(&cdata->rwsem);
+
+		if (err < 0)
+			break;
+
+		written += err;
+		buf += err;
+
+		if (written == count)
+			break;
+	}
+
+	if (written)
+		err = written;
+
+out_unlock:
+	mutex_unlock(&cdata->write_mutex);
+
+	return err;
+}
+
+static __poll_t motmdm_cdev_poll(struct file *file, poll_table *wait)
+{
+	struct motmdm_cdev *cdata = file->private_data;
+	struct motmdm_dlci *mot_dlci = cdata->dlci;
+	__poll_t mask = 0;
+
+	poll_wait(file, &mot_dlci->read_queue, wait);
+
+	if (!kfifo_is_empty(&mot_dlci->read_fifo))
+		mask |= EPOLLIN | EPOLLRDNORM;
+	if (cdata->write_offset < cdata->write_buf_sz)
+		mask |= EPOLLOUT | EPOLLWRNORM;
+	if (cdata->disconnected)
+		mask |= EPOLLHUP;
+
+	return mask;
+}
+
+static const struct file_operations motmdm_fops = {
+	.owner		= THIS_MODULE,
+	.open		= motmdm_cdev_open,
+	.release	= motmdm_cdev_release,
+	.read		= motmdm_cdev_read,
+	.write		= motmdm_cdev_write,
+	.poll		= motmdm_cdev_poll,
+	.llseek		= no_llseek,
+};
+
+static int motmdm_cdev_init_one(struct motmdm *ddata, int index)
+{
+	struct motmdm_cdev *cdata;
+	struct motmdm_dlci *mot_dlci;
+	int err;
+
+	mot_dlci = kzalloc(sizeof(*mot_dlci), GFP_KERNEL);
+	if (!mot_dlci)
+		return -ENOMEM;
+
+	mot_dlci->drvdata = ddata;
+	mot_dlci->line = index;
+
+	err = kfifo_alloc(&mot_dlci->read_fifo, MOTMDM_READ_FIFO_SIZE,
+			  GFP_KERNEL);
+	if (err)
+		goto err_free_dlci;
+
+	cdata = kzalloc(sizeof(*cdata), GFP_KERNEL);
+	if (!cdata)
+		goto err_free_kfifo;
+
+	cdata->dlci = mot_dlci;
+	init_rwsem(&cdata->rwsem);
+	mutex_init(&cdata->read_mutex);
+	mutex_init(&cdata->write_mutex);
+	cdata->write_buf_sz = MOTMDM_WRITE_BUF_SIZE;
+	cdata->write_buf = kzalloc(cdata->write_buf_sz, GFP_KERNEL);
+	if (!cdata->write_buf)
+		goto err_free_cdata;
+
+	err = motmdm_register_dlci(ddata->dev, cdata->dlci);
+	if (err)
+		goto err_free_write_buf;
+
+	cdata->dev = device_create(ddata->class, ddata->dev,
+				   MKDEV(MAJOR(ddata->dev_id), index),
+				   mot_dlci, "%s%i", motmdm_driver_name,
+				   index);
+	if (IS_ERR(cdata->dev)) {
+		err = PTR_ERR(cdata->dev);
+		goto err_unregister;
+	}
+
+	cdev_init(&cdata->cdev, &motmdm_fops);
+	err = cdev_add(&cdata->cdev, MKDEV(MAJOR(ddata->dev_id), index),
+		       MOTMDM_DLCI_MAX);
+	if (err)
+		goto err_device_destroy;
+
+	list_add_tail(&cdata->node, &ddata->cdevs);
+
+	return 0;
+
+err_device_destroy:
+	device_destroy(ddata->class, MKDEV(MAJOR(ddata->dev_id), index));
+err_unregister:
+	motmdm_unregister_dlci(ddata->dev, cdata->dlci);
+err_free_write_buf:
+	kfree(cdata->write_buf);
+err_free_cdata:
+	kfree(cdata);
+err_free_kfifo:
+	kfifo_free(&mot_dlci->read_fifo);
+err_free_dlci:
+	kfree(mot_dlci);
+
+	return err;
+}
+
+static void motmdm_cdev_cleanup(struct device *dev);
+
+static int motmdm_cdev_init(struct device *dev)
+{
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	int bit, err;
+
+	err = alloc_chrdev_region(&ddata->dev_id, 0, MOTMDM_DLCI_MAX,
+				  motmdm_driver_name);
+	if (err)
+		return err;
+
+	ddata->class = class_create(THIS_MODULE, motmdm_driver_name);
+	if (IS_ERR(ddata->class)) {
+		err = PTR_ERR(ddata->class);
+		motmdm_cdev_cleanup(dev);
+
+		return err;
+	}
+
+	for_each_set_bit(bit, &ddata->cfg->cdevmask, BITS_PER_LONG) {
+		err = motmdm_cdev_init_one(ddata, bit);
+		if (err) {
+			motmdm_cdev_cleanup(dev);
+
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static void motmdm_cdev_free_one(struct motmdm_cdev *cdata)
+{
+	struct motmdm_dlci *mot_dlci = cdata->dlci;
+	struct motmdm *ddata = mot_dlci->privdata;
+
+	down_write(&cdata->rwsem);
+	cdata->disconnected = true;
+	if (cdata->count)
+		wake_up(&mot_dlci->read_queue);
+	up_write(&cdata->rwsem);
+
+	cdev_del(&cdata->cdev);
+	device_destroy(ddata->class,
+		       MKDEV(MAJOR(ddata->dev_id), mot_dlci->line));
+	kfree(cdata->write_buf);
+
+	motmdm_unregister_dlci(ddata->dev, cdata->dlci);
+	kfifo_free(&mot_dlci->read_fifo);
+	kfree(cdata->dlci);
+	kfree(cdata);
+}
+
+static void motmdm_cdev_cleanup(struct device *dev)
+{
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	struct motmdm_cdev *cdata, *tmp;
+
+	list_for_each_entry_safe(cdata, tmp, &ddata->cdevs, node) {
+		list_del(&cdata->node);
+		motmdm_cdev_free_one(cdata);
+	}
+
+	class_destroy(ddata->class);
+
+	unregister_chrdev_region(ddata->dev_id, MOTMDM_DLCI_MAX);
+}
+
+static int motmdm_check_revision(struct device *dev)
+{
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	struct motmdm_dlci *mot_dlci;
+	const unsigned char *cmd = "AT+VERSION=";
+	unsigned char *buf;
+	int retries = 3, err;
+
+	mot_dlci = kzalloc(sizeof(*mot_dlci), GFP_KERNEL);
+	if (!mot_dlci)
+		return -ENOMEM;
+
+	mot_dlci->drvdata = ddata;
+	mot_dlci->line = MOTMDM_DLCI6;
+
+	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf) {
+		err = -ENOMEM;
+		goto free_dlci;
+	}
+
+	err = motmdm_register_dlci(dev, mot_dlci);
+	if (err)
+		goto free_buf;
+
+	while (retries--) {
+		err = motmdm_send_command(dev, mot_dlci, 1000, cmd, strlen(cmd),
+					  buf, PAGE_SIZE);
+		if (err >= 0) {
+			msleep(100);
+			break;
+		}
+
+		msleep(500);
+	}
+
+	if (err < 0) {
+		dev_err(dev, "Could not connect: %i\n", err);
+	} else if (!strncmp(buf, "ERROR", 5)) {
+		dev_err(dev, "Firmware error: %s\n", buf);
+		err = -ENODEV;
+	} else {
+		dev_info(dev, "Firmware: %s\n", buf);
+		err = 0;
+	}
+
+	motmdm_unregister_dlci(dev, mot_dlci);
+
+free_buf:
+	kfree(buf);
+free_dlci:
+	kfree(mot_dlci);
+
+	return err;
+}
+
+static int motmdm_set_config(struct device *dev, int retransmissions)
+{
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	struct gsm_serdev *gsd = &ddata->gsd;
+	struct gsm_config c;
+	int err;
+
+	err = gsm_serdev_get_config(gsd, &c);
+	if (err)
+		return err;
+
+	c.i = 1;		/* 1 = UIH, 2 = UI */
+	c.initiator = 1;
+	c.encapsulation = 0;	/* basic mode */
+	c.adaption = 1;
+	c.mru = 1024;		/* from android TS 27010 driver */
+	c.mtu = 1024;		/* from android TS 27010 driver */
+	c.t1 = 10;		/* ack timer, default 10ms */
+	c.t2 = 34;		/* response timer, default 34 */
+	c.n2 = retransmissions;	/* retransmissions, default 3 */
+
+	err = gsm_serdev_set_config(gsd, &c);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int motmdm_output(struct gsm_serdev *gsd, u8 *data, int len)
+{
+	struct serdev_device *serdev = gsd->serdev;
+	struct device *dev = &serdev->dev;
+	int err;
+
+	err = pm_runtime_get(dev);
+	if ((err != -EINPROGRESS) && err < 0) {
+		pm_runtime_put_noidle(dev);
+
+		return err;
+	}
+
+	serdev_device_write_buf(serdev, data, len);
+
+	pm_runtime_put(dev);
+
+	return len;
+}
+
+static const struct motmdm_cfg mapphone_mdm6600_data = {
+	.cdevmask = MOTMDM_DLCI_MASK & ~(BIT(MOTMDM_DLCI2) | BIT(MOTMDM_DLCI4)),
+	.aggressive_pm = true,
+	.modem_dlci = MOTMDM_DLCI1,
+	.codec_dlci = MOTMDM_DLCI2,
+};
+
+static const struct of_device_id motmdm_id_table[] = {
+	{
+		.compatible = "motorola,mapphone-mdm6600-serial",
+		.data = &mapphone_mdm6600_data,
+	},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, motmdm_id_table);
+
+static const struct mfd_cell motmdm_mfd_devices[] = {
+	{
+		.name = "mot-mdm6600-codec",
+	},
+	{
+		.name = "gnss-mot-mdm6600",
+		.of_compatible = "motorola,mapphone-mdm6600-gnss",
+	},
+};
+
+static int motmdm_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	const struct of_device_id *match;
+	struct gsm_serdev *gsd;
+	struct motmdm *ddata;
+	int err;
+
+	match = of_match_device(of_match_ptr(motmdm_id_table), dev);
+	if (!match)
+		return -ENODEV;
+
+	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	ddata->dev = dev;
+	ddata->cfg = match->data;
+
+	INIT_LIST_HEAD(&ddata->dlcis);
+	INIT_LIST_HEAD(&ddata->cdevs);
+	gsd = &ddata->gsd;
+	gsd->serdev = serdev;
+	gsd->output = motmdm_output;
+	serdev_device_set_drvdata(serdev, gsd);
+	gsm_serdev_set_drvdata(dev, ddata);
+
+	err = motmdm_init_phy(dev);
+	if (err)
+		return err;
+
+	pm_runtime_set_autosuspend_delay(dev, 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;
+	}
+
+	err = gsm_serdev_register_device(gsd);
+	if (err)
+		goto disable;
+
+	err = serdev_device_open(gsd->serdev);
+	if (err)
+		goto disable;
+
+	serdev_device_set_baudrate(gsd->serdev, 115200);
+	serdev_device_set_rts(gsd->serdev, true);
+	serdev_device_set_flow_control(gsd->serdev, true);
+
+	/*
+	 * Getting dlci0 connected quirk: We set initial retransmissions
+	 * value high to get n_gsm to send SABM packets. Then after about
+	 * three seconds we'll get a "reassembly overrun" error from firmware
+	 * on dlci0 followed by DM(P) packets and then we're connected in ADM
+	 * mode. Note we will set the retransmissions back to default value
+	 * later on.
+	 */
+	err = motmdm_set_config(dev, MOTMDM_C_N2 * 10);
+	if (err)
+		goto close;
+
+	msleep(3000);
+
+	err = motmdm_check_revision(dev);
+	if (err)
+		goto close;
+
+	msleep(500);
+
+	err = motmdm_cdev_init(dev);
+	if (err)
+		goto close;
+
+	pm_runtime_put_sync(dev);
+
+	err = devm_mfd_add_devices(dev, 0, motmdm_mfd_devices,
+				   ARRAY_SIZE(motmdm_mfd_devices),
+				   NULL, 0, NULL);
+	if (err)
+		goto cdev_cleanup;
+
+	/* Set initial retransmissions back to default value */
+	err = motmdm_set_config(dev, MOTMDM_C_N2);
+	if (err)
+		goto cdev_cleanup;
+
+	if (ddata->cfg->aggressive_pm) {
+		/*
+		 * 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.
+		 */
+		pm_runtime_set_autosuspend_delay(serdev->ctrl->dev.parent, 700);
+
+		/*
+		 * Allow parent serdev device to idle when open, balanced in
+		 * remove
+		 */
+		pm_runtime_put(&serdev->ctrl->dev);
+
+		/*
+		 * Keep parent SoC 8250 device active during use because of the
+		 * OOB GPIO wake-up signaling shared with USB PHY.
+		 */
+		pm_suspend_ignore_children(&serdev->ctrl->dev, false);
+	}
+
+	return 0;
+
+cdev_cleanup:
+	motmdm_cdev_cleanup(dev);
+
+close:
+	serdev_device_close(serdev);
+
+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 motmdm_remove(struct serdev_device *serdev)
+{
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+	struct device *dev = &serdev->dev;
+	struct motmdm *ddata = gsm_serdev_get_drvdata(dev);
+	int err;
+
+	/* Balance the put done in probe for UART */
+	if (ddata->cfg->aggressive_pm)
+		pm_runtime_get(&serdev->ctrl->dev);
+
+	err = pm_runtime_get_sync(dev);
+	if (err < 0)
+		dev_warn(dev, "%s: PM runtime: %i\n", __func__, err);
+
+	motmdm_cdev_cleanup(dev);
+	serdev_device_close(serdev);
+	gsm_serdev_unregister_device(gsd);
+
+	pm_runtime_dont_use_autosuspend(dev);
+	pm_runtime_put_sync(dev);
+	pm_runtime_disable(dev);
+}
+
+static struct serdev_device_driver motmdm_driver = {
+	.driver = {
+		.name = "motmdm",
+		.of_match_table = of_match_ptr(motmdm_id_table),
+		.pm = &motmdm_pm_ops,
+	},
+	.probe = motmdm_probe,
+	.remove = motmdm_remove,
+};
+
+module_serdev_device_driver(motmdm_driver);
+
+MODULE_ALIAS("platform:motorola-mdm");
+MODULE_DESCRIPTION("Motorola Modem TS 27.010 serdev driver");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com");
+MODULE_LICENSE("GPL v2");
-- 
2.25.1

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

* [PATCH 3/4] dt-bindings: mfd: motmdm: Add binding for motorola-mdm
  2020-03-19 17:37 [PATCHv5 0/4] n_gsm serdev support and protocol driver for droid4 modem Tony Lindgren
  2020-03-19 17:37 ` [PATCH 1/4] tty: n_gsm: Add support for serdev drivers Tony Lindgren
  2020-03-19 17:37 ` [PATCH 2/4] serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for droid4 Tony Lindgren
@ 2020-03-19 17:37 ` Tony Lindgren
  2020-03-24 11:58   ` Lee Jones
  2020-03-19 17:37 ` [PATCH 4/4] ARM: dts: omap4-droid4: Enable basic modem support Tony Lindgren
  3 siblings, 1 reply; 14+ messages in thread
From: Tony Lindgren @ 2020-03-19 17:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring
  Cc: Alan Cox, Lee Jones, Jiri Slaby, Johan Hovold, Merlijn Wajer,
	Pavel Machek, Peter Hurley, Sebastian Reichel, linux-serial,
	devicetree, linux-kernel, linux-omap

Add a binding document for Motorola modems controllable by
TS 27.010 UART line discipline using serdev drivers.

Reviewed-by: Rob Herring <robh@kernel.org>
[tony@atomide.com: moved to live under bindings/serdev]
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 .../serdev/motorola,mapphone-mdm6600.yaml     | 34 +++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml

diff --git a/Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml b/Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml
new file mode 100644
--- /dev/null
+++ b/Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serdev/motorola,mapphone-mdm6600.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Motorola Mapphone MDM6600 Modem
+
+maintainers:
+  - Tony Lindgren <tony@atomide.com>
+
+properties:
+  compatible:
+    items:
+      - const: motorola,mapphone-mdm6600-serial
+
+  phys:
+    maxItems: 1
+
+  phy-names:
+    const: usb
+
+required:
+  - compatible
+  - phys
+  - phy-names
+
+examples:
+  - |
+    modem {
+        compatible = "motorola,mapphone-mdm6600-serial";
+        phys = <&fsusb1_phy>;
+        phy-names = "usb";
+    };
-- 
2.25.1

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

* [PATCH 4/4] ARM: dts: omap4-droid4: Enable basic modem support
  2020-03-19 17:37 [PATCHv5 0/4] n_gsm serdev support and protocol driver for droid4 modem Tony Lindgren
                   ` (2 preceding siblings ...)
  2020-03-19 17:37 ` [PATCH 3/4] dt-bindings: mfd: motmdm: Add binding for motorola-mdm Tony Lindgren
@ 2020-03-19 17:37 ` Tony Lindgren
  3 siblings, 0 replies; 14+ messages in thread
From: Tony Lindgren @ 2020-03-19 17:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rob Herring
  Cc: Alan Cox, Lee Jones, Jiri Slaby, Johan Hovold, Merlijn Wajer,
	Pavel Machek, Peter Hurley, Sebastian Reichel, linux-serial,
	devicetree, linux-kernel, linux-omap

This allows apps to use /dev/motmdm1 for voice call AT commands,
/dev/motmdm3 for sending SMS, and /dev/motmdm9 for reading SMS.

Voice call audio mixer and GNSS are not yet supported.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 arch/arm/boot/dts/motorola-mapphone-common.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm/boot/dts/motorola-mapphone-common.dtsi b/arch/arm/boot/dts/motorola-mapphone-common.dtsi
--- a/arch/arm/boot/dts/motorola-mapphone-common.dtsi
+++ b/arch/arm/boot/dts/motorola-mapphone-common.dtsi
@@ -698,6 +698,12 @@ &uart1 {
 	pinctrl-0 = <&uart1_pins>;
 	interrupts-extended = <&wakeupgen GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH
 			       &omap4_pmx_core 0xfc>;
+
+	modem {
+		compatible = "motorola,mapphone-mdm6600-serial";
+		phys = <&fsusb1_phy>;
+		phy-names = "usb";
+	};
 };
 
 &uart3 {
-- 
2.25.1

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

* Re: [PATCH 2/4] serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for droid4
  2020-03-19 17:37 ` [PATCH 2/4] serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for droid4 Tony Lindgren
@ 2020-03-22 22:09   ` Pavel Machek
  2020-03-24 16:34     ` Tony Lindgren
  0 siblings, 1 reply; 14+ messages in thread
From: Pavel Machek @ 2020-03-22 22:09 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: Greg Kroah-Hartman, Rob Herring, Alan Cox, Lee Jones, Jiri Slaby,
	Johan Hovold, Merlijn Wajer, Peter Hurley, Sebastian Reichel,
	linux-serial, devicetree, linux-kernel, linux-omap

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

Hi!

> Many Motorola phones are controlling the modem using a custom variant
> of TS 27.010 serial line discipline. Devices on these modems have a
> dedicated TS 27.010 channel for features like audio mixer, GNSS, voice
> modem, SIM card reader and so on.

I get warning here while applying:

Applying: serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem
driver for droid4
.git/rebase-apply/patch:22: new blank line at EOF.
+

Best regards,
								Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

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

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

* Droid 4 in -next -- still no backlight was Re: [PATCH 1/4] tty: n_gsm: Add support for serdev drivers
  2020-03-19 17:37 ` [PATCH 1/4] tty: n_gsm: Add support for serdev drivers Tony Lindgren
@ 2020-03-22 22:42   ` " Pavel Machek
  2020-03-24 17:01     ` Tony Lindgren
  0 siblings, 1 reply; 14+ messages in thread
From: Pavel Machek @ 2020-03-22 22:42 UTC (permalink / raw)
  To: Tony Lindgren, kernel list, linux-arm-kernel, linux-omap, sre,
	nekit1000, mpartap, merlijn, martin_rysavy
  Cc: Merlijn Wajer, Peter Hurley, Sebastian Reichel, linux-serial,
	devicetree, linux-kernel, linux-omap

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

Hi!

> We can make use of serdev drivers to do simple device drivers for
> TS 27.010 chanels, and we can handle vendor specific protocols on top
> of TS 27.010 with serdev drivers.

I took all three series (

[PATCHv3 0/3] Lost key-up interrupt handling for omap4-keypa
[PATCH 1/3] Input: atmel_mxt_ts - use runtime PM instead of
[PATCHv5 0/4] n_gsm serdev support and protocol driver for d

) and applied them on top of next-20200320.

Good news is that result boots. (So I did partial testing of the patches).

Bad news is that I still don't have working backlight. I do have LED
and there's backlight device connected to the LED, so I can't control
the LED directly, but the screen is black (and I don't see boot
messages either).

Before I start debugging, does screen work for you in -next, or do you
have some fixes I could try?

Thanks and best regards,
									Pavel

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

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

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

* Re: [PATCH 3/4] dt-bindings: mfd: motmdm: Add binding for motorola-mdm
  2020-03-19 17:37 ` [PATCH 3/4] dt-bindings: mfd: motmdm: Add binding for motorola-mdm Tony Lindgren
@ 2020-03-24 11:58   ` Lee Jones
  2020-03-24 16:37     ` Tony Lindgren
  0 siblings, 1 reply; 14+ messages in thread
From: Lee Jones @ 2020-03-24 11:58 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: Greg Kroah-Hartman, Rob Herring, Alan Cox, Jiri Slaby,
	Johan Hovold, Merlijn Wajer, Pavel Machek, Peter Hurley,
	Sebastian Reichel, linux-serial, devicetree, linux-kernel,
	linux-omap

> Add a binding document for Motorola modems controllable by
> TS 27.010 UART line discipline using serdev drivers.
> 
> Reviewed-by: Rob Herring <robh@kernel.org>
> [tony@atomide.com: moved to live under bindings/serdev]
> Signed-off-by: Tony Lindgren <tony@atomide.com>
> ---
>  .../serdev/motorola,mapphone-mdm6600.yaml     | 34 +++++++++++++++++++
>  1 file changed, 34 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml

Nit: subject line is out of date.

> diff --git a/Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml b/Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml
> new file mode 100644
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml
> @@ -0,0 +1,34 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/serdev/motorola,mapphone-mdm6600.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Motorola Mapphone MDM6600 Modem
> +
> +maintainers:
> +  - Tony Lindgren <tony@atomide.com>
> +
> +properties:
> +  compatible:
> +    items:
> +      - const: motorola,mapphone-mdm6600-serial
> +
> +  phys:
> +    maxItems: 1
> +
> +  phy-names:
> +    const: usb
> +
> +required:
> +  - compatible
> +  - phys
> +  - phy-names
> +
> +examples:
> +  - |
> +    modem {
> +        compatible = "motorola,mapphone-mdm6600-serial";
> +        phys = <&fsusb1_phy>;
> +        phy-names = "usb";
> +    };

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH 2/4] serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for droid4
  2020-03-22 22:09   ` Pavel Machek
@ 2020-03-24 16:34     ` Tony Lindgren
  0 siblings, 0 replies; 14+ messages in thread
From: Tony Lindgren @ 2020-03-24 16:34 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Greg Kroah-Hartman, Rob Herring, Alan Cox, Lee Jones, Jiri Slaby,
	Johan Hovold, Merlijn Wajer, Peter Hurley, Sebastian Reichel,
	linux-serial, devicetree, linux-kernel, linux-omap

* Pavel Machek <pavel@denx.de> [200322 22:09]:
> Hi!
> 
> > Many Motorola phones are controlling the modem using a custom variant
> > of TS 27.010 serial line discipline. Devices on these modems have a
> > dedicated TS 27.010 channel for features like audio mixer, GNSS, voice
> > modem, SIM card reader and so on.
> 
> I get warning here while applying:
> 
> Applying: serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem
> driver for droid4
> .git/rebase-apply/patch:22: new blank line at EOF.
> +

Thanks yes looks like an extra empty line added to Kconfig.

Regards,

Tony

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

* Re: [PATCH 3/4] dt-bindings: mfd: motmdm: Add binding for motorola-mdm
  2020-03-24 11:58   ` Lee Jones
@ 2020-03-24 16:37     ` Tony Lindgren
  0 siblings, 0 replies; 14+ messages in thread
From: Tony Lindgren @ 2020-03-24 16:37 UTC (permalink / raw)
  To: Lee Jones
  Cc: Greg Kroah-Hartman, Rob Herring, Alan Cox, Jiri Slaby,
	Johan Hovold, Merlijn Wajer, Pavel Machek, Peter Hurley,
	Sebastian Reichel, linux-serial, devicetree, linux-kernel,
	linux-omap

* Lee Jones <lee.jones@linaro.org> [200324 11:58]:
> > Add a binding document for Motorola modems controllable by
> > TS 27.010 UART line discipline using serdev drivers.
> > 
> > Reviewed-by: Rob Herring <robh@kernel.org>
> > [tony@atomide.com: moved to live under bindings/serdev]
> > Signed-off-by: Tony Lindgren <tony@atomide.com>
> > ---
> >  .../serdev/motorola,mapphone-mdm6600.yaml     | 34 +++++++++++++++++++
> >  1 file changed, 34 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/serdev/motorola,mapphone-mdm6600.yaml
> 
> Nit: subject line is out of date.

Thanks for spotting that, will change it to use the following:

dt-bindings: serdev: motmdm: Add binding for motorola-mdm

Regards,

Tony

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

* Re: Droid 4 in -next -- still no backlight was Re: [PATCH 1/4] tty: n_gsm: Add support for serdev drivers
  2020-03-22 22:42   ` Droid 4 in -next -- still no backlight was " Pavel Machek
@ 2020-03-24 17:01     ` Tony Lindgren
  2020-03-24 17:36       ` Tony Lindgren
  0 siblings, 1 reply; 14+ messages in thread
From: Tony Lindgren @ 2020-03-24 17:01 UTC (permalink / raw)
  To: Pavel Machek
  Cc: kernel list, linux-arm-kernel, linux-omap, sre, nekit1000,
	mpartap, merlijn, martin_rysavy, Peter Hurley, linux-serial,
	devicetree, Laurent Pinchart

* Pavel Machek <pavel@denx.de> [200322 22:43]:
> Hi!
> 
> > We can make use of serdev drivers to do simple device drivers for
> > TS 27.010 chanels, and we can handle vendor specific protocols on top
> > of TS 27.010 with serdev drivers.
> 
> I took all three series (
> 
> [PATCHv3 0/3] Lost key-up interrupt handling for omap4-keypa
> [PATCH 1/3] Input: atmel_mxt_ts - use runtime PM instead of
> [PATCHv5 0/4] n_gsm serdev support and protocol driver for d
> 
> ) and applied them on top of next-20200320.
> 
> Good news is that result boots. (So I did partial testing of the patches).
> 
> Bad news is that I still don't have working backlight. I do have LED
> and there's backlight device connected to the LED, so I can't control
> the LED directly, but the screen is black (and I don't see boot
> messages either).
> 
> Before I start debugging, does screen work for you in -next, or do you
> have some fixes I could try?

Yes the backlight works for me now just fine with current v5.6-rc.

But yeah, looks like LCD is again broken in current Linux next,
maybe Laurent and Sebastian have some clues?

Regards,

Tony

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

* Re: Droid 4 in -next -- still no backlight was Re: [PATCH 1/4] tty: n_gsm: Add support for serdev drivers
  2020-03-24 17:01     ` Tony Lindgren
@ 2020-03-24 17:36       ` Tony Lindgren
  0 siblings, 0 replies; 14+ messages in thread
From: Tony Lindgren @ 2020-03-24 17:36 UTC (permalink / raw)
  To: Pavel Machek
  Cc: kernel list, linux-arm-kernel, linux-omap, sre, nekit1000,
	mpartap, merlijn, martin_rysavy, Peter Hurley, linux-serial,
	devicetree, Laurent Pinchart

* Tony Lindgren <tony@atomide.com> [200324 17:02]:
> * Pavel Machek <pavel@denx.de> [200322 22:43]:
> > Before I start debugging, does screen work for you in -next, or do you
> > have some fixes I could try?
> 
> Yes the backlight works for me now just fine with current v5.6-rc.
> 
> But yeah, looks like LCD is again broken in current Linux next,
> maybe Laurent and Sebastian have some clues?

Oh it's just that now display-connector needs to be loaded instead
of the old omap specific hdmi connector :)

See commit e7e67d9a2f1d ("drm/omap: Switch the HDMI and VENC outputs
to drm_bridge") and check that you have the .config options enabled
in that patch also in your custom .config.

Regards,

Tony

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

* [PATCH 1/4] tty: n_gsm: Add support for serdev drivers
  2020-02-20 19:59 [PATCHv4 0/4] n_gsm serdev support and mfd driver for droid4 modem Tony Lindgren
@ 2020-02-20 19:59 ` Tony Lindgren
  0 siblings, 0 replies; 14+ messages in thread
From: Tony Lindgren @ 2020-02-20 19:59 UTC (permalink / raw)
  To: Lee Jones, Greg Kroah-Hartman
  Cc: Alan Cox, Jiri Slaby, Johan Hovold, Merlijn Wajer, Pavel Machek,
	Peter Hurley, Rob Herring, Sebastian Reichel, linux-serial,
	devicetree, linux-omap, linux-kernel

We can make use of serdev drivers to do simple device drivers for
TS 27.010 chanels, and we can handle vendor specific protocols on top
of TS 27.010 with serdev drivers.

So far this has been tested with Motorola droid4 where there is a custom
packet numbering protocol on top of TS 27.010 for the MDM6600 modem.

I initially though about adding the serdev support into a separate file,
but that will take some refactoring of n_gsm.c. And I'd like to have
things working first. Then later on we might want to consider splitting
n_gsm.c into three pieces for core, tty and serdev parts.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/tty/n_gsm.c        | 372 +++++++++++++++++++++++++++++++++++++
 include/linux/serdev-gsm.h | 168 +++++++++++++++++
 2 files changed, 540 insertions(+)
 create mode 100644 include/linux/serdev-gsm.h

diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -39,6 +39,7 @@
 #include <linux/file.h>
 #include <linux/uaccess.h>
 #include <linux/module.h>
+#include <linux/serdev.h>
 #include <linux/timer.h>
 #include <linux/tty_flip.h>
 #include <linux/tty_driver.h>
@@ -50,6 +51,7 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/gsmmux.h>
+#include <linux/serdev-gsm.h>
 
 static int debug;
 module_param(debug, int, 0600);
@@ -145,6 +147,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 *ops; /* serdev dlci ops, if used */
 	struct net_device *net; /* network interface, if created */
 };
 
@@ -179,6 +182,7 @@ struct gsm_control {
  */
 
 struct gsm_mux {
+	struct gsm_serdev *gsd;		/* Serial device bus data */
 	struct tty_struct *tty;		/* The tty our ldisc is bound to */
 	spinlock_t lock;
 	struct mutex mutex;
@@ -2326,6 +2330,374 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
 	return 0;
 }
 
+#ifdef CONFIG_SERIAL_DEV_BUS
+
+/**
+ * gsm_serdev_get_config - read ts 27.010 config
+ * @gsd:	serdev-gsm instance
+ * @c:		ts 27.010 config data
+ *
+ * See gsm_copy_config_values() for more information.
+ */
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	struct gsm_mux *gsm;
+
+	if (!gsd || !gsd->gsm)
+		return -ENODEV;
+
+	gsm = gsd->gsm;
+
+	if (!c)
+		return -EINVAL;
+
+	gsm_copy_config_values(gsm, c);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_get_config);
+
+/**
+ * gsm_serdev_set_config - set ts 27.010 config
+ * @gsd:	serdev-gsm instance
+ * @c:		ts 27.010 config data
+ *
+ * See gsm_config() for more information.
+ */
+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 >= 63)
+		return ERR_PTR(-EINVAL);
+
+	mutex_lock(&gsm->mutex);
+
+	if (gsm->dlci[line]) {
+		dlci = gsm->dlci[line];
+		goto unlock;
+	} else if (!allocate) {
+		dlci = ERR_PTR(-ENODEV);
+		goto unlock;
+	}
+
+	dlci = gsm_dlci_alloc(gsm, line);
+	if (!dlci) {
+		gsm = ERR_PTR(-ENOMEM);
+		goto unlock;
+	}
+
+	gsm->dlci[line] = dlci;
+
+unlock:
+	mutex_unlock(&gsm->mutex);
+
+	return dlci;
+}
+
+static void gsd_dlci_data(struct gsm_dlci *dlci, const u8 *buf, int len)
+{
+	struct gsm_mux *gsm = dlci->gsm;
+	struct gsm_serdev *gsd = gsm->gsd;
+
+	if (!gsd || !dlci->ops)
+		return;
+
+	switch (dlci->adaption) {
+	case 0:
+	case 1:
+		if (dlci->ops->receive_buf)
+			dlci->ops->receive_buf(dlci->ops, buf, len);
+		break;
+	default:
+		pr_warn("dlci%i adaption %i not yet implemented\n",
+			dlci->addr, dlci->adaption);
+		break;
+	}
+}
+
+/**
+ * gsm_serdev_write - write data to a ts 27.010 channel
+ * @gsd:	serdev-gsm instance
+ * @ops:	channel ops
+ * @buf:	write buffer
+ * @len:	buffer length
+ */
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+		     const u8 *buf, int len)
+{
+	struct gsm_mux *gsm;
+	struct gsm_dlci *dlci;
+	struct gsm_msg *msg;
+	int h, size, total_size = 0;
+	u8 *dp;
+
+	if (!gsd || !gsd->gsm)
+		return -ENODEV;
+
+	gsm = gsd->gsm;
+
+	dlci = gsd_dlci_get(gsd, ops->line, false);
+	if (IS_ERR(dlci))
+		return PTR_ERR(dlci);
+
+	h = dlci->adaption - 1;
+
+	if (len > gsm->mtu)
+		len = gsm->mtu;
+
+	size = len + h;
+
+	msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+	if (!msg)
+		return -ENOMEM;
+
+	dp = msg->data;
+	switch (dlci->adaption) {
+	case 1:
+		break;
+	case 2:
+		*dp++ = gsm_encode_modem(dlci);
+		break;
+	}
+	memcpy(dp, buf, len);
+	gsm_data_queue(dlci, msg);
+	total_size += size;
+
+	return total_size;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_write);
+
+/**
+ * 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);
+	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 *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 || !ops->receive_buf)
+		return -EINVAL;
+
+	dlci = gsd_dlci_get(gsd, ops->line, true);
+	if (IS_ERR(dlci))
+		return PTR_ERR(dlci);
+
+	if (dlci->state == DLCI_OPENING || dlci->state == DLCI_OPEN ||
+	    dlci->state == DLCI_CLOSING)
+		return -EBUSY;
+
+	mutex_lock(&dlci->mutex);
+	dlci->ops = ops;
+	dlci->modem_rx = 0;
+	dlci->prev_data = dlci->data;
+	dlci->data = gsd_dlci_data;
+	mutex_unlock(&dlci->mutex);
+
+	gsm_dlci_begin_open(dlci);
+
+	/*
+	 * 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 *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 = 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 gsm_serdev *gsd = gsm->gsd;
+	struct serdev_device *serdev = gsm->gsd->serdev;
+	bool asleep;
+
+	asleep = atomic_read(&gsd->asleep);
+	if (asleep)
+		return -ENOSPC;
+
+	if (gsm->gsd->output)
+		return gsm->gsd->output(gsm->gsd, data, len);
+	else
+		return serdev_device_write_buf(serdev, data, len);
+}
+
+static int gsd_receive_buf(struct serdev_device *serdev, const u8 *data,
+			   size_t count)
+{
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+	struct gsm_mux *gsm;
+	const unsigned char *dp;
+	int i;
+
+	if (WARN_ON(!gsd))
+		return 0;
+
+	gsm = gsd->gsm;
+
+	if (debug & 4)
+		print_hex_dump_bytes("gsd_receive_buf: ",
+				     DUMP_PREFIX_OFFSET,
+				     data, count);
+
+	for (i = count, dp = data; i; i--, dp++)
+		gsm->receive(gsm, *dp);
+
+	return count;
+}
+
+static void gsd_write_wakeup(struct serdev_device *serdev)
+{
+	serdev_device_write_wakeup(serdev);
+}
+
+static struct serdev_device_ops gsd_client_ops = {
+	.receive_buf = gsd_receive_buf,
+	.write_wakeup = gsd_write_wakeup,
+};
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+	struct gsm_mux *gsm;
+	int error;
+
+	if (WARN(!gsd || !gsd->serdev || !gsd->output,
+		 "serdev and output must be initialized\n"))
+		return -EINVAL;
+
+	serdev_device_set_client_ops(gsd->serdev, &gsd_client_ops);
+
+	gsm = gsm_alloc_mux();
+	if (!gsm)
+		return -ENOMEM;
+
+	gsm->encoding = 1;
+	gsm->tty = NULL;
+	gsm->gsd = gsd;
+	gsm->output = gsm_serdev_output;
+	atomic_set(&gsd->asleep, 0);
+	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
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
new file mode 100644
--- /dev/null
+++ b/include/linux/serdev-gsm.h
@@ -0,0 +1,168 @@
+/* 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;
+struct gsm_config;
+
+/**
+ * struct gsm_serdev - serdev-gsm instance
+ * @serdev:		serdev instance
+ * @gsm:		ts 27.010 n_gsm instance
+ * @asleep:		device is in idle state
+ * @drvdata:		serdev-gsm consumer driver data
+ * @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;
+	atomic_t asleep;
+	void *drvdata;
+	int (*output)(struct gsm_serdev *gsd, u8 *data, int len);
+};
+
+/**
+ * struct gsm_serdev_dlci - serdev-gsm ts 27.010 channel data
+ * @line:		ts 27.010 channel, control channel 0 is not available
+ * @receive_buf:	function to handle data received for the channel
+ */
+struct gsm_serdev_dlci {
+	int line;
+	int (*receive_buf)(struct gsm_serdev_dlci *ops,
+			   const unsigned char *buf,
+			   size_t len);
+};
+
+#if IS_ENABLED(CONFIG_N_GSM) && IS_ENABLED(CONFIG_SERIAL_DEV_BUS)
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd);
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+	if (gsd)
+		return gsd->drvdata;
+
+	return NULL;
+}
+
+static inline void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+	if (gsd)
+		gsd->drvdata = drvdata;
+}
+
+static inline void gsm_serdev_suspend(struct gsm_serdev *gsd)
+{
+	if (!gsd)
+		return;
+
+	atomic_set(&gsd->asleep, 1);
+}
+
+static inline int gsm_serdev_resume(struct gsm_serdev *gsd)
+{
+	if (!gsd)
+		return -EINVAL;
+
+	atomic_set(&gsd->asleep, 0);
+
+	return 0;
+}
+
+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 *ops);
+extern void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				       struct gsm_serdev_dlci *ops);
+extern int gsm_serdev_write(struct gsm_serdev *gsd,
+			    struct gsm_serdev_dlci *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 void *gsm_serdev_get_drvdata(struct device *dev)
+{
+	return NULL;
+}
+
+static inline
+void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+}
+
+static inline void gsm_serdev_suspend(struct gsm_serdev *gsd)
+{
+}
+
+static inline int gsm_serdev_resume(struct gsm_serdev *gsd)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+			     struct gsm_serdev_dlci *ops)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				struct gsm_serdev_dlci *ops)
+{
+}
+
+static inline
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+		     const u8 *buf, int len)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+}
+
+#endif	/* CONFIG_N_GSM && CONFIG_SERIAL_DEV_BUS */
+#endif	/* _LINUX_SERDEV_GSM_H */
-- 
2.25.1

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

* [PATCH 1/4] tty: n_gsm: Add support for serdev drivers
  2020-02-10  4:01 [PATCHv2 0/4] n_gsm serdev support and mfd driver for droid4 modem Tony Lindgren
@ 2020-02-10  4:01 ` Tony Lindgren
  0 siblings, 0 replies; 14+ messages in thread
From: Tony Lindgren @ 2020-02-10  4:01 UTC (permalink / raw)
  To: Lee Jones, Greg Kroah-Hartman
  Cc: Alan Cox, Jiri Slaby, Johan Hovold, Merlijn Wajer, Pavel Machek,
	Peter Hurley, Rob Herring, Sebastian Reichel, linux-serial,
	devicetree, linux-omap, linux-kernel

We can make use of serdev drivers to do simple device drivers for
TS 27.010 chanels, and we can handle vendor specific protocols on top
of TS 27.010 with serdev drivers.

So far this has been tested with Motorola droid4 where there is a custom
packet numbering protocol on top of TS 27.010 for the MDM6600 modem.

I initially though about adding the serdev support into a separate file,
but that will take some refactoring of n_gsm.c. And I'd like to have
things working first. Then later on we might want to consider splitting
n_gsm.c into three pieces for core, tty and serdev parts.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/tty/n_gsm.c        | 372 +++++++++++++++++++++++++++++++++++++
 include/linux/serdev-gsm.h | 168 +++++++++++++++++
 2 files changed, 540 insertions(+)
 create mode 100644 include/linux/serdev-gsm.h

diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -39,6 +39,7 @@
 #include <linux/file.h>
 #include <linux/uaccess.h>
 #include <linux/module.h>
+#include <linux/serdev.h>
 #include <linux/timer.h>
 #include <linux/tty_flip.h>
 #include <linux/tty_driver.h>
@@ -50,6 +51,7 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/gsmmux.h>
+#include <linux/serdev-gsm.h>
 
 static int debug;
 module_param(debug, int, 0600);
@@ -145,6 +147,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 *ops; /* serdev dlci ops, if used */
 	struct net_device *net; /* network interface, if created */
 };
 
@@ -179,6 +182,7 @@ struct gsm_control {
  */
 
 struct gsm_mux {
+	struct gsm_serdev *gsd;		/* Serial device bus data */
 	struct tty_struct *tty;		/* The tty our ldisc is bound to */
 	spinlock_t lock;
 	struct mutex mutex;
@@ -2326,6 +2330,374 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
 	return 0;
 }
 
+#ifdef CONFIG_SERIAL_DEV_BUS
+
+/**
+ * gsm_serdev_get_config - read ts 27.010 config
+ * @gsd:	serdev-gsm instance
+ * @c:		ts 27.010 config data
+ *
+ * See gsm_copy_config_values() for more information.
+ */
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	struct gsm_mux *gsm;
+
+	if (!gsd || !gsd->gsm)
+		return -ENODEV;
+
+	gsm = gsd->gsm;
+
+	if (!c)
+		return -EINVAL;
+
+	gsm_copy_config_values(gsm, c);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_get_config);
+
+/**
+ * gsm_serdev_set_config - set ts 27.010 config
+ * @gsd:	serdev-gsm instance
+ * @c:		ts 27.010 config data
+ *
+ * See gsm_config() for more information.
+ */
+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 >= 63)
+		return ERR_PTR(-EINVAL);
+
+	mutex_lock(&gsm->mutex);
+
+	if (gsm->dlci[line]) {
+		dlci = gsm->dlci[line];
+		goto unlock;
+	} else if (!allocate) {
+		dlci = ERR_PTR(-ENODEV);
+		goto unlock;
+	}
+
+	dlci = gsm_dlci_alloc(gsm, line);
+	if (!dlci) {
+		gsm = ERR_PTR(-ENOMEM);
+		goto unlock;
+	}
+
+	gsm->dlci[line] = dlci;
+
+unlock:
+	mutex_unlock(&gsm->mutex);
+
+	return dlci;
+}
+
+static void gsd_dlci_data(struct gsm_dlci *dlci, const u8 *buf, int len)
+{
+	struct gsm_mux *gsm = dlci->gsm;
+	struct gsm_serdev *gsd = gsm->gsd;
+
+	if (!gsd || !dlci->ops)
+		return;
+
+	switch (dlci->adaption) {
+	case 0:
+	case 1:
+		if (dlci->ops->receive_buf)
+			dlci->ops->receive_buf(dlci->ops, buf, len);
+		break;
+	default:
+		pr_warn("dlci%i adaption %i not yet implemented\n",
+			dlci->addr, dlci->adaption);
+		break;
+	}
+}
+
+/**
+ * gsm_serdev_write - write data to a ts 27.010 channel
+ * @gsd:	serdev-gsm instance
+ * @ops:	channel ops
+ * @buf:	write buffer
+ * @len:	buffer length
+ */
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+		     const u8 *buf, int len)
+{
+	struct gsm_mux *gsm;
+	struct gsm_dlci *dlci;
+	struct gsm_msg *msg;
+	int h, size, total_size = 0;
+	u8 *dp;
+
+	if (!gsd || !gsd->gsm)
+		return -ENODEV;
+
+	gsm = gsd->gsm;
+
+	dlci = gsd_dlci_get(gsd, ops->line, false);
+	if (IS_ERR(dlci))
+		return PTR_ERR(dlci);
+
+	h = dlci->adaption - 1;
+
+	if (len > gsm->mtu)
+		len = gsm->mtu;
+
+	size = len + h;
+
+	msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+	if (!msg)
+		return -ENOMEM;
+
+	dp = msg->data;
+	switch (dlci->adaption) {
+	case 1:
+		break;
+	case 2:
+		*dp++ = gsm_encode_modem(dlci);
+		break;
+	}
+	memcpy(dp, buf, len);
+	gsm_data_queue(dlci, msg);
+	total_size += size;
+
+	return total_size;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_write);
+
+/**
+ * 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);
+	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 *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 || !ops->receive_buf)
+		return -EINVAL;
+
+	dlci = gsd_dlci_get(gsd, ops->line, true);
+	if (IS_ERR(dlci))
+		return PTR_ERR(dlci);
+
+	if (dlci->state == DLCI_OPENING || dlci->state == DLCI_OPEN ||
+	    dlci->state == DLCI_CLOSING)
+		return -EBUSY;
+
+	mutex_lock(&dlci->mutex);
+	dlci->ops = ops;
+	dlci->modem_rx = 0;
+	dlci->prev_data = dlci->data;
+	dlci->data = gsd_dlci_data;
+	mutex_unlock(&dlci->mutex);
+
+	gsm_dlci_begin_open(dlci);
+
+	/*
+	 * 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 *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 = 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 gsm_serdev *gsd = gsm->gsd;
+	struct serdev_device *serdev = gsm->gsd->serdev;
+	bool asleep;
+
+	asleep = atomic_read(&gsd->asleep);
+	if (asleep)
+		return -ENOSPC;
+
+	if (gsm->gsd->output)
+		return gsm->gsd->output(gsm->gsd, data, len);
+	else
+		return serdev_device_write_buf(serdev, data, len);
+}
+
+static int gsd_receive_buf(struct serdev_device *serdev, const u8 *data,
+			   size_t count)
+{
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+	struct gsm_mux *gsm;
+	const unsigned char *dp;
+	int i;
+
+	if (WARN_ON(!gsd))
+		return 0;
+
+	gsm = gsd->gsm;
+
+	if (debug & 4)
+		print_hex_dump_bytes("gsd_receive_buf: ",
+				     DUMP_PREFIX_OFFSET,
+				     data, count);
+
+	for (i = count, dp = data; i; i--, dp++)
+		gsm->receive(gsm, *dp);
+
+	return count;
+}
+
+static void gsd_write_wakeup(struct serdev_device *serdev)
+{
+	serdev_device_write_wakeup(serdev);
+}
+
+static struct serdev_device_ops gsd_client_ops = {
+	.receive_buf = gsd_receive_buf,
+	.write_wakeup = gsd_write_wakeup,
+};
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+	struct gsm_mux *gsm;
+	int error;
+
+	if (WARN(!gsd || !gsd->serdev || !gsd->output,
+		 "serdev and output must be initialized\n"))
+		return -EINVAL;
+
+	serdev_device_set_client_ops(gsd->serdev, &gsd_client_ops);
+
+	gsm = gsm_alloc_mux();
+	if (!gsm)
+		return -ENOMEM;
+
+	gsm->encoding = 1;
+	gsm->tty = NULL;
+	gsm->gsd = gsd;
+	gsm->output = gsm_serdev_output;
+	atomic_set(&gsd->asleep, 0);
+	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
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
new file mode 100644
--- /dev/null
+++ b/include/linux/serdev-gsm.h
@@ -0,0 +1,168 @@
+/* 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;
+struct gsm_config;
+
+/**
+ * struct gsm_serdev - serdev-gsm instance
+ * @serdev:		serdev instance
+ * @gsm:		ts 27.010 n_gsm instance
+ * @asleep:		device is in idle state
+ * @drvdata:		serdev-gsm consumer driver data
+ * @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;
+	atomic_t asleep;
+	void *drvdata;
+	int (*output)(struct gsm_serdev *gsd, u8 *data, int len);
+};
+
+/**
+ * struct gsm_serdev_dlci - serdev-gsm ts 27.010 channel data
+ * @line:		ts 27.010 channel, control channel 0 is not available
+ * @receive_buf:	function to handle data received for the channel
+ */
+struct gsm_serdev_dlci {
+	int line;
+	int (*receive_buf)(struct gsm_serdev_dlci *ops,
+			   const unsigned char *buf,
+			   size_t len);
+};
+
+#ifdef CONFIG_SERIAL_DEV_BUS
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd);
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+	if (gsd)
+		return gsd->drvdata;
+
+	return NULL;
+}
+
+static inline void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+	struct serdev_device *serdev = to_serdev_device(dev);
+	struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+	if (gsd)
+		gsd->drvdata = drvdata;
+}
+
+static inline void gsm_serdev_suspend(struct gsm_serdev *gsd)
+{
+	if (!gsd)
+		return;
+
+	atomic_set(&gsd->asleep, 1);
+}
+
+static inline int gsm_serdev_resume(struct gsm_serdev *gsd)
+{
+	if (!gsd)
+		return -EINVAL;
+
+	atomic_set(&gsd->asleep, 0);
+
+	return 0;
+}
+
+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 *ops);
+extern void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				       struct gsm_serdev_dlci *ops);
+extern int gsm_serdev_write(struct gsm_serdev *gsd,
+			    struct gsm_serdev_dlci *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 void *gsm_serdev_get_drvdata(struct device *dev)
+{
+	return NULL;
+}
+
+static inline
+void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+}
+
+static inline void gsm_serdev_suspend(struct gsm_serdev *gsd)
+{
+}
+
+static inline int gsm_serdev_resume(struct gsm_serdev *gsd)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+	return -ENODEV;
+}
+
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+			     struct gsm_serdev_dlci *ops)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+				struct gsm_serdev_dlci *ops)
+{
+}
+
+static inline
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+		     const u8 *buf, int len)
+{
+	return -ENODEV;
+}
+
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+}
+
+#endif	/* CONFIG_SERIAL_DEV_BUS */
+#endif	/* _LINUX_SERDEV_GSM_H */
-- 
2.25.0

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

end of thread, back to index

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-19 17:37 [PATCHv5 0/4] n_gsm serdev support and protocol driver for droid4 modem Tony Lindgren
2020-03-19 17:37 ` [PATCH 1/4] tty: n_gsm: Add support for serdev drivers Tony Lindgren
2020-03-22 22:42   ` Droid 4 in -next -- still no backlight was " Pavel Machek
2020-03-24 17:01     ` Tony Lindgren
2020-03-24 17:36       ` Tony Lindgren
2020-03-19 17:37 ` [PATCH 2/4] serdev: ngsm-motmdm: Add Motorola TS 27.010 serdev modem driver for droid4 Tony Lindgren
2020-03-22 22:09   ` Pavel Machek
2020-03-24 16:34     ` Tony Lindgren
2020-03-19 17:37 ` [PATCH 3/4] dt-bindings: mfd: motmdm: Add binding for motorola-mdm Tony Lindgren
2020-03-24 11:58   ` Lee Jones
2020-03-24 16:37     ` Tony Lindgren
2020-03-19 17:37 ` [PATCH 4/4] ARM: dts: omap4-droid4: Enable basic modem support Tony Lindgren
  -- strict thread matches above, loose matches on Subject: below --
2020-02-20 19:59 [PATCHv4 0/4] n_gsm serdev support and mfd driver for droid4 modem Tony Lindgren
2020-02-20 19:59 ` [PATCH 1/4] tty: n_gsm: Add support for serdev drivers Tony Lindgren
2020-02-10  4:01 [PATCHv2 0/4] n_gsm serdev support and mfd driver for droid4 modem Tony Lindgren
2020-02-10  4:01 ` [PATCH 1/4] tty: n_gsm: Add support for serdev drivers Tony Lindgren

Linux-OMAP Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-omap/0 linux-omap/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-omap linux-omap/ https://lore.kernel.org/linux-omap \
		linux-omap@vger.kernel.org
	public-inbox-index linux-omap

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-omap


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git