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", ®)) { + 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
signature.asc
Description: PGP signature

