Hi!

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

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

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

Signed-off-by: Pavel Machek <[email protected]>

Best regards,
                                                                Pavel

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


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

Attachment: signature.asc
Description: PGP signature

Reply via email to