All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/5] misc serdev: new serdev based driver for Wi2Wi w2sg00x4 GPS module
@ 2017-11-15 21:37 ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

Changes V4:
* removed all pdata remains
* added one missing Signes-off-By

2017-11-15 16:19:17: Changes V3:
* worked in suggestions by kbuild test robot
* added misc+serdev to the subject

2017-11-12 22:00:02: Changes V2:
* reduced to submit only w2sg00x4 GPS driver code
* add DT node for GTA04 device to make use of the driver
* split into base code and a debugging Kconfig option (brings device into false power state after boot)
* worked in comments by kbuild robot and Rob Herring

2017-05-21 12:44:07: RFC V1
* RFC concerning new serdev based drivers for Wi2Wi w2sg00x4 GPS module and w2cbw003 bluetooth

H. Nikolaus Schaller (5):
  dt-bindings: vendor prefix for Wi2Wi, Inc.
  dt-bindings: gps: add w2sg00x4 bindings documentation (GPS module with
    UART))
  misc serdev: Add w2sg0004 (gps receiver) power control driver
  DTS: gta04: add serdev node for w2sg00x4
  misc serdev: w2sg0004: add debugging code and Kconfig

 .../devicetree/bindings/gps/wi2wi,w2sg0004.txt     |  24 +
 .../devicetree/bindings/vendor-prefixes.txt        |   1 +
 arch/arm/boot/dts/omap3-gta04.dtsi                 |   6 +
 drivers/misc/Kconfig                               |  18 +
 drivers/misc/Makefile                              |   1 +
 drivers/misc/w2sg0004.c                            | 582 +++++++++++++++++++++
 6 files changed, 632 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
 create mode 100644 drivers/misc/w2sg0004.c

-- 
2.12.2

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

* [PATCH v4 0/5] misc serdev: new serdev based driver for Wi2Wi w2sg00x4 GPS module
@ 2017-11-15 21:37 ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA,
	letux-kernel-S0jZdbWzriLCfDggNXIi3w,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

Changes V4:
* removed all pdata remains
* added one missing Signes-off-By

2017-11-15 16:19:17: Changes V3:
* worked in suggestions by kbuild test robot
* added misc+serdev to the subject

2017-11-12 22:00:02: Changes V2:
* reduced to submit only w2sg00x4 GPS driver code
* add DT node for GTA04 device to make use of the driver
* split into base code and a debugging Kconfig option (brings device into false power state after boot)
* worked in comments by kbuild robot and Rob Herring

2017-05-21 12:44:07: RFC V1
* RFC concerning new serdev based drivers for Wi2Wi w2sg00x4 GPS module and w2cbw003 bluetooth

H. Nikolaus Schaller (5):
  dt-bindings: vendor prefix for Wi2Wi, Inc.
  dt-bindings: gps: add w2sg00x4 bindings documentation (GPS module with
    UART))
  misc serdev: Add w2sg0004 (gps receiver) power control driver
  DTS: gta04: add serdev node for w2sg00x4
  misc serdev: w2sg0004: add debugging code and Kconfig

 .../devicetree/bindings/gps/wi2wi,w2sg0004.txt     |  24 +
 .../devicetree/bindings/vendor-prefixes.txt        |   1 +
 arch/arm/boot/dts/omap3-gta04.dtsi                 |   6 +
 drivers/misc/Kconfig                               |  18 +
 drivers/misc/Makefile                              |   1 +
 drivers/misc/w2sg0004.c                            | 582 +++++++++++++++++++++
 6 files changed, 632 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
 create mode 100644 drivers/misc/w2sg0004.c

-- 
2.12.2

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v4 0/5] misc serdev: new serdev based driver for Wi2Wi w2sg00x4 GPS module
@ 2017-11-15 21:37 ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: linux-arm-kernel

Changes V4:
* removed all pdata remains
* added one missing Signes-off-By

2017-11-15 16:19:17: Changes V3:
* worked in suggestions by kbuild test robot
* added misc+serdev to the subject

2017-11-12 22:00:02: Changes V2:
* reduced to submit only w2sg00x4 GPS driver code
* add DT node for GTA04 device to make use of the driver
* split into base code and a debugging Kconfig option (brings device into false power state after boot)
* worked in comments by kbuild robot and Rob Herring

2017-05-21 12:44:07: RFC V1
* RFC concerning new serdev based drivers for Wi2Wi w2sg00x4 GPS module and w2cbw003 bluetooth

H. Nikolaus Schaller (5):
  dt-bindings: vendor prefix for Wi2Wi, Inc.
  dt-bindings: gps: add w2sg00x4 bindings documentation (GPS module with
    UART))
  misc serdev: Add w2sg0004 (gps receiver) power control driver
  DTS: gta04: add serdev node for w2sg00x4
  misc serdev: w2sg0004: add debugging code and Kconfig

 .../devicetree/bindings/gps/wi2wi,w2sg0004.txt     |  24 +
 .../devicetree/bindings/vendor-prefixes.txt        |   1 +
 arch/arm/boot/dts/omap3-gta04.dtsi                 |   6 +
 drivers/misc/Kconfig                               |  18 +
 drivers/misc/Makefile                              |   1 +
 drivers/misc/w2sg0004.c                            | 582 +++++++++++++++++++++
 6 files changed, 632 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
 create mode 100644 drivers/misc/w2sg0004.c

-- 
2.12.2

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

* [PATCH v4 1/5] dt-bindings: vendor prefix for Wi2Wi, Inc.
  2017-11-15 21:37 ` H. Nikolaus Schaller
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  -1 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

Introduce vendor prefix for Wi2Wi, Inc. for W2SG00x4 GPS modules
and W2CBW003 Bluetooth/WiFi combo (CSR/Marvell).

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Acked-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 1afd298eddd7..ad4381738954 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -366,6 +366,7 @@ voipac	Voipac Technologies s.r.o.
 wd	Western Digital Corp.
 wetek	WeTek Electronics, limited.
 wexler	Wexler
+wi2wi	Wi2Wi, Inc.
 winbond Winbond Electronics corp.
 winstar	Winstar Display Corp.
 wlf	Wolfson Microelectronics
-- 
2.12.2

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

* [PATCH v4 1/5] dt-bindings: vendor prefix for Wi2Wi, Inc.
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: linux-arm-kernel

Introduce vendor prefix for Wi2Wi, Inc. for W2SG00x4 GPS modules
and W2CBW003 Bluetooth/WiFi combo (CSR/Marvell).

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Acked-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 1afd298eddd7..ad4381738954 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -366,6 +366,7 @@ voipac	Voipac Technologies s.r.o.
 wd	Western Digital Corp.
 wetek	WeTek Electronics, limited.
 wexler	Wexler
+wi2wi	Wi2Wi, Inc.
 winbond Winbond Electronics corp.
 winstar	Winstar Display Corp.
 wlf	Wolfson Microelectronics
-- 
2.12.2

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

* [PATCH v4 2/5] dt-bindings: gps: add w2sg00x4 bindings documentation (GPS module with UART))
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

add bindings documentation for Wi2Wi W2SG00x4 GPS module.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Acked-by: Rob Herring <robh@kernel.org>
---
 .../devicetree/bindings/gps/wi2wi,w2sg0004.txt     | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt

diff --git a/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt b/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
new file mode 100644
index 000000000000..bcf307382afb
--- /dev/null
+++ b/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
@@ -0,0 +1,24 @@
+Wi2Wi GPS module connected through UART
+
+Should be a subnode of the SoC UART it is connected to.
+
+Required properties:
+- compatible:	should be one of (depending on precise model)
+	"wi2wi,w2sg0004"
+	"wi2wi,w2sg0084"
+- enable-gpio:	the GPIO that controls the module's power toggle
+	input. A positive impulse of sufficent length toggles the
+	power state.
+
+Optional properties:
+- lna-supply:	an (optional) LNA regulator that is enabled together with the GPS receiver
+
+Example:
+
+&uart2 {
+	w2sg0004: gps {
+		compatible = "wi2wi,w2sg0004";
+		lna-supply = <&vsim>;   /* LNA regulator */
+		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
+        };
+};
-- 
2.12.2

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

* [PATCH v4 2/5] dt-bindings: gps: add w2sg00x4 bindings documentation (GPS module with UART))
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA,
	letux-kernel-S0jZdbWzriLCfDggNXIi3w,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

add bindings documentation for Wi2Wi W2SG00x4 GPS module.

Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
 .../devicetree/bindings/gps/wi2wi,w2sg0004.txt     | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt

diff --git a/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt b/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
new file mode 100644
index 000000000000..bcf307382afb
--- /dev/null
+++ b/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
@@ -0,0 +1,24 @@
+Wi2Wi GPS module connected through UART
+
+Should be a subnode of the SoC UART it is connected to.
+
+Required properties:
+- compatible:	should be one of (depending on precise model)
+	"wi2wi,w2sg0004"
+	"wi2wi,w2sg0084"
+- enable-gpio:	the GPIO that controls the module's power toggle
+	input. A positive impulse of sufficent length toggles the
+	power state.
+
+Optional properties:
+- lna-supply:	an (optional) LNA regulator that is enabled together with the GPS receiver
+
+Example:
+
+&uart2 {
+	w2sg0004: gps {
+		compatible = "wi2wi,w2sg0004";
+		lna-supply = <&vsim>;   /* LNA regulator */
+		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
+        };
+};
-- 
2.12.2

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v4 2/5] dt-bindings: gps: add w2sg00x4 bindings documentation (GPS module with UART))
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: linux-arm-kernel

add bindings documentation for Wi2Wi W2SG00x4 GPS module.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Acked-by: Rob Herring <robh@kernel.org>
---
 .../devicetree/bindings/gps/wi2wi,w2sg0004.txt     | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt

diff --git a/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt b/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
new file mode 100644
index 000000000000..bcf307382afb
--- /dev/null
+++ b/Documentation/devicetree/bindings/gps/wi2wi,w2sg0004.txt
@@ -0,0 +1,24 @@
+Wi2Wi GPS module connected through UART
+
+Should be a subnode of the SoC UART it is connected to.
+
+Required properties:
+- compatible:	should be one of (depending on precise model)
+	"wi2wi,w2sg0004"
+	"wi2wi,w2sg0084"
+- enable-gpio:	the GPIO that controls the module's power toggle
+	input. A positive impulse of sufficent length toggles the
+	power state.
+
+Optional properties:
+- lna-supply:	an (optional) LNA regulator that is enabled together with the GPS receiver
+
+Example:
+
+&uart2 {
+	w2sg0004: gps {
+		compatible = "wi2wi,w2sg0004";
+		lna-supply = <&vsim>;   /* LNA regulator */
+		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
+        };
+};
-- 
2.12.2

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

* [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
  2017-11-15 21:37 ` H. Nikolaus Schaller
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  -1 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.

Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
and turn on/off the module. It also detects if the module is turned on (sends data)
but should be off, e.g. if it was already turned on during boot or power-on-reset.

Additionally, rfkill block/unblock can be used to control an external LNA
(and power down the module if not needed).

The driver concept is based on code developed by NeilBrown <neilb@suse.de>
but simplified and adapted to use the new serdev API introduced in 4.11.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 drivers/misc/Kconfig    |  10 +
 drivers/misc/Makefile   |   1 +
 drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 556 insertions(+)
 create mode 100644 drivers/misc/w2sg0004.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 8136dc7e863d..09d171d68408 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
 source "drivers/misc/genwqe/Kconfig"
 source "drivers/misc/echo/Kconfig"
 source "drivers/misc/cxl/Kconfig"
+
+config W2SG0004
+	tristate "W2SG00x4 on/off control"
+	depends on GPIOLIB && SERIAL_DEV_BUS
+	help
+          Enable on/off control of W2SG00x4 GPS moduled connected
+	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
+	  is opened/closed.
+	  It also provides a rfkill gps name to control the LNA power.
+
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index ad0e64fdba34..abcb667e0ff0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
 obj-y				+= mic/
 obj-$(CONFIG_GENWQE)		+= genwqe/
 obj-$(CONFIG_ECHO)		+= echo/
+obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
 obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
 obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
new file mode 100644
index 000000000000..12e14b5e0a99
--- /dev/null
+++ b/drivers/misc/w2sg0004.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
+ *
+ * This receiver has an ON/OFF pin which must be toggled to
+ * turn the device 'on' of 'off'.  A high->low->high toggle
+ * will switch the device on if it is off, and off if it is on.
+ *
+ * To enable receiving on/off requests we register with the
+ * UART power management notifications.
+ *
+ * It is not possible to directly detect the state of the device.
+ * However when it is on it will send characters on a UART line
+ * regularly.
+ *
+ * To detect that the power state is out of sync (e.g. if GPS
+ * was enabled before a reboot), we register for UART data received
+ * notifications.
+ *
+ * In addition we register as a rfkill client so that we can
+ * control the LNA power.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/rfkill.h>
+#include <linux/serdev.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/workqueue.h>
+
+/*
+ * There seems to be restrictions on how quickly we can toggle the
+ * on/off line.  data sheets says "two rtc ticks", whatever that means.
+ * If we do it too soon it doesn't work.
+ * So we have a state machine which uses the common work queue to ensure
+ * clean transitions.
+ * When a change is requested we record that request and only act on it
+ * once the previous change has completed.
+ * A change involves a 10ms low pulse, and a 990ms raised level, so only
+ * one change per second.
+ */
+
+enum w2sg_state {
+	W2SG_IDLE,	/* is not changing state */
+	W2SG_PULSE,	/* activate on/off impulse */
+	W2SG_NOPULSE	/* deactivate on/off impulse */
+};
+
+struct w2sg_data {
+	struct		rfkill *rf_kill;
+	struct		regulator *lna_regulator;
+	int		lna_blocked;	/* rfkill block gps active */
+	int		lna_is_off;	/* LNA is currently off */
+	int		is_on;		/* current state (0/1) */
+	unsigned long	last_toggle;
+	unsigned long	backoff;	/* time to wait since last_toggle */
+	int		on_off_gpio;	/* the on-off gpio number */
+	struct		serdev_device *uart;	/* uart connected to the chip */
+	struct		tty_driver *tty_drv;	/* this is the user space tty */
+	struct		device *dev;	/* from tty_port_register_device() */
+	struct		tty_port port;
+	int		open_count;	/* how often we were opened */
+	enum		w2sg_state state;
+	int		requested;	/* requested state (0/1) */
+	int		suspended;
+	struct delayed_work work;
+	int		discard_count;
+};
+
+static struct w2sg_data *w2sg_by_minor[1];
+
+static int w2sg_set_lna_power(struct w2sg_data *data)
+{
+	int ret = 0;
+	int off = data->suspended || !data->requested || data->lna_blocked;
+
+	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
+
+	if (off != data->lna_is_off) {
+		data->lna_is_off = off;
+		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
+			if (off)
+				regulator_disable(data->lna_regulator);
+			else
+				ret = regulator_enable(data->lna_regulator);
+		}
+	}
+
+	return ret;
+}
+
+static void w2sg_set_power(void *pdata, int val)
+{
+	struct w2sg_data *data = (struct w2sg_data *) pdata;
+
+	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
+
+	if (val && !data->requested) {
+		data->requested = true;
+	} else if (!val && data->requested) {
+		data->backoff = HZ;
+		data->requested = false;
+	} else
+		return;
+
+	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
+
+	if (!data->suspended)
+		schedule_delayed_work(&data->work, 0);
+}
+
+/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
+
+static int w2sg_uart_receive_buf(struct serdev_device *serdev,
+				const unsigned char *rxdata,
+				size_t count)
+{
+	struct w2sg_data *data =
+		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
+
+	if (!data->requested && !data->is_on) {
+		/*
+		 * we have received characters while the w2sg
+		 * should have been be turned off
+		 */
+		data->discard_count += count;
+		if ((data->state == W2SG_IDLE) &&
+		    time_after(jiffies,
+		    data->last_toggle + data->backoff)) {
+			/* Should be off by now, time to toggle again */
+			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
+				data->discard_count);
+
+			data->discard_count = 0;
+
+			data->is_on = true;
+			data->backoff *= 2;
+			if (!data->suspended)
+				schedule_delayed_work(&data->work, 0);
+		}
+	} else if (data->open_count > 0) {
+		int n;
+
+		pr_debug("w2sg00x4: push %lu chars to tty port\n",
+			(unsigned long) count);
+
+		/* pass to user-space */
+		n = tty_insert_flip_string(&data->port, rxdata, count);
+		if (n != count)
+			pr_err("w2sg00x4: did loose %lu characters\n",
+				(unsigned long) (count - n));
+		tty_flip_buffer_push(&data->port);
+		return n;
+	}
+
+	/* assume we have processed everything */
+	return count;
+}
+
+/* try to toggle the power state by sending a pulse to the on-off GPIO */
+
+static void toggle_work(struct work_struct *work)
+{
+	struct w2sg_data *data = container_of(work, struct w2sg_data,
+					      work.work);
+
+	switch (data->state) {
+	case W2SG_IDLE:
+		if (data->requested == data->is_on)
+			return;
+
+		w2sg_set_lna_power(data);	/* update LNA power state */
+		gpio_set_value_cansleep(data->on_off_gpio, 0);
+		data->state = W2SG_PULSE;
+
+		pr_debug("w2sg: power gpio ON\n");
+
+		schedule_delayed_work(&data->work,
+				      msecs_to_jiffies(10));
+		break;
+
+	case W2SG_PULSE:
+		gpio_set_value_cansleep(data->on_off_gpio, 1);
+		data->last_toggle = jiffies;
+		data->state = W2SG_NOPULSE;
+		data->is_on = !data->is_on;
+
+		pr_debug("w2sg: power gpio OFF\n");
+
+		schedule_delayed_work(&data->work,
+				      msecs_to_jiffies(10));
+		break;
+
+	case W2SG_NOPULSE:
+		data->state = W2SG_IDLE;
+
+		pr_debug("w2sg: idle\n");
+
+		break;
+
+	}
+}
+
+static int w2sg_rfkill_set_block(void *pdata, bool blocked)
+{
+	struct w2sg_data *data = pdata;
+
+	pr_debug("%s: blocked: %d\n", __func__, blocked);
+
+	data->lna_blocked = blocked;
+
+	return w2sg_set_lna_power(data);
+}
+
+static struct rfkill_ops w2sg0004_rfkill_ops = {
+	.set_block = w2sg_rfkill_set_block,
+};
+
+static struct serdev_device_ops serdev_ops = {
+	.receive_buf = w2sg_uart_receive_buf,
+};
+
+/*
+ * we are a man-in the middle between the user-space visible tty port
+ * and the serdev tty where the chip is connected.
+ * This allows us to recognise when the device should be powered on
+ * or off and handle the "false" state that data arrives while no
+ * users-space tty client exists.
+ */
+
+static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
+{
+	return w2sg_by_minor[minor];
+}
+
+static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+	struct w2sg_data *data;
+	int retval;
+
+	pr_debug("%s() tty = %p\n", __func__, tty);
+
+	data = w2sg_get_by_minor(tty->index);
+
+	if (!data)
+		return -ENODEV;
+
+	retval = tty_standard_install(driver, tty);
+	if (retval)
+		goto error_init_termios;
+
+	tty->driver_data = data;
+
+	return 0;
+
+error_init_termios:
+	tty_port_put(&data->port);
+	return retval;
+}
+
+static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
+{
+	struct w2sg_data *data = tty->driver_data;
+
+	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
+
+	w2sg_set_power(data, ++data->open_count > 0);
+
+	return tty_port_open(&data->port, tty, file);
+}
+
+static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
+{
+	struct w2sg_data *data = tty->driver_data;
+
+	pr_debug("%s()\n", __func__);
+
+	w2sg_set_power(data, --data->open_count > 0);
+
+	tty_port_close(&data->port, tty, file);
+}
+
+static int w2sg_tty_write(struct tty_struct *tty,
+		const unsigned char *buffer, int count)
+{
+	struct w2sg_data *data = tty->driver_data;
+
+	/* simply pass down to UART */
+	return serdev_device_write_buf(data->uart, buffer, count);
+}
+
+static const struct tty_operations w2sg_serial_ops = {
+	.install = w2sg_tty_install,
+	.open = w2sg_tty_open,
+	.close = w2sg_tty_close,
+	.write = w2sg_tty_write,
+};
+
+static const struct tty_port_operations w2sg_port_ops = {
+};
+
+static int w2sg_probe(struct serdev_device *serdev)
+{
+	struct w2sg_data *data;
+	struct rfkill *rf_kill;
+	int err;
+	int minor;
+	enum of_gpio_flags flags;
+
+	pr_debug("%s()\n", __func__);
+
+	minor = 0;
+	if (w2sg_by_minor[minor]) {
+		pr_err("w2sg minor is already in use!\n");
+		return -ENODEV;
+	}
+
+	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	w2sg_by_minor[minor] = data;
+
+	serdev_device_set_drvdata(serdev, data);
+
+	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
+						     "enable-gpios", 0,
+						     &flags);
+	/* defer until we have all gpios */
+	if (data->on_off_gpio == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+
+	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
+							"lna");
+	if (IS_ERR(data->lna_regulator)) {
+		/* defer until we can get the regulator */
+		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+
+		data->lna_regulator = NULL;
+	}
+	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
+
+	data->lna_blocked = true;
+	data->lna_is_off = true;
+
+	data->is_on = false;
+	data->requested = false;
+	data->state = W2SG_IDLE;
+	data->last_toggle = jiffies;
+	data->backoff = HZ;
+
+	data->uart = serdev;
+
+	INIT_DELAYED_WORK(&data->work, toggle_work);
+
+	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
+				"w2sg0004-on-off");
+	if (err < 0)
+		goto out;
+
+	gpio_direction_output(data->on_off_gpio, false);
+
+	serdev_device_set_client_ops(data->uart, &serdev_ops);
+	serdev_device_open(data->uart);
+
+	serdev_device_set_baudrate(data->uart, 9600);
+	serdev_device_set_flow_control(data->uart, false);
+
+	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
+				&w2sg0004_rfkill_ops, data);
+	if (rf_kill == NULL) {
+		err = -ENOMEM;
+		goto err_rfkill;
+	}
+
+	err = rfkill_register(rf_kill);
+	if (err) {
+		dev_err(&serdev->dev, "Cannot register rfkill device\n");
+		goto err_rfkill;
+	}
+
+	data->rf_kill = rf_kill;
+
+	/* allocate the tty driver */
+	data->tty_drv = alloc_tty_driver(1);
+	if (!data->tty_drv)
+		return -ENOMEM;
+
+	/* initialize the tty driver */
+	data->tty_drv->owner = THIS_MODULE;
+	data->tty_drv->driver_name = "w2sg0004";
+	data->tty_drv->name = "ttyGPS";
+	data->tty_drv->major = 0;
+	data->tty_drv->minor_start = 0;
+	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
+	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
+	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	data->tty_drv->init_termios = tty_std_termios;
+	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
+					      HUPCL | CLOCAL;
+	/*
+	 * optional:
+	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
+					115200, 115200);
+	 * w2sg_tty_termios(&data->tty_drv->init_termios);
+	 */
+	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
+
+	/* register the tty driver */
+	err = tty_register_driver(data->tty_drv);
+	if (err) {
+		pr_err("%s - tty_register_driver failed(%d)\n",
+			__func__, err);
+		put_tty_driver(data->tty_drv);
+		goto err_rfkill;
+	}
+
+	tty_port_init(&data->port);
+	data->port.ops = &w2sg_port_ops;
+
+	pr_debug("w2sg call tty_port_register_device\n");
+
+	data->dev = tty_port_register_device(&data->port,
+			data->tty_drv, minor, &serdev->dev);
+
+	pr_debug("w2sg probed\n");
+
+	/* keep off until user space requests the device */
+	w2sg_set_power(data, false);
+
+	return 0;
+
+err_rfkill:
+	rfkill_destroy(rf_kill);
+	serdev_device_close(data->uart);
+out:
+	return err;
+}
+
+static void w2sg_remove(struct serdev_device *serdev)
+{
+	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
+	int minor;
+
+	cancel_delayed_work_sync(&data->work);
+
+	/* what is the right sequence to avoid problems? */
+	serdev_device_close(data->uart);
+
+	minor = 0;
+	tty_unregister_device(data->tty_drv, minor);
+
+	tty_unregister_driver(data->tty_drv);
+}
+
+static int w2sg_suspend(struct device *dev)
+{
+	struct w2sg_data *data = dev_get_drvdata(dev);
+
+	data->suspended = true;
+
+	cancel_delayed_work_sync(&data->work);
+
+	w2sg_set_lna_power(data);	/* shuts down if needed */
+
+	if (data->state == W2SG_PULSE) {
+		msleep(10);
+		gpio_set_value_cansleep(data->on_off_gpio, 1);
+		data->last_toggle = jiffies;
+		data->is_on = !data->is_on;
+		data->state = W2SG_NOPULSE;
+	}
+
+	if (data->state == W2SG_NOPULSE) {
+		msleep(10);
+		data->state = W2SG_IDLE;
+	}
+
+	if (data->is_on) {
+		pr_info("GPS off for suspend %d %d %d\n", data->requested,
+			data->is_on, data->lna_is_off);
+
+		gpio_set_value_cansleep(data->on_off_gpio, 0);
+		msleep(10);
+		gpio_set_value_cansleep(data->on_off_gpio, 1);
+		data->is_on = 0;
+	}
+
+	return 0;
+}
+
+static int w2sg_resume(struct device *dev)
+{
+	struct w2sg_data *data = dev_get_drvdata(dev);
+
+	data->suspended = false;
+
+	pr_info("GPS resuming %d %d %d\n", data->requested,
+		data->is_on, data->lna_is_off);
+
+	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
+
+	return 0;
+}
+
+static const struct of_device_id w2sg0004_of_match[] = {
+	{ .compatible = "wi2wi,w2sg0004" },
+	{ .compatible = "wi2wi,w2sg0084" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
+
+SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
+
+static struct serdev_device_driver w2sg_driver = {
+	.probe		= w2sg_probe,
+	.remove		= w2sg_remove,
+	.driver = {
+		.name	= "w2sg0004",
+		.owner	= THIS_MODULE,
+		.pm	= &w2sg_pm_ops,
+		.of_match_table = of_match_ptr(w2sg0004_of_match)
+	},
+};
+
+module_serdev_device_driver(w2sg_driver);
+
+MODULE_ALIAS("w2sg0004");
+
+MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
+MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
+MODULE_LICENSE("GPL v2");
-- 
2.12.2

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

* [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: linux-arm-kernel

Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.

Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
and turn on/off the module. It also detects if the module is turned on (sends data)
but should be off, e.g. if it was already turned on during boot or power-on-reset.

Additionally, rfkill block/unblock can be used to control an external LNA
(and power down the module if not needed).

The driver concept is based on code developed by NeilBrown <neilb@suse.de>
but simplified and adapted to use the new serdev API introduced in 4.11.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 drivers/misc/Kconfig    |  10 +
 drivers/misc/Makefile   |   1 +
 drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 556 insertions(+)
 create mode 100644 drivers/misc/w2sg0004.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 8136dc7e863d..09d171d68408 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
 source "drivers/misc/genwqe/Kconfig"
 source "drivers/misc/echo/Kconfig"
 source "drivers/misc/cxl/Kconfig"
+
+config W2SG0004
+	tristate "W2SG00x4 on/off control"
+	depends on GPIOLIB && SERIAL_DEV_BUS
+	help
+          Enable on/off control of W2SG00x4 GPS moduled connected
+	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
+	  is opened/closed.
+	  It also provides a rfkill gps name to control the LNA power.
+
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index ad0e64fdba34..abcb667e0ff0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
 obj-y				+= mic/
 obj-$(CONFIG_GENWQE)		+= genwqe/
 obj-$(CONFIG_ECHO)		+= echo/
+obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
 obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
 obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
new file mode 100644
index 000000000000..12e14b5e0a99
--- /dev/null
+++ b/drivers/misc/w2sg0004.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
+ *
+ * This receiver has an ON/OFF pin which must be toggled to
+ * turn the device 'on' of 'off'.  A high->low->high toggle
+ * will switch the device on if it is off, and off if it is on.
+ *
+ * To enable receiving on/off requests we register with the
+ * UART power management notifications.
+ *
+ * It is not possible to directly detect the state of the device.
+ * However when it is on it will send characters on a UART line
+ * regularly.
+ *
+ * To detect that the power state is out of sync (e.g. if GPS
+ * was enabled before a reboot), we register for UART data received
+ * notifications.
+ *
+ * In addition we register as a rfkill client so that we can
+ * control the LNA power.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/rfkill.h>
+#include <linux/serdev.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/workqueue.h>
+
+/*
+ * There seems to be restrictions on how quickly we can toggle the
+ * on/off line.  data sheets says "two rtc ticks", whatever that means.
+ * If we do it too soon it doesn't work.
+ * So we have a state machine which uses the common work queue to ensure
+ * clean transitions.
+ * When a change is requested we record that request and only act on it
+ * once the previous change has completed.
+ * A change involves a 10ms low pulse, and a 990ms raised level, so only
+ * one change per second.
+ */
+
+enum w2sg_state {
+	W2SG_IDLE,	/* is not changing state */
+	W2SG_PULSE,	/* activate on/off impulse */
+	W2SG_NOPULSE	/* deactivate on/off impulse */
+};
+
+struct w2sg_data {
+	struct		rfkill *rf_kill;
+	struct		regulator *lna_regulator;
+	int		lna_blocked;	/* rfkill block gps active */
+	int		lna_is_off;	/* LNA is currently off */
+	int		is_on;		/* current state (0/1) */
+	unsigned long	last_toggle;
+	unsigned long	backoff;	/* time to wait since last_toggle */
+	int		on_off_gpio;	/* the on-off gpio number */
+	struct		serdev_device *uart;	/* uart connected to the chip */
+	struct		tty_driver *tty_drv;	/* this is the user space tty */
+	struct		device *dev;	/* from tty_port_register_device() */
+	struct		tty_port port;
+	int		open_count;	/* how often we were opened */
+	enum		w2sg_state state;
+	int		requested;	/* requested state (0/1) */
+	int		suspended;
+	struct delayed_work work;
+	int		discard_count;
+};
+
+static struct w2sg_data *w2sg_by_minor[1];
+
+static int w2sg_set_lna_power(struct w2sg_data *data)
+{
+	int ret = 0;
+	int off = data->suspended || !data->requested || data->lna_blocked;
+
+	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
+
+	if (off != data->lna_is_off) {
+		data->lna_is_off = off;
+		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
+			if (off)
+				regulator_disable(data->lna_regulator);
+			else
+				ret = regulator_enable(data->lna_regulator);
+		}
+	}
+
+	return ret;
+}
+
+static void w2sg_set_power(void *pdata, int val)
+{
+	struct w2sg_data *data = (struct w2sg_data *) pdata;
+
+	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
+
+	if (val && !data->requested) {
+		data->requested = true;
+	} else if (!val && data->requested) {
+		data->backoff = HZ;
+		data->requested = false;
+	} else
+		return;
+
+	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
+
+	if (!data->suspended)
+		schedule_delayed_work(&data->work, 0);
+}
+
+/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
+
+static int w2sg_uart_receive_buf(struct serdev_device *serdev,
+				const unsigned char *rxdata,
+				size_t count)
+{
+	struct w2sg_data *data =
+		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
+
+	if (!data->requested && !data->is_on) {
+		/*
+		 * we have received characters while the w2sg
+		 * should have been be turned off
+		 */
+		data->discard_count += count;
+		if ((data->state == W2SG_IDLE) &&
+		    time_after(jiffies,
+		    data->last_toggle + data->backoff)) {
+			/* Should be off by now, time to toggle again */
+			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
+				data->discard_count);
+
+			data->discard_count = 0;
+
+			data->is_on = true;
+			data->backoff *= 2;
+			if (!data->suspended)
+				schedule_delayed_work(&data->work, 0);
+		}
+	} else if (data->open_count > 0) {
+		int n;
+
+		pr_debug("w2sg00x4: push %lu chars to tty port\n",
+			(unsigned long) count);
+
+		/* pass to user-space */
+		n = tty_insert_flip_string(&data->port, rxdata, count);
+		if (n != count)
+			pr_err("w2sg00x4: did loose %lu characters\n",
+				(unsigned long) (count - n));
+		tty_flip_buffer_push(&data->port);
+		return n;
+	}
+
+	/* assume we have processed everything */
+	return count;
+}
+
+/* try to toggle the power state by sending a pulse to the on-off GPIO */
+
+static void toggle_work(struct work_struct *work)
+{
+	struct w2sg_data *data = container_of(work, struct w2sg_data,
+					      work.work);
+
+	switch (data->state) {
+	case W2SG_IDLE:
+		if (data->requested == data->is_on)
+			return;
+
+		w2sg_set_lna_power(data);	/* update LNA power state */
+		gpio_set_value_cansleep(data->on_off_gpio, 0);
+		data->state = W2SG_PULSE;
+
+		pr_debug("w2sg: power gpio ON\n");
+
+		schedule_delayed_work(&data->work,
+				      msecs_to_jiffies(10));
+		break;
+
+	case W2SG_PULSE:
+		gpio_set_value_cansleep(data->on_off_gpio, 1);
+		data->last_toggle = jiffies;
+		data->state = W2SG_NOPULSE;
+		data->is_on = !data->is_on;
+
+		pr_debug("w2sg: power gpio OFF\n");
+
+		schedule_delayed_work(&data->work,
+				      msecs_to_jiffies(10));
+		break;
+
+	case W2SG_NOPULSE:
+		data->state = W2SG_IDLE;
+
+		pr_debug("w2sg: idle\n");
+
+		break;
+
+	}
+}
+
+static int w2sg_rfkill_set_block(void *pdata, bool blocked)
+{
+	struct w2sg_data *data = pdata;
+
+	pr_debug("%s: blocked: %d\n", __func__, blocked);
+
+	data->lna_blocked = blocked;
+
+	return w2sg_set_lna_power(data);
+}
+
+static struct rfkill_ops w2sg0004_rfkill_ops = {
+	.set_block = w2sg_rfkill_set_block,
+};
+
+static struct serdev_device_ops serdev_ops = {
+	.receive_buf = w2sg_uart_receive_buf,
+};
+
+/*
+ * we are a man-in the middle between the user-space visible tty port
+ * and the serdev tty where the chip is connected.
+ * This allows us to recognise when the device should be powered on
+ * or off and handle the "false" state that data arrives while no
+ * users-space tty client exists.
+ */
+
+static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
+{
+	return w2sg_by_minor[minor];
+}
+
+static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+	struct w2sg_data *data;
+	int retval;
+
+	pr_debug("%s() tty = %p\n", __func__, tty);
+
+	data = w2sg_get_by_minor(tty->index);
+
+	if (!data)
+		return -ENODEV;
+
+	retval = tty_standard_install(driver, tty);
+	if (retval)
+		goto error_init_termios;
+
+	tty->driver_data = data;
+
+	return 0;
+
+error_init_termios:
+	tty_port_put(&data->port);
+	return retval;
+}
+
+static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
+{
+	struct w2sg_data *data = tty->driver_data;
+
+	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
+
+	w2sg_set_power(data, ++data->open_count > 0);
+
+	return tty_port_open(&data->port, tty, file);
+}
+
+static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
+{
+	struct w2sg_data *data = tty->driver_data;
+
+	pr_debug("%s()\n", __func__);
+
+	w2sg_set_power(data, --data->open_count > 0);
+
+	tty_port_close(&data->port, tty, file);
+}
+
+static int w2sg_tty_write(struct tty_struct *tty,
+		const unsigned char *buffer, int count)
+{
+	struct w2sg_data *data = tty->driver_data;
+
+	/* simply pass down to UART */
+	return serdev_device_write_buf(data->uart, buffer, count);
+}
+
+static const struct tty_operations w2sg_serial_ops = {
+	.install = w2sg_tty_install,
+	.open = w2sg_tty_open,
+	.close = w2sg_tty_close,
+	.write = w2sg_tty_write,
+};
+
+static const struct tty_port_operations w2sg_port_ops = {
+};
+
+static int w2sg_probe(struct serdev_device *serdev)
+{
+	struct w2sg_data *data;
+	struct rfkill *rf_kill;
+	int err;
+	int minor;
+	enum of_gpio_flags flags;
+
+	pr_debug("%s()\n", __func__);
+
+	minor = 0;
+	if (w2sg_by_minor[minor]) {
+		pr_err("w2sg minor is already in use!\n");
+		return -ENODEV;
+	}
+
+	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	w2sg_by_minor[minor] = data;
+
+	serdev_device_set_drvdata(serdev, data);
+
+	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
+						     "enable-gpios", 0,
+						     &flags);
+	/* defer until we have all gpios */
+	if (data->on_off_gpio == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+
+	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
+							"lna");
+	if (IS_ERR(data->lna_regulator)) {
+		/* defer until we can get the regulator */
+		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+
+		data->lna_regulator = NULL;
+	}
+	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
+
+	data->lna_blocked = true;
+	data->lna_is_off = true;
+
+	data->is_on = false;
+	data->requested = false;
+	data->state = W2SG_IDLE;
+	data->last_toggle = jiffies;
+	data->backoff = HZ;
+
+	data->uart = serdev;
+
+	INIT_DELAYED_WORK(&data->work, toggle_work);
+
+	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
+				"w2sg0004-on-off");
+	if (err < 0)
+		goto out;
+
+	gpio_direction_output(data->on_off_gpio, false);
+
+	serdev_device_set_client_ops(data->uart, &serdev_ops);
+	serdev_device_open(data->uart);
+
+	serdev_device_set_baudrate(data->uart, 9600);
+	serdev_device_set_flow_control(data->uart, false);
+
+	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
+				&w2sg0004_rfkill_ops, data);
+	if (rf_kill == NULL) {
+		err = -ENOMEM;
+		goto err_rfkill;
+	}
+
+	err = rfkill_register(rf_kill);
+	if (err) {
+		dev_err(&serdev->dev, "Cannot register rfkill device\n");
+		goto err_rfkill;
+	}
+
+	data->rf_kill = rf_kill;
+
+	/* allocate the tty driver */
+	data->tty_drv = alloc_tty_driver(1);
+	if (!data->tty_drv)
+		return -ENOMEM;
+
+	/* initialize the tty driver */
+	data->tty_drv->owner = THIS_MODULE;
+	data->tty_drv->driver_name = "w2sg0004";
+	data->tty_drv->name = "ttyGPS";
+	data->tty_drv->major = 0;
+	data->tty_drv->minor_start = 0;
+	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
+	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
+	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	data->tty_drv->init_termios = tty_std_termios;
+	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
+					      HUPCL | CLOCAL;
+	/*
+	 * optional:
+	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
+					115200, 115200);
+	 * w2sg_tty_termios(&data->tty_drv->init_termios);
+	 */
+	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
+
+	/* register the tty driver */
+	err = tty_register_driver(data->tty_drv);
+	if (err) {
+		pr_err("%s - tty_register_driver failed(%d)\n",
+			__func__, err);
+		put_tty_driver(data->tty_drv);
+		goto err_rfkill;
+	}
+
+	tty_port_init(&data->port);
+	data->port.ops = &w2sg_port_ops;
+
+	pr_debug("w2sg call tty_port_register_device\n");
+
+	data->dev = tty_port_register_device(&data->port,
+			data->tty_drv, minor, &serdev->dev);
+
+	pr_debug("w2sg probed\n");
+
+	/* keep off until user space requests the device */
+	w2sg_set_power(data, false);
+
+	return 0;
+
+err_rfkill:
+	rfkill_destroy(rf_kill);
+	serdev_device_close(data->uart);
+out:
+	return err;
+}
+
+static void w2sg_remove(struct serdev_device *serdev)
+{
+	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
+	int minor;
+
+	cancel_delayed_work_sync(&data->work);
+
+	/* what is the right sequence to avoid problems? */
+	serdev_device_close(data->uart);
+
+	minor = 0;
+	tty_unregister_device(data->tty_drv, minor);
+
+	tty_unregister_driver(data->tty_drv);
+}
+
+static int w2sg_suspend(struct device *dev)
+{
+	struct w2sg_data *data = dev_get_drvdata(dev);
+
+	data->suspended = true;
+
+	cancel_delayed_work_sync(&data->work);
+
+	w2sg_set_lna_power(data);	/* shuts down if needed */
+
+	if (data->state == W2SG_PULSE) {
+		msleep(10);
+		gpio_set_value_cansleep(data->on_off_gpio, 1);
+		data->last_toggle = jiffies;
+		data->is_on = !data->is_on;
+		data->state = W2SG_NOPULSE;
+	}
+
+	if (data->state == W2SG_NOPULSE) {
+		msleep(10);
+		data->state = W2SG_IDLE;
+	}
+
+	if (data->is_on) {
+		pr_info("GPS off for suspend %d %d %d\n", data->requested,
+			data->is_on, data->lna_is_off);
+
+		gpio_set_value_cansleep(data->on_off_gpio, 0);
+		msleep(10);
+		gpio_set_value_cansleep(data->on_off_gpio, 1);
+		data->is_on = 0;
+	}
+
+	return 0;
+}
+
+static int w2sg_resume(struct device *dev)
+{
+	struct w2sg_data *data = dev_get_drvdata(dev);
+
+	data->suspended = false;
+
+	pr_info("GPS resuming %d %d %d\n", data->requested,
+		data->is_on, data->lna_is_off);
+
+	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
+
+	return 0;
+}
+
+static const struct of_device_id w2sg0004_of_match[] = {
+	{ .compatible = "wi2wi,w2sg0004" },
+	{ .compatible = "wi2wi,w2sg0084" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
+
+SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
+
+static struct serdev_device_driver w2sg_driver = {
+	.probe		= w2sg_probe,
+	.remove		= w2sg_remove,
+	.driver = {
+		.name	= "w2sg0004",
+		.owner	= THIS_MODULE,
+		.pm	= &w2sg_pm_ops,
+		.of_match_table = of_match_ptr(w2sg0004_of_match)
+	},
+};
+
+module_serdev_device_driver(w2sg_driver);
+
+MODULE_ALIAS("w2sg0004");
+
+MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
+MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
+MODULE_LICENSE("GPL v2");
-- 
2.12.2

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

* [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
processor. A GPIO can pulse the on/off toggle switch.

The VSIM regulator is used to power on/off the LNA of an external
active GPS antenna so that a driver can turn the LNA off if GPS is
not needed to save battery energy.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
index 4504908c23fe..e967d738aaaf 100644
--- a/arch/arm/boot/dts/omap3-gta04.dtsi
+++ b/arch/arm/boot/dts/omap3-gta04.dtsi
@@ -477,6 +477,12 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
+
+	gps: gps {
+		compatible = "wi2wi,w2sg0004";
+		lna-supply = <&vsim>;   /* LNA regulator */
+		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
+	};
 };
 
 &uart3 {
-- 
2.12.2

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

* [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA,
	letux-kernel-S0jZdbWzriLCfDggNXIi3w,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
processor. A GPIO can pulse the on/off toggle switch.

The VSIM regulator is used to power on/off the LNA of an external
active GPS antenna so that a driver can turn the LNA off if GPS is
not needed to save battery energy.

Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
---
 arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
index 4504908c23fe..e967d738aaaf 100644
--- a/arch/arm/boot/dts/omap3-gta04.dtsi
+++ b/arch/arm/boot/dts/omap3-gta04.dtsi
@@ -477,6 +477,12 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
+
+	gps: gps {
+		compatible = "wi2wi,w2sg0004";
+		lna-supply = <&vsim>;   /* LNA regulator */
+		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
+	};
 };
 
 &uart3 {
-- 
2.12.2

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: linux-arm-kernel

GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
processor. A GPIO can pulse the on/off toggle switch.

The VSIM regulator is used to power on/off the LNA of an external
active GPS antenna so that a driver can turn the LNA off if GPS is
not needed to save battery energy.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
index 4504908c23fe..e967d738aaaf 100644
--- a/arch/arm/boot/dts/omap3-gta04.dtsi
+++ b/arch/arm/boot/dts/omap3-gta04.dtsi
@@ -477,6 +477,12 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
+
+	gps: gps {
+		compatible = "wi2wi,w2sg0004";
+		lna-supply = <&vsim>;   /* LNA regulator */
+		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
+	};
 };
 
 &uart3 {
-- 
2.12.2

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

* [PATCH v4 5/5] misc serdev: w2sg0004: add debugging code and Kconfig
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

This allows to set CONFIG_W2SG0004_DEBUG which will
make the driver report more activities and it will turn on the
GPS module during boot while the driver assumes that it
is off. This can be used to debug the correct functioning of
the hardware. Therefore we add it as an option to the driver
because we think it is of general use (and a little tricky to
add by system testers).

Normally it should be off.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 drivers/misc/Kconfig    |  8 ++++++++
 drivers/misc/w2sg0004.c | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 09d171d68408..f15ff5a97e42 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -528,4 +528,12 @@ config W2SG0004
 	  is opened/closed.
 	  It also provides a rfkill gps name to control the LNA power.
 
+config W2SG0004_DEBUG
+	bool "W2SG0004 on/off debugging"
+	depends on W2SG0004
+	help
+	  Enable driver debugging mode of W2SG0004 GPS. If you say y here
+	  this will turn on the module and you can check if it is turned
+	  off by the driver.
+
 endmenu
diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
index 12e14b5e0a99..51ddc807eac5 100644
--- a/drivers/misc/w2sg0004.c
+++ b/drivers/misc/w2sg0004.c
@@ -22,6 +22,10 @@
  *
  */
 
+#ifdef CONFIG_W2SG0004_DEBUG
+#define DEBUG 1
+#endif
+
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/interrupt.h>
@@ -253,6 +257,7 @@ static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
 	pr_debug("%s() tty = %p\n", __func__, tty);
 
 	data = w2sg_get_by_minor(tty->index);
+	pr_debug("%s() data = %p\n", __func__, data);
 
 	if (!data)
 		return -ENODEV;
@@ -333,6 +338,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	w2sg_by_minor[minor] = data;
 
+	pr_debug("w2sg serdev_device_set_drvdata\n");
+
 	serdev_device_set_drvdata(serdev, data);
 
 	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
@@ -366,6 +373,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	INIT_DELAYED_WORK(&data->work, toggle_work);
 
+	pr_debug("w2sg devm_gpio_request\n");
+
 	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
 				"w2sg0004-on-off");
 	if (err < 0)
@@ -379,6 +388,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	serdev_device_set_baudrate(data->uart, 9600);
 	serdev_device_set_flow_control(data->uart, false);
 
+	pr_debug("w2sg rfkill_alloc\n");
+
 	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
 				&w2sg0004_rfkill_ops, data);
 	if (rf_kill == NULL) {
@@ -386,6 +397,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 		goto err_rfkill;
 	}
 
+	pr_debug("w2sg register rfkill\n");
+
 	err = rfkill_register(rf_kill);
 	if (err) {
 		dev_err(&serdev->dev, "Cannot register rfkill device\n");
@@ -394,6 +407,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	data->rf_kill = rf_kill;
 
+	pr_debug("w2sg alloc_tty_driver\n");
+
 	/* allocate the tty driver */
 	data->tty_drv = alloc_tty_driver(1);
 	if (!data->tty_drv)
@@ -419,6 +434,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	 */
 	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
 
+	pr_debug("w2sg tty_register_driver\n");
+
 	/* register the tty driver */
 	err = tty_register_driver(data->tty_drv);
 	if (err) {
@@ -428,16 +445,34 @@ static int w2sg_probe(struct serdev_device *serdev)
 		goto err_rfkill;
 	}
 
+	pr_debug("w2sg call tty_port_init\n");
+
 	tty_port_init(&data->port);
 	data->port.ops = &w2sg_port_ops;
 
 	pr_debug("w2sg call tty_port_register_device\n");
 
+	pr_debug("w2sg call tty_port_register_device\n");
+
 	data->dev = tty_port_register_device(&data->port,
 			data->tty_drv, minor, &serdev->dev);
 
+	pr_debug("w2sg tty_port_register_device -> %p\n", data->dev);
+	pr_debug("w2sg port.tty = %p\n", data->port.tty);
+
 	pr_debug("w2sg probed\n");
 
+#ifdef CONFIG_W2SG0004_DEBUG
+	pr_debug("w2sg DEBUGGING MODE enabled\n");
+	/* turn on for debugging rx notifications */
+	pr_debug("w2sg power gpio ON\n");
+	gpio_set_value_cansleep(data->on_off_gpio, 1);
+	mdelay(100);
+	pr_debug("w2sg power gpio OFF\n");
+	gpio_set_value_cansleep(data->on_off_gpio, 0);
+	mdelay(300);
+#endif
+
 	/* keep off until user space requests the device */
 	w2sg_set_power(data, false);
 
@@ -447,6 +482,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	rfkill_destroy(rf_kill);
 	serdev_device_close(data->uart);
 out:
+	pr_debug("w2sg error %d\n", err);
+
 	return err;
 }
 
-- 
2.12.2

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

* [PATCH v4 5/5] misc serdev: w2sg0004: add debugging code and Kconfig
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman,
	H. Nikolaus Schaller, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA,
	letux-kernel-S0jZdbWzriLCfDggNXIi3w,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

This allows to set CONFIG_W2SG0004_DEBUG which will
make the driver report more activities and it will turn on the
GPS module during boot while the driver assumes that it
is off. This can be used to debug the correct functioning of
the hardware. Therefore we add it as an option to the driver
because we think it is of general use (and a little tricky to
add by system testers).

Normally it should be off.

Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
---
 drivers/misc/Kconfig    |  8 ++++++++
 drivers/misc/w2sg0004.c | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 09d171d68408..f15ff5a97e42 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -528,4 +528,12 @@ config W2SG0004
 	  is opened/closed.
 	  It also provides a rfkill gps name to control the LNA power.
 
+config W2SG0004_DEBUG
+	bool "W2SG0004 on/off debugging"
+	depends on W2SG0004
+	help
+	  Enable driver debugging mode of W2SG0004 GPS. If you say y here
+	  this will turn on the module and you can check if it is turned
+	  off by the driver.
+
 endmenu
diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
index 12e14b5e0a99..51ddc807eac5 100644
--- a/drivers/misc/w2sg0004.c
+++ b/drivers/misc/w2sg0004.c
@@ -22,6 +22,10 @@
  *
  */
 
+#ifdef CONFIG_W2SG0004_DEBUG
+#define DEBUG 1
+#endif
+
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/interrupt.h>
@@ -253,6 +257,7 @@ static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
 	pr_debug("%s() tty = %p\n", __func__, tty);
 
 	data = w2sg_get_by_minor(tty->index);
+	pr_debug("%s() data = %p\n", __func__, data);
 
 	if (!data)
 		return -ENODEV;
@@ -333,6 +338,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	w2sg_by_minor[minor] = data;
 
+	pr_debug("w2sg serdev_device_set_drvdata\n");
+
 	serdev_device_set_drvdata(serdev, data);
 
 	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
@@ -366,6 +373,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	INIT_DELAYED_WORK(&data->work, toggle_work);
 
+	pr_debug("w2sg devm_gpio_request\n");
+
 	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
 				"w2sg0004-on-off");
 	if (err < 0)
@@ -379,6 +388,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	serdev_device_set_baudrate(data->uart, 9600);
 	serdev_device_set_flow_control(data->uart, false);
 
+	pr_debug("w2sg rfkill_alloc\n");
+
 	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
 				&w2sg0004_rfkill_ops, data);
 	if (rf_kill == NULL) {
@@ -386,6 +397,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 		goto err_rfkill;
 	}
 
+	pr_debug("w2sg register rfkill\n");
+
 	err = rfkill_register(rf_kill);
 	if (err) {
 		dev_err(&serdev->dev, "Cannot register rfkill device\n");
@@ -394,6 +407,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	data->rf_kill = rf_kill;
 
+	pr_debug("w2sg alloc_tty_driver\n");
+
 	/* allocate the tty driver */
 	data->tty_drv = alloc_tty_driver(1);
 	if (!data->tty_drv)
@@ -419,6 +434,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	 */
 	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
 
+	pr_debug("w2sg tty_register_driver\n");
+
 	/* register the tty driver */
 	err = tty_register_driver(data->tty_drv);
 	if (err) {
@@ -428,16 +445,34 @@ static int w2sg_probe(struct serdev_device *serdev)
 		goto err_rfkill;
 	}
 
+	pr_debug("w2sg call tty_port_init\n");
+
 	tty_port_init(&data->port);
 	data->port.ops = &w2sg_port_ops;
 
 	pr_debug("w2sg call tty_port_register_device\n");
 
+	pr_debug("w2sg call tty_port_register_device\n");
+
 	data->dev = tty_port_register_device(&data->port,
 			data->tty_drv, minor, &serdev->dev);
 
+	pr_debug("w2sg tty_port_register_device -> %p\n", data->dev);
+	pr_debug("w2sg port.tty = %p\n", data->port.tty);
+
 	pr_debug("w2sg probed\n");
 
+#ifdef CONFIG_W2SG0004_DEBUG
+	pr_debug("w2sg DEBUGGING MODE enabled\n");
+	/* turn on for debugging rx notifications */
+	pr_debug("w2sg power gpio ON\n");
+	gpio_set_value_cansleep(data->on_off_gpio, 1);
+	mdelay(100);
+	pr_debug("w2sg power gpio OFF\n");
+	gpio_set_value_cansleep(data->on_off_gpio, 0);
+	mdelay(300);
+#endif
+
 	/* keep off until user space requests the device */
 	w2sg_set_power(data, false);
 
@@ -447,6 +482,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	rfkill_destroy(rf_kill);
 	serdev_device_close(data->uart);
 out:
+	pr_debug("w2sg error %d\n", err);
+
 	return err;
 }
 
-- 
2.12.2

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v4 5/5] misc serdev: w2sg0004: add debugging code and Kconfig
@ 2017-11-15 21:37   ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-15 21:37 UTC (permalink / raw)
  To: linux-arm-kernel

This allows to set CONFIG_W2SG0004_DEBUG which will
make the driver report more activities and it will turn on the
GPS module during boot while the driver assumes that it
is off. This can be used to debug the correct functioning of
the hardware. Therefore we add it as an option to the driver
because we think it is of general use (and a little tricky to
add by system testers).

Normally it should be off.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 drivers/misc/Kconfig    |  8 ++++++++
 drivers/misc/w2sg0004.c | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 09d171d68408..f15ff5a97e42 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -528,4 +528,12 @@ config W2SG0004
 	  is opened/closed.
 	  It also provides a rfkill gps name to control the LNA power.
 
+config W2SG0004_DEBUG
+	bool "W2SG0004 on/off debugging"
+	depends on W2SG0004
+	help
+	  Enable driver debugging mode of W2SG0004 GPS. If you say y here
+	  this will turn on the module and you can check if it is turned
+	  off by the driver.
+
 endmenu
diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
index 12e14b5e0a99..51ddc807eac5 100644
--- a/drivers/misc/w2sg0004.c
+++ b/drivers/misc/w2sg0004.c
@@ -22,6 +22,10 @@
  *
  */
 
+#ifdef CONFIG_W2SG0004_DEBUG
+#define DEBUG 1
+#endif
+
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/interrupt.h>
@@ -253,6 +257,7 @@ static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
 	pr_debug("%s() tty = %p\n", __func__, tty);
 
 	data = w2sg_get_by_minor(tty->index);
+	pr_debug("%s() data = %p\n", __func__, data);
 
 	if (!data)
 		return -ENODEV;
@@ -333,6 +338,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	w2sg_by_minor[minor] = data;
 
+	pr_debug("w2sg serdev_device_set_drvdata\n");
+
 	serdev_device_set_drvdata(serdev, data);
 
 	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
@@ -366,6 +373,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	INIT_DELAYED_WORK(&data->work, toggle_work);
 
+	pr_debug("w2sg devm_gpio_request\n");
+
 	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
 				"w2sg0004-on-off");
 	if (err < 0)
@@ -379,6 +388,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	serdev_device_set_baudrate(data->uart, 9600);
 	serdev_device_set_flow_control(data->uart, false);
 
+	pr_debug("w2sg rfkill_alloc\n");
+
 	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
 				&w2sg0004_rfkill_ops, data);
 	if (rf_kill == NULL) {
@@ -386,6 +397,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 		goto err_rfkill;
 	}
 
+	pr_debug("w2sg register rfkill\n");
+
 	err = rfkill_register(rf_kill);
 	if (err) {
 		dev_err(&serdev->dev, "Cannot register rfkill device\n");
@@ -394,6 +407,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 
 	data->rf_kill = rf_kill;
 
+	pr_debug("w2sg alloc_tty_driver\n");
+
 	/* allocate the tty driver */
 	data->tty_drv = alloc_tty_driver(1);
 	if (!data->tty_drv)
@@ -419,6 +434,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	 */
 	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
 
+	pr_debug("w2sg tty_register_driver\n");
+
 	/* register the tty driver */
 	err = tty_register_driver(data->tty_drv);
 	if (err) {
@@ -428,16 +445,34 @@ static int w2sg_probe(struct serdev_device *serdev)
 		goto err_rfkill;
 	}
 
+	pr_debug("w2sg call tty_port_init\n");
+
 	tty_port_init(&data->port);
 	data->port.ops = &w2sg_port_ops;
 
 	pr_debug("w2sg call tty_port_register_device\n");
 
+	pr_debug("w2sg call tty_port_register_device\n");
+
 	data->dev = tty_port_register_device(&data->port,
 			data->tty_drv, minor, &serdev->dev);
 
+	pr_debug("w2sg tty_port_register_device -> %p\n", data->dev);
+	pr_debug("w2sg port.tty = %p\n", data->port.tty);
+
 	pr_debug("w2sg probed\n");
 
+#ifdef CONFIG_W2SG0004_DEBUG
+	pr_debug("w2sg DEBUGGING MODE enabled\n");
+	/* turn on for debugging rx notifications */
+	pr_debug("w2sg power gpio ON\n");
+	gpio_set_value_cansleep(data->on_off_gpio, 1);
+	mdelay(100);
+	pr_debug("w2sg power gpio OFF\n");
+	gpio_set_value_cansleep(data->on_off_gpio, 0);
+	mdelay(300);
+#endif
+
 	/* keep off until user space requests the device */
 	w2sg_set_power(data, false);
 
@@ -447,6 +482,8 @@ static int w2sg_probe(struct serdev_device *serdev)
 	rfkill_destroy(rf_kill);
 	serdev_device_close(data->uart);
 out:
+	pr_debug("w2sg error %d\n", err);
+
 	return err;
 }
 
-- 
2.12.2

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

* Re: [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-17 17:08     ` Tony Lindgren
  0 siblings, 0 replies; 33+ messages in thread
From: Tony Lindgren @ 2017-11-17 17:08 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Rob Herring, Mark Rutland, Benoît Cousson, Russell King,
	Arnd Bergmann, Greg Kroah-Hartman, Kevin Hilman,
	Andreas Färber, Thierry Reding, Jonathan Cameron,
	devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

* H. Nikolaus Schaller <hns@goldelico.com> [171115 21:41]:
> GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
> processor. A GPIO can pulse the on/off toggle switch.
> 
> The VSIM regulator is used to power on/off the LNA of an external
> active GPS antenna so that a driver can turn the LNA off if GPS is
> not needed to save battery energy.
> 
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
>  arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
> index 4504908c23fe..e967d738aaaf 100644
> --- a/arch/arm/boot/dts/omap3-gta04.dtsi
> +++ b/arch/arm/boot/dts/omap3-gta04.dtsi
> @@ -477,6 +477,12 @@
>  &uart2 {
>  	pinctrl-names = "default";
>  	pinctrl-0 = <&uart2_pins>;
> +
> +	gps: gps {
> +		compatible = "wi2wi,w2sg0004";
> +		lna-supply = <&vsim>;   /* LNA regulator */
> +		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
> +	};
>  };

How about move the comment above the node to avoid long lines?
Or just change it to gpio_145 for the comment as the property
is already enable-gpios?

Regards,

Tony

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

* Re: [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-17 17:08     ` Tony Lindgren
  0 siblings, 0 replies; 33+ messages in thread
From: Tony Lindgren @ 2017-11-17 17:08 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Rob Herring, Mark Rutland, Benoît Cousson, Russell King,
	Arnd Bergmann, Greg Kroah-Hartman, Kevin Hilman,
	Andreas Färber, Thierry Reding, Jonathan Cameron,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA,
	letux-kernel-S0jZdbWzriLCfDggNXIi3w,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

* H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org> [171115 21:41]:
> GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
> processor. A GPIO can pulse the on/off toggle switch.
> 
> The VSIM regulator is used to power on/off the LNA of an external
> active GPS antenna so that a driver can turn the LNA off if GPS is
> not needed to save battery energy.
> 
> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
> ---
>  arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
> index 4504908c23fe..e967d738aaaf 100644
> --- a/arch/arm/boot/dts/omap3-gta04.dtsi
> +++ b/arch/arm/boot/dts/omap3-gta04.dtsi
> @@ -477,6 +477,12 @@
>  &uart2 {
>  	pinctrl-names = "default";
>  	pinctrl-0 = <&uart2_pins>;
> +
> +	gps: gps {
> +		compatible = "wi2wi,w2sg0004";
> +		lna-supply = <&vsim>;   /* LNA regulator */
> +		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
> +	};
>  };

How about move the comment above the node to avoid long lines?
Or just change it to gpio_145 for the comment as the property
is already enable-gpios?

Regards,

Tony
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-17 17:08     ` Tony Lindgren
  0 siblings, 0 replies; 33+ messages in thread
From: Tony Lindgren @ 2017-11-17 17:08 UTC (permalink / raw)
  To: linux-arm-kernel

* H. Nikolaus Schaller <hns@goldelico.com> [171115 21:41]:
> GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
> processor. A GPIO can pulse the on/off toggle switch.
> 
> The VSIM regulator is used to power on/off the LNA of an external
> active GPS antenna so that a driver can turn the LNA off if GPS is
> not needed to save battery energy.
> 
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
>  arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
> index 4504908c23fe..e967d738aaaf 100644
> --- a/arch/arm/boot/dts/omap3-gta04.dtsi
> +++ b/arch/arm/boot/dts/omap3-gta04.dtsi
> @@ -477,6 +477,12 @@
>  &uart2 {
>  	pinctrl-names = "default";
>  	pinctrl-0 = <&uart2_pins>;
> +
> +	gps: gps {
> +		compatible = "wi2wi,w2sg0004";
> +		lna-supply = <&vsim>;   /* LNA regulator */
> +		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
> +	};
>  };

How about move the comment above the node to avoid long lines?
Or just change it to gpio_145 for the comment as the property
is already enable-gpios?

Regards,

Tony

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

* Re: [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-20  5:29       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-20  5:29 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: Rob Herring, Mark Rutland, Benoît Cousson, Russell King,
	Arnd Bergmann, Greg Kroah-Hartman, Kevin Hilman,
	Andreas Färber, Thierry Reding, Jonathan Cameron,
	devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

Hi Tony,

> Am 17.11.2017 um 18:08 schrieb Tony Lindgren <tony@atomide.com>:
> 
> * H. Nikolaus Schaller <hns@goldelico.com> [171115 21:41]:
>> GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
>> processor. A GPIO can pulse the on/off toggle switch.
>> 
>> The VSIM regulator is used to power on/off the LNA of an external
>> active GPS antenna so that a driver can turn the LNA off if GPS is
>> not needed to save battery energy.
>> 
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> ---
>> arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
>> 1 file changed, 6 insertions(+)
>> 
>> diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
>> index 4504908c23fe..e967d738aaaf 100644
>> --- a/arch/arm/boot/dts/omap3-gta04.dtsi
>> +++ b/arch/arm/boot/dts/omap3-gta04.dtsi
>> @@ -477,6 +477,12 @@
>> &uart2 {
>> 	pinctrl-names = "default";
>> 	pinctrl-0 = <&uart2_pins>;
>> +
>> +	gps: gps {
>> +		compatible = "wi2wi,w2sg0004";
>> +		lna-supply = <&vsim>;   /* LNA regulator */
>> +		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
>> +	};
>> };
> 
> How about move the comment above the node to avoid long lines?
> Or just change it to gpio_145 for the comment as the property
> is already enable-gpios?
> 
> Regards,
> 
> Tony

Done so that it will be included in next version.

BR and thanks,
Nikolaus

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

* Re: [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-20  5:29       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-20  5:29 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: Rob Herring, Mark Rutland, Benoît Cousson, Russell King,
	Arnd Bergmann, Greg Kroah-Hartman, Kevin Hilman,
	Andreas Färber, Thierry Reding, Jonathan Cameron,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-omap-u79uwXL29TY76Z2rM5mHXA,
	letux-kernel-S0jZdbWzriLCfDggNXIi3w,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r

Hi Tony,

> Am 17.11.2017 um 18:08 schrieb Tony Lindgren <tony-4v6yS6AI5VpBDgjK7y7TUQ@public.gmane.org>:
> 
> * H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org> [171115 21:41]:
>> GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
>> processor. A GPIO can pulse the on/off toggle switch.
>> 
>> The VSIM regulator is used to power on/off the LNA of an external
>> active GPS antenna so that a driver can turn the LNA off if GPS is
>> not needed to save battery energy.
>> 
>> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
>> ---
>> arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
>> 1 file changed, 6 insertions(+)
>> 
>> diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
>> index 4504908c23fe..e967d738aaaf 100644
>> --- a/arch/arm/boot/dts/omap3-gta04.dtsi
>> +++ b/arch/arm/boot/dts/omap3-gta04.dtsi
>> @@ -477,6 +477,12 @@
>> &uart2 {
>> 	pinctrl-names = "default";
>> 	pinctrl-0 = <&uart2_pins>;
>> +
>> +	gps: gps {
>> +		compatible = "wi2wi,w2sg0004";
>> +		lna-supply = <&vsim>;   /* LNA regulator */
>> +		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
>> +	};
>> };
> 
> How about move the comment above the node to avoid long lines?
> Or just change it to gpio_145 for the comment as the property
> is already enable-gpios?
> 
> Regards,
> 
> Tony

Done so that it will be included in next version.

BR and thanks,
Nikolaus

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4
@ 2017-11-20  5:29       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-20  5:29 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Tony,

> Am 17.11.2017 um 18:08 schrieb Tony Lindgren <tony@atomide.com>:
> 
> * H. Nikolaus Schaller <hns@goldelico.com> [171115 21:41]:
>> GTA04 has a W2SG0004/84 connected to UART2 of the OMAP3
>> processor. A GPIO can pulse the on/off toggle switch.
>> 
>> The VSIM regulator is used to power on/off the LNA of an external
>> active GPS antenna so that a driver can turn the LNA off if GPS is
>> not needed to save battery energy.
>> 
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> ---
>> arch/arm/boot/dts/omap3-gta04.dtsi | 6 ++++++
>> 1 file changed, 6 insertions(+)
>> 
>> diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
>> index 4504908c23fe..e967d738aaaf 100644
>> --- a/arch/arm/boot/dts/omap3-gta04.dtsi
>> +++ b/arch/arm/boot/dts/omap3-gta04.dtsi
>> @@ -477,6 +477,12 @@
>> &uart2 {
>> 	pinctrl-names = "default";
>> 	pinctrl-0 = <&uart2_pins>;
>> +
>> +	gps: gps {
>> +		compatible = "wi2wi,w2sg0004";
>> +		lna-supply = <&vsim>;   /* LNA regulator */
>> +		enable-gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>;    /* GPIO_145: trigger for turning on/off w2sg0004 */
>> +	};
>> };
> 
> How about move the comment above the node to avoid long lines?
> Or just change it to gpio_145 for the comment as the property
> is already enable-gpios?
> 
> Regards,
> 
> Tony

Done so that it will be included in next version.

BR and thanks,
Nikolaus

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

* Re: [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
  2017-11-15 21:37   ` H. Nikolaus Schaller
  (?)
@ 2017-11-23 16:06     ` Andrew F. Davis
  -1 siblings, 0 replies; 33+ messages in thread
From: Andrew F. Davis @ 2017-11-23 16:06 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Rob Herring, Mark Rutland,
	Benoît Cousson, Tony Lindgren, Russell King, Arnd Bergmann,
	Greg Kroah-Hartman, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-kernel, linux-omap, letux-kernel, kernel,
	linux-arm-kernel

On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
> 
> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
> and turn on/off the module. It also detects if the module is turned on (sends data)
> but should be off, e.g. if it was already turned on during boot or power-on-reset.
> 
> Additionally, rfkill block/unblock can be used to control an external LNA
> (and power down the module if not needed).
> 
> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
> but simplified and adapted to use the new serdev API introduced in 4.11.
> 
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
>  drivers/misc/Kconfig    |  10 +
>  drivers/misc/Makefile   |   1 +
>  drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 556 insertions(+)
>  create mode 100644 drivers/misc/w2sg0004.c
> 
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 8136dc7e863d..09d171d68408 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>  source "drivers/misc/genwqe/Kconfig"
>  source "drivers/misc/echo/Kconfig"
>  source "drivers/misc/cxl/Kconfig"
> +
> +config W2SG0004
> +	tristate "W2SG00x4 on/off control"
> +	depends on GPIOLIB && SERIAL_DEV_BUS
> +	help
> +          Enable on/off control of W2SG00x4 GPS moduled connected
> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
> +	  is opened/closed.
> +	  It also provides a rfkill gps name to control the LNA power.
> +
>  endmenu
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index ad0e64fdba34..abcb667e0ff0 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>  obj-y				+= mic/
>  obj-$(CONFIG_GENWQE)		+= genwqe/
>  obj-$(CONFIG_ECHO)		+= echo/
> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>  obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>  obj-$(CONFIG_CXL_BASE)		+= cxl/
>  obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
> new file mode 100644
> index 000000000000..12e14b5e0a99
> --- /dev/null
> +++ b/drivers/misc/w2sg0004.c
> @@ -0,0 +1,545 @@
> +// SPDX-License-Identifier: GPL-2.0

Damn this looks ugly, oh well :/

> +/*
> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
> + *


Think you still need copyright tag here somewhere.

> + * This receiver has an ON/OFF pin which must be toggled to
> + * turn the device 'on' of 'off'.  A high->low->high toggle
> + * will switch the device on if it is off, and off if it is on.
> + *
> + * To enable receiving on/off requests we register with the
> + * UART power management notifications.
> + *
> + * It is not possible to directly detect the state of the device.
> + * However when it is on it will send characters on a UART line
> + * regularly.
> + *
> + * To detect that the power state is out of sync (e.g. if GPS
> + * was enabled before a reboot), we register for UART data received
> + * notifications.
> + *
> + * In addition we register as a rfkill client so that we can
> + * control the LNA power.
> + *
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/rfkill.h>
> +#include <linux/serdev.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +#include <linux/workqueue.h>
> +
> +/*
> + * There seems to be restrictions on how quickly we can toggle the
> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
> + * If we do it too soon it doesn't work.
> + * So we have a state machine which uses the common work queue to ensure
> + * clean transitions.
> + * When a change is requested we record that request and only act on it
> + * once the previous change has completed.
> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
> + * one change per second.
> + */
> +
> +enum w2sg_state {
> +	W2SG_IDLE,	/* is not changing state */
> +	W2SG_PULSE,	/* activate on/off impulse */
> +	W2SG_NOPULSE	/* deactivate on/off impulse */
> +};
> +
> +struct w2sg_data {
> +	struct		rfkill *rf_kill;
> +	struct		regulator *lna_regulator;
> +	int		lna_blocked;	/* rfkill block gps active */
> +	int		lna_is_off;	/* LNA is currently off */
> +	int		is_on;		/* current state (0/1) */
> +	unsigned long	last_toggle;
> +	unsigned long	backoff;	/* time to wait since last_toggle */
> +	int		on_off_gpio;	/* the on-off gpio number */
> +	struct		serdev_device *uart;	/* uart connected to the chip */
> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
> +	struct		device *dev;	/* from tty_port_register_device() */
> +	struct		tty_port port;
> +	int		open_count;	/* how often we were opened */
> +	enum		w2sg_state state;
> +	int		requested;	/* requested state (0/1) */
> +	int		suspended;
> +	struct delayed_work work;
> +	int		discard_count;
> +};
> +

Kernel doc style.

> +static struct w2sg_data *w2sg_by_minor[1];
> +

If you can only have one right now then just drop the array.

> +static int w2sg_set_lna_power(struct w2sg_data *data)
> +{
> +	int ret = 0;
> +	int off = data->suspended || !data->requested || data->lna_blocked;
> +
> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
> +
> +	if (off != data->lna_is_off) {
> +		data->lna_is_off = off;
> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
> +			if (off)
> +				regulator_disable(data->lna_regulator);
> +			else
> +				ret = regulator_enable(data->lna_regulator);
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static void w2sg_set_power(void *pdata, int val)
> +{
> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
> +
> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
> +
> +	if (val && !data->requested) {
> +		data->requested = true;
> +	} else if (!val && data->requested) {
> +		data->backoff = HZ;
> +		data->requested = false;
> +	} else
> +		return;
> +
> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
> +
> +	if (!data->suspended)
> +		schedule_delayed_work(&data->work, 0);
> +}
> +
> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
> +
> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
> +				const unsigned char *rxdata,
> +				size_t count)
> +{
> +	struct w2sg_data *data =
> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
> +
> +	if (!data->requested && !data->is_on) {
> +		/*
> +		 * we have received characters while the w2sg
> +		 * should have been be turned off
> +		 */
> +		data->discard_count += count;
> +		if ((data->state == W2SG_IDLE) &&
> +		    time_after(jiffies,
> +		    data->last_toggle + data->backoff)) {
> +			/* Should be off by now, time to toggle again */
> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
> +				data->discard_count);
> +
> +			data->discard_count = 0;
> +
> +			data->is_on = true;
> +			data->backoff *= 2;
> +			if (!data->suspended)
> +				schedule_delayed_work(&data->work, 0);
> +		}
> +	} else if (data->open_count > 0) {
> +		int n;
> +
> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
> +			(unsigned long) count);
> +
> +		/* pass to user-space */
> +		n = tty_insert_flip_string(&data->port, rxdata, count);
> +		if (n != count)
> +			pr_err("w2sg00x4: did loose %lu characters\n",
> +				(unsigned long) (count - n));
> +		tty_flip_buffer_push(&data->port);
> +		return n;
> +	}
> +
> +	/* assume we have processed everything */
> +	return count;
> +}
> +
> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
> +

drop extra line, same everywhere

> +static void toggle_work(struct work_struct *work)
> +{
> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
> +					      work.work);
> +
> +	switch (data->state) {
> +	case W2SG_IDLE:
> +		if (data->requested == data->is_on)
> +			return;
> +
> +		w2sg_set_lna_power(data);	/* update LNA power state */
> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
> +		data->state = W2SG_PULSE;
> +
> +		pr_debug("w2sg: power gpio ON\n");
> +
> +		schedule_delayed_work(&data->work,
> +				      msecs_to_jiffies(10));
> +		break;
> +
> +	case W2SG_PULSE:
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->last_toggle = jiffies;
> +		data->state = W2SG_NOPULSE;
> +		data->is_on = !data->is_on;
> +
> +		pr_debug("w2sg: power gpio OFF\n");
> +
> +		schedule_delayed_work(&data->work,
> +				      msecs_to_jiffies(10));
> +		break;
> +
> +	case W2SG_NOPULSE:
> +		data->state = W2SG_IDLE;
> +
> +		pr_debug("w2sg: idle\n");
> +
> +		break;
> +
> +	}
> +}
> +
> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
> +{
> +	struct w2sg_data *data = pdata;
> +
> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
> +
> +	data->lna_blocked = blocked;
> +
> +	return w2sg_set_lna_power(data);
> +}
> +
> +static struct rfkill_ops w2sg0004_rfkill_ops = {
> +	.set_block = w2sg_rfkill_set_block,
> +};
> +
> +static struct serdev_device_ops serdev_ops = {
> +	.receive_buf = w2sg_uart_receive_buf,
> +};
> +
> +/*
> + * we are a man-in the middle between the user-space visible tty port
> + * and the serdev tty where the chip is connected.
> + * This allows us to recognise when the device should be powered on
> + * or off and handle the "false" state that data arrives while no
> + * users-space tty client exists.
> + */
> +
> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
> +{
> +	return w2sg_by_minor[minor];
> +}
> +
> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
> +{
> +	struct w2sg_data *data;
> +	int retval;
> +
> +	pr_debug("%s() tty = %p\n", __func__, tty);
> +
> +	data = w2sg_get_by_minor(tty->index);
> +
> +	if (!data)
> +		return -ENODEV;
> +
> +	retval = tty_standard_install(driver, tty);
> +	if (retval)
> +		goto error_init_termios;
> +
> +	tty->driver_data = data;
> +
> +	return 0;
> +
> +error_init_termios:
> +	tty_port_put(&data->port);
> +	return retval;
> +}
> +
> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
> +
> +	w2sg_set_power(data, ++data->open_count > 0);
> +
> +	return tty_port_open(&data->port, tty, file);
> +}
> +
> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	pr_debug("%s()\n", __func__);
> +
> +	w2sg_set_power(data, --data->open_count > 0);
> +
> +	tty_port_close(&data->port, tty, file);
> +}
> +
> +static int w2sg_tty_write(struct tty_struct *tty,
> +		const unsigned char *buffer, int count)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	/* simply pass down to UART */
> +	return serdev_device_write_buf(data->uart, buffer, count);
> +}
> +
> +static const struct tty_operations w2sg_serial_ops = {
> +	.install = w2sg_tty_install,
> +	.open = w2sg_tty_open,
> +	.close = w2sg_tty_close,
> +	.write = w2sg_tty_write,
> +};
> +
> +static const struct tty_port_operations w2sg_port_ops = {
> +};

drop the brackets? or use NULL below, not sure if this needs memory.

> +
> +static int w2sg_probe(struct serdev_device *serdev)
> +{
> +	struct w2sg_data *data;
> +	struct rfkill *rf_kill;
> +	int err;
> +	int minor;
> +	enum of_gpio_flags flags;
> +
> +	pr_debug("%s()\n", __func__);
> +
> +	minor = 0;
> +	if (w2sg_by_minor[minor]) {
> +		pr_err("w2sg minor is already in use!\n");
> +		return -ENODEV;
> +	}
> +
> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (data == NULL)
> +		return -ENOMEM;
> +
> +	w2sg_by_minor[minor] = data;
> +
> +	serdev_device_set_drvdata(serdev, data);
> +
> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
> +						     "enable-gpios", 0,
> +						     &flags);
> +	/* defer until we have all gpios */
> +	if (data->on_off_gpio == -EPROBE_DEFER)
> +		return -EPROBE_DEFER;
> +
> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
> +							"lna");
> +	if (IS_ERR(data->lna_regulator)) {
> +		/* defer until we can get the regulator */
> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;
> +
> +		data->lna_regulator = NULL;
> +	}
> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
> +
> +	data->lna_blocked = true;
> +	data->lna_is_off = true;
> +
> +	data->is_on = false;
> +	data->requested = false;
> +	data->state = W2SG_IDLE;
> +	data->last_toggle = jiffies;
> +	data->backoff = HZ;
> +
> +	data->uart = serdev;
> +
> +	INIT_DELAYED_WORK(&data->work, toggle_work);
> +
> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
> +				"w2sg0004-on-off");
> +	if (err < 0)
> +		goto out;
> +
> +	gpio_direction_output(data->on_off_gpio, false);
> +
> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
> +	serdev_device_open(data->uart);
> +
> +	serdev_device_set_baudrate(data->uart, 9600);
> +	serdev_device_set_flow_control(data->uart, false);
> +
> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
> +				&w2sg0004_rfkill_ops, data);
> +	if (rf_kill == NULL) {
> +		err = -ENOMEM;
> +		goto err_rfkill;
> +	}
> +
> +	err = rfkill_register(rf_kill);
> +	if (err) {
> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
> +		goto err_rfkill;
> +	}
> +
> +	data->rf_kill = rf_kill;
> +
> +	/* allocate the tty driver */
> +	data->tty_drv = alloc_tty_driver(1);
> +	if (!data->tty_drv)
> +		return -ENOMEM;
> +
> +	/* initialize the tty driver */
> +	data->tty_drv->owner = THIS_MODULE;
> +	data->tty_drv->driver_name = "w2sg0004";
> +	data->tty_drv->name = "ttyGPS";
> +	data->tty_drv->major = 0;
> +	data->tty_drv->minor_start = 0;
> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
> +	data->tty_drv->init_termios = tty_std_termios;
> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
> +					      HUPCL | CLOCAL;
> +	/*
> +	 * optional:
> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
> +					115200, 115200);
> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
> +	 */
> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
> +
> +	/* register the tty driver */
> +	err = tty_register_driver(data->tty_drv);
> +	if (err) {
> +		pr_err("%s - tty_register_driver failed(%d)\n",
> +			__func__, err);
> +		put_tty_driver(data->tty_drv);
> +		goto err_rfkill;
> +	}
> +
> +	tty_port_init(&data->port);
> +	data->port.ops = &w2sg_port_ops;
> +
> +	pr_debug("w2sg call tty_port_register_device\n");
> +
> +	data->dev = tty_port_register_device(&data->port,
> +			data->tty_drv, minor, &serdev->dev);
> +
> +	pr_debug("w2sg probed\n");
> +
> +	/* keep off until user space requests the device */
> +	w2sg_set_power(data, false);
> +
> +	return 0;
> +
> +err_rfkill:
> +	rfkill_destroy(rf_kill);
> +	serdev_device_close(data->uart);
> +out:
> +	return err;
> +}
> +
> +static void w2sg_remove(struct serdev_device *serdev)
> +{
> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
> +	int minor;
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	/* what is the right sequence to avoid problems? */
> +	serdev_device_close(data->uart);
> +
> +	minor = 0;
> +	tty_unregister_device(data->tty_drv, minor);
> +
> +	tty_unregister_driver(data->tty_drv);
> +}
> +
> +static int w2sg_suspend(struct device *dev)

__maybe_unused, or if PM is disabled you will get a warning.

> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	data->suspended = true;
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	w2sg_set_lna_power(data);	/* shuts down if needed */
> +
> +	if (data->state == W2SG_PULSE) {
> +		msleep(10);
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->last_toggle = jiffies;
> +		data->is_on = !data->is_on;
> +		data->state = W2SG_NOPULSE;
> +	}
> +
> +	if (data->state == W2SG_NOPULSE) {
> +		msleep(10);
> +		data->state = W2SG_IDLE;
> +	}
> +
> +	if (data->is_on) {
> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
> +			data->is_on, data->lna_is_off);
> +
> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
> +		msleep(10);
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->is_on = 0;
> +	}
> +
> +	return 0;
> +}
> +
> +static int w2sg_resume(struct device *dev)
> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	data->suspended = false;
> +
> +	pr_info("GPS resuming %d %d %d\n", data->requested,
> +		data->is_on, data->lna_is_off);
> +
> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id w2sg0004_of_match[] = {
> +	{ .compatible = "wi2wi,w2sg0004" },
> +	{ .compatible = "wi2wi,w2sg0084" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
> +
> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
> +
> +static struct serdev_device_driver w2sg_driver = {
> +	.probe		= w2sg_probe,
> +	.remove		= w2sg_remove,
> +	.driver = {
> +		.name	= "w2sg0004",
> +		.owner	= THIS_MODULE,
> +		.pm	= &w2sg_pm_ops,
> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
> +	},
> +};
> +
> +module_serdev_device_driver(w2sg_driver);
> +
> +MODULE_ALIAS("w2sg0004");

Is this needed?

> +
> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");

Who really wrote this?

> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
> +MODULE_LICENSE("GPL v2");
> 

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

* Re: [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-23 16:06     ` Andrew F. Davis
  0 siblings, 0 replies; 33+ messages in thread
From: Andrew F. Davis @ 2017-11-23 16:06 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Rob Herring, Mark Rutland,
	Benoît Cousson, Tony Lindgren, Russell King, Arnd Bergmann,
	Greg Kroah-Hartman, Kevin Hilman, Andreas Färber,
	Thierry Reding, Jonathan Cameron
  Cc: devicetree, linux-omap, linux-kernel, kernel, letux-kernel,
	linux-arm-kernel

On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
> 
> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
> and turn on/off the module. It also detects if the module is turned on (sends data)
> but should be off, e.g. if it was already turned on during boot or power-on-reset.
> 
> Additionally, rfkill block/unblock can be used to control an external LNA
> (and power down the module if not needed).
> 
> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
> but simplified and adapted to use the new serdev API introduced in 4.11.
> 
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
>  drivers/misc/Kconfig    |  10 +
>  drivers/misc/Makefile   |   1 +
>  drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 556 insertions(+)
>  create mode 100644 drivers/misc/w2sg0004.c
> 
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 8136dc7e863d..09d171d68408 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>  source "drivers/misc/genwqe/Kconfig"
>  source "drivers/misc/echo/Kconfig"
>  source "drivers/misc/cxl/Kconfig"
> +
> +config W2SG0004
> +	tristate "W2SG00x4 on/off control"
> +	depends on GPIOLIB && SERIAL_DEV_BUS
> +	help
> +          Enable on/off control of W2SG00x4 GPS moduled connected
> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
> +	  is opened/closed.
> +	  It also provides a rfkill gps name to control the LNA power.
> +
>  endmenu
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index ad0e64fdba34..abcb667e0ff0 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>  obj-y				+= mic/
>  obj-$(CONFIG_GENWQE)		+= genwqe/
>  obj-$(CONFIG_ECHO)		+= echo/
> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>  obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>  obj-$(CONFIG_CXL_BASE)		+= cxl/
>  obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
> new file mode 100644
> index 000000000000..12e14b5e0a99
> --- /dev/null
> +++ b/drivers/misc/w2sg0004.c
> @@ -0,0 +1,545 @@
> +// SPDX-License-Identifier: GPL-2.0

Damn this looks ugly, oh well :/

> +/*
> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
> + *


Think you still need copyright tag here somewhere.

> + * This receiver has an ON/OFF pin which must be toggled to
> + * turn the device 'on' of 'off'.  A high->low->high toggle
> + * will switch the device on if it is off, and off if it is on.
> + *
> + * To enable receiving on/off requests we register with the
> + * UART power management notifications.
> + *
> + * It is not possible to directly detect the state of the device.
> + * However when it is on it will send characters on a UART line
> + * regularly.
> + *
> + * To detect that the power state is out of sync (e.g. if GPS
> + * was enabled before a reboot), we register for UART data received
> + * notifications.
> + *
> + * In addition we register as a rfkill client so that we can
> + * control the LNA power.
> + *
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/rfkill.h>
> +#include <linux/serdev.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +#include <linux/workqueue.h>
> +
> +/*
> + * There seems to be restrictions on how quickly we can toggle the
> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
> + * If we do it too soon it doesn't work.
> + * So we have a state machine which uses the common work queue to ensure
> + * clean transitions.
> + * When a change is requested we record that request and only act on it
> + * once the previous change has completed.
> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
> + * one change per second.
> + */
> +
> +enum w2sg_state {
> +	W2SG_IDLE,	/* is not changing state */
> +	W2SG_PULSE,	/* activate on/off impulse */
> +	W2SG_NOPULSE	/* deactivate on/off impulse */
> +};
> +
> +struct w2sg_data {
> +	struct		rfkill *rf_kill;
> +	struct		regulator *lna_regulator;
> +	int		lna_blocked;	/* rfkill block gps active */
> +	int		lna_is_off;	/* LNA is currently off */
> +	int		is_on;		/* current state (0/1) */
> +	unsigned long	last_toggle;
> +	unsigned long	backoff;	/* time to wait since last_toggle */
> +	int		on_off_gpio;	/* the on-off gpio number */
> +	struct		serdev_device *uart;	/* uart connected to the chip */
> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
> +	struct		device *dev;	/* from tty_port_register_device() */
> +	struct		tty_port port;
> +	int		open_count;	/* how often we were opened */
> +	enum		w2sg_state state;
> +	int		requested;	/* requested state (0/1) */
> +	int		suspended;
> +	struct delayed_work work;
> +	int		discard_count;
> +};
> +

Kernel doc style.

> +static struct w2sg_data *w2sg_by_minor[1];
> +

If you can only have one right now then just drop the array.

> +static int w2sg_set_lna_power(struct w2sg_data *data)
> +{
> +	int ret = 0;
> +	int off = data->suspended || !data->requested || data->lna_blocked;
> +
> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
> +
> +	if (off != data->lna_is_off) {
> +		data->lna_is_off = off;
> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
> +			if (off)
> +				regulator_disable(data->lna_regulator);
> +			else
> +				ret = regulator_enable(data->lna_regulator);
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static void w2sg_set_power(void *pdata, int val)
> +{
> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
> +
> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
> +
> +	if (val && !data->requested) {
> +		data->requested = true;
> +	} else if (!val && data->requested) {
> +		data->backoff = HZ;
> +		data->requested = false;
> +	} else
> +		return;
> +
> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
> +
> +	if (!data->suspended)
> +		schedule_delayed_work(&data->work, 0);
> +}
> +
> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
> +
> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
> +				const unsigned char *rxdata,
> +				size_t count)
> +{
> +	struct w2sg_data *data =
> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
> +
> +	if (!data->requested && !data->is_on) {
> +		/*
> +		 * we have received characters while the w2sg
> +		 * should have been be turned off
> +		 */
> +		data->discard_count += count;
> +		if ((data->state == W2SG_IDLE) &&
> +		    time_after(jiffies,
> +		    data->last_toggle + data->backoff)) {
> +			/* Should be off by now, time to toggle again */
> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
> +				data->discard_count);
> +
> +			data->discard_count = 0;
> +
> +			data->is_on = true;
> +			data->backoff *= 2;
> +			if (!data->suspended)
> +				schedule_delayed_work(&data->work, 0);
> +		}
> +	} else if (data->open_count > 0) {
> +		int n;
> +
> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
> +			(unsigned long) count);
> +
> +		/* pass to user-space */
> +		n = tty_insert_flip_string(&data->port, rxdata, count);
> +		if (n != count)
> +			pr_err("w2sg00x4: did loose %lu characters\n",
> +				(unsigned long) (count - n));
> +		tty_flip_buffer_push(&data->port);
> +		return n;
> +	}
> +
> +	/* assume we have processed everything */
> +	return count;
> +}
> +
> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
> +

drop extra line, same everywhere

> +static void toggle_work(struct work_struct *work)
> +{
> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
> +					      work.work);
> +
> +	switch (data->state) {
> +	case W2SG_IDLE:
> +		if (data->requested == data->is_on)
> +			return;
> +
> +		w2sg_set_lna_power(data);	/* update LNA power state */
> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
> +		data->state = W2SG_PULSE;
> +
> +		pr_debug("w2sg: power gpio ON\n");
> +
> +		schedule_delayed_work(&data->work,
> +				      msecs_to_jiffies(10));
> +		break;
> +
> +	case W2SG_PULSE:
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->last_toggle = jiffies;
> +		data->state = W2SG_NOPULSE;
> +		data->is_on = !data->is_on;
> +
> +		pr_debug("w2sg: power gpio OFF\n");
> +
> +		schedule_delayed_work(&data->work,
> +				      msecs_to_jiffies(10));
> +		break;
> +
> +	case W2SG_NOPULSE:
> +		data->state = W2SG_IDLE;
> +
> +		pr_debug("w2sg: idle\n");
> +
> +		break;
> +
> +	}
> +}
> +
> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
> +{
> +	struct w2sg_data *data = pdata;
> +
> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
> +
> +	data->lna_blocked = blocked;
> +
> +	return w2sg_set_lna_power(data);
> +}
> +
> +static struct rfkill_ops w2sg0004_rfkill_ops = {
> +	.set_block = w2sg_rfkill_set_block,
> +};
> +
> +static struct serdev_device_ops serdev_ops = {
> +	.receive_buf = w2sg_uart_receive_buf,
> +};
> +
> +/*
> + * we are a man-in the middle between the user-space visible tty port
> + * and the serdev tty where the chip is connected.
> + * This allows us to recognise when the device should be powered on
> + * or off and handle the "false" state that data arrives while no
> + * users-space tty client exists.
> + */
> +
> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
> +{
> +	return w2sg_by_minor[minor];
> +}
> +
> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
> +{
> +	struct w2sg_data *data;
> +	int retval;
> +
> +	pr_debug("%s() tty = %p\n", __func__, tty);
> +
> +	data = w2sg_get_by_minor(tty->index);
> +
> +	if (!data)
> +		return -ENODEV;
> +
> +	retval = tty_standard_install(driver, tty);
> +	if (retval)
> +		goto error_init_termios;
> +
> +	tty->driver_data = data;
> +
> +	return 0;
> +
> +error_init_termios:
> +	tty_port_put(&data->port);
> +	return retval;
> +}
> +
> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
> +
> +	w2sg_set_power(data, ++data->open_count > 0);
> +
> +	return tty_port_open(&data->port, tty, file);
> +}
> +
> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	pr_debug("%s()\n", __func__);
> +
> +	w2sg_set_power(data, --data->open_count > 0);
> +
> +	tty_port_close(&data->port, tty, file);
> +}
> +
> +static int w2sg_tty_write(struct tty_struct *tty,
> +		const unsigned char *buffer, int count)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	/* simply pass down to UART */
> +	return serdev_device_write_buf(data->uart, buffer, count);
> +}
> +
> +static const struct tty_operations w2sg_serial_ops = {
> +	.install = w2sg_tty_install,
> +	.open = w2sg_tty_open,
> +	.close = w2sg_tty_close,
> +	.write = w2sg_tty_write,
> +};
> +
> +static const struct tty_port_operations w2sg_port_ops = {
> +};

drop the brackets? or use NULL below, not sure if this needs memory.

> +
> +static int w2sg_probe(struct serdev_device *serdev)
> +{
> +	struct w2sg_data *data;
> +	struct rfkill *rf_kill;
> +	int err;
> +	int minor;
> +	enum of_gpio_flags flags;
> +
> +	pr_debug("%s()\n", __func__);
> +
> +	minor = 0;
> +	if (w2sg_by_minor[minor]) {
> +		pr_err("w2sg minor is already in use!\n");
> +		return -ENODEV;
> +	}
> +
> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (data == NULL)
> +		return -ENOMEM;
> +
> +	w2sg_by_minor[minor] = data;
> +
> +	serdev_device_set_drvdata(serdev, data);
> +
> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
> +						     "enable-gpios", 0,
> +						     &flags);
> +	/* defer until we have all gpios */
> +	if (data->on_off_gpio == -EPROBE_DEFER)
> +		return -EPROBE_DEFER;
> +
> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
> +							"lna");
> +	if (IS_ERR(data->lna_regulator)) {
> +		/* defer until we can get the regulator */
> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;
> +
> +		data->lna_regulator = NULL;
> +	}
> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
> +
> +	data->lna_blocked = true;
> +	data->lna_is_off = true;
> +
> +	data->is_on = false;
> +	data->requested = false;
> +	data->state = W2SG_IDLE;
> +	data->last_toggle = jiffies;
> +	data->backoff = HZ;
> +
> +	data->uart = serdev;
> +
> +	INIT_DELAYED_WORK(&data->work, toggle_work);
> +
> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
> +				"w2sg0004-on-off");
> +	if (err < 0)
> +		goto out;
> +
> +	gpio_direction_output(data->on_off_gpio, false);
> +
> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
> +	serdev_device_open(data->uart);
> +
> +	serdev_device_set_baudrate(data->uart, 9600);
> +	serdev_device_set_flow_control(data->uart, false);
> +
> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
> +				&w2sg0004_rfkill_ops, data);
> +	if (rf_kill == NULL) {
> +		err = -ENOMEM;
> +		goto err_rfkill;
> +	}
> +
> +	err = rfkill_register(rf_kill);
> +	if (err) {
> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
> +		goto err_rfkill;
> +	}
> +
> +	data->rf_kill = rf_kill;
> +
> +	/* allocate the tty driver */
> +	data->tty_drv = alloc_tty_driver(1);
> +	if (!data->tty_drv)
> +		return -ENOMEM;
> +
> +	/* initialize the tty driver */
> +	data->tty_drv->owner = THIS_MODULE;
> +	data->tty_drv->driver_name = "w2sg0004";
> +	data->tty_drv->name = "ttyGPS";
> +	data->tty_drv->major = 0;
> +	data->tty_drv->minor_start = 0;
> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
> +	data->tty_drv->init_termios = tty_std_termios;
> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
> +					      HUPCL | CLOCAL;
> +	/*
> +	 * optional:
> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
> +					115200, 115200);
> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
> +	 */
> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
> +
> +	/* register the tty driver */
> +	err = tty_register_driver(data->tty_drv);
> +	if (err) {
> +		pr_err("%s - tty_register_driver failed(%d)\n",
> +			__func__, err);
> +		put_tty_driver(data->tty_drv);
> +		goto err_rfkill;
> +	}
> +
> +	tty_port_init(&data->port);
> +	data->port.ops = &w2sg_port_ops;
> +
> +	pr_debug("w2sg call tty_port_register_device\n");
> +
> +	data->dev = tty_port_register_device(&data->port,
> +			data->tty_drv, minor, &serdev->dev);
> +
> +	pr_debug("w2sg probed\n");
> +
> +	/* keep off until user space requests the device */
> +	w2sg_set_power(data, false);
> +
> +	return 0;
> +
> +err_rfkill:
> +	rfkill_destroy(rf_kill);
> +	serdev_device_close(data->uart);
> +out:
> +	return err;
> +}
> +
> +static void w2sg_remove(struct serdev_device *serdev)
> +{
> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
> +	int minor;
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	/* what is the right sequence to avoid problems? */
> +	serdev_device_close(data->uart);
> +
> +	minor = 0;
> +	tty_unregister_device(data->tty_drv, minor);
> +
> +	tty_unregister_driver(data->tty_drv);
> +}
> +
> +static int w2sg_suspend(struct device *dev)

__maybe_unused, or if PM is disabled you will get a warning.

> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	data->suspended = true;
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	w2sg_set_lna_power(data);	/* shuts down if needed */
> +
> +	if (data->state == W2SG_PULSE) {
> +		msleep(10);
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->last_toggle = jiffies;
> +		data->is_on = !data->is_on;
> +		data->state = W2SG_NOPULSE;
> +	}
> +
> +	if (data->state == W2SG_NOPULSE) {
> +		msleep(10);
> +		data->state = W2SG_IDLE;
> +	}
> +
> +	if (data->is_on) {
> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
> +			data->is_on, data->lna_is_off);
> +
> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
> +		msleep(10);
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->is_on = 0;
> +	}
> +
> +	return 0;
> +}
> +
> +static int w2sg_resume(struct device *dev)
> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	data->suspended = false;
> +
> +	pr_info("GPS resuming %d %d %d\n", data->requested,
> +		data->is_on, data->lna_is_off);
> +
> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id w2sg0004_of_match[] = {
> +	{ .compatible = "wi2wi,w2sg0004" },
> +	{ .compatible = "wi2wi,w2sg0084" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
> +
> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
> +
> +static struct serdev_device_driver w2sg_driver = {
> +	.probe		= w2sg_probe,
> +	.remove		= w2sg_remove,
> +	.driver = {
> +		.name	= "w2sg0004",
> +		.owner	= THIS_MODULE,
> +		.pm	= &w2sg_pm_ops,
> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
> +	},
> +};
> +
> +module_serdev_device_driver(w2sg_driver);
> +
> +MODULE_ALIAS("w2sg0004");

Is this needed?

> +
> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");

Who really wrote this?

> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
> +MODULE_LICENSE("GPL v2");
> 

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

* [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-23 16:06     ` Andrew F. Davis
  0 siblings, 0 replies; 33+ messages in thread
From: Andrew F. Davis @ 2017-11-23 16:06 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
> 
> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
> and turn on/off the module. It also detects if the module is turned on (sends data)
> but should be off, e.g. if it was already turned on during boot or power-on-reset.
> 
> Additionally, rfkill block/unblock can be used to control an external LNA
> (and power down the module if not needed).
> 
> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
> but simplified and adapted to use the new serdev API introduced in 4.11.
> 
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
>  drivers/misc/Kconfig    |  10 +
>  drivers/misc/Makefile   |   1 +
>  drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 556 insertions(+)
>  create mode 100644 drivers/misc/w2sg0004.c
> 
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 8136dc7e863d..09d171d68408 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>  source "drivers/misc/genwqe/Kconfig"
>  source "drivers/misc/echo/Kconfig"
>  source "drivers/misc/cxl/Kconfig"
> +
> +config W2SG0004
> +	tristate "W2SG00x4 on/off control"
> +	depends on GPIOLIB && SERIAL_DEV_BUS
> +	help
> +          Enable on/off control of W2SG00x4 GPS moduled connected
> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
> +	  is opened/closed.
> +	  It also provides a rfkill gps name to control the LNA power.
> +
>  endmenu
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index ad0e64fdba34..abcb667e0ff0 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>  obj-y				+= mic/
>  obj-$(CONFIG_GENWQE)		+= genwqe/
>  obj-$(CONFIG_ECHO)		+= echo/
> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>  obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>  obj-$(CONFIG_CXL_BASE)		+= cxl/
>  obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
> new file mode 100644
> index 000000000000..12e14b5e0a99
> --- /dev/null
> +++ b/drivers/misc/w2sg0004.c
> @@ -0,0 +1,545 @@
> +// SPDX-License-Identifier: GPL-2.0

Damn this looks ugly, oh well :/

> +/*
> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
> + *


Think you still need copyright tag here somewhere.

> + * This receiver has an ON/OFF pin which must be toggled to
> + * turn the device 'on' of 'off'.  A high->low->high toggle
> + * will switch the device on if it is off, and off if it is on.
> + *
> + * To enable receiving on/off requests we register with the
> + * UART power management notifications.
> + *
> + * It is not possible to directly detect the state of the device.
> + * However when it is on it will send characters on a UART line
> + * regularly.
> + *
> + * To detect that the power state is out of sync (e.g. if GPS
> + * was enabled before a reboot), we register for UART data received
> + * notifications.
> + *
> + * In addition we register as a rfkill client so that we can
> + * control the LNA power.
> + *
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/rfkill.h>
> +#include <linux/serdev.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +#include <linux/workqueue.h>
> +
> +/*
> + * There seems to be restrictions on how quickly we can toggle the
> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
> + * If we do it too soon it doesn't work.
> + * So we have a state machine which uses the common work queue to ensure
> + * clean transitions.
> + * When a change is requested we record that request and only act on it
> + * once the previous change has completed.
> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
> + * one change per second.
> + */
> +
> +enum w2sg_state {
> +	W2SG_IDLE,	/* is not changing state */
> +	W2SG_PULSE,	/* activate on/off impulse */
> +	W2SG_NOPULSE	/* deactivate on/off impulse */
> +};
> +
> +struct w2sg_data {
> +	struct		rfkill *rf_kill;
> +	struct		regulator *lna_regulator;
> +	int		lna_blocked;	/* rfkill block gps active */
> +	int		lna_is_off;	/* LNA is currently off */
> +	int		is_on;		/* current state (0/1) */
> +	unsigned long	last_toggle;
> +	unsigned long	backoff;	/* time to wait since last_toggle */
> +	int		on_off_gpio;	/* the on-off gpio number */
> +	struct		serdev_device *uart;	/* uart connected to the chip */
> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
> +	struct		device *dev;	/* from tty_port_register_device() */
> +	struct		tty_port port;
> +	int		open_count;	/* how often we were opened */
> +	enum		w2sg_state state;
> +	int		requested;	/* requested state (0/1) */
> +	int		suspended;
> +	struct delayed_work work;
> +	int		discard_count;
> +};
> +

Kernel doc style.

> +static struct w2sg_data *w2sg_by_minor[1];
> +

If you can only have one right now then just drop the array.

> +static int w2sg_set_lna_power(struct w2sg_data *data)
> +{
> +	int ret = 0;
> +	int off = data->suspended || !data->requested || data->lna_blocked;
> +
> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
> +
> +	if (off != data->lna_is_off) {
> +		data->lna_is_off = off;
> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
> +			if (off)
> +				regulator_disable(data->lna_regulator);
> +			else
> +				ret = regulator_enable(data->lna_regulator);
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static void w2sg_set_power(void *pdata, int val)
> +{
> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
> +
> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
> +
> +	if (val && !data->requested) {
> +		data->requested = true;
> +	} else if (!val && data->requested) {
> +		data->backoff = HZ;
> +		data->requested = false;
> +	} else
> +		return;
> +
> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
> +
> +	if (!data->suspended)
> +		schedule_delayed_work(&data->work, 0);
> +}
> +
> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
> +
> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
> +				const unsigned char *rxdata,
> +				size_t count)
> +{
> +	struct w2sg_data *data =
> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
> +
> +	if (!data->requested && !data->is_on) {
> +		/*
> +		 * we have received characters while the w2sg
> +		 * should have been be turned off
> +		 */
> +		data->discard_count += count;
> +		if ((data->state == W2SG_IDLE) &&
> +		    time_after(jiffies,
> +		    data->last_toggle + data->backoff)) {
> +			/* Should be off by now, time to toggle again */
> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
> +				data->discard_count);
> +
> +			data->discard_count = 0;
> +
> +			data->is_on = true;
> +			data->backoff *= 2;
> +			if (!data->suspended)
> +				schedule_delayed_work(&data->work, 0);
> +		}
> +	} else if (data->open_count > 0) {
> +		int n;
> +
> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
> +			(unsigned long) count);
> +
> +		/* pass to user-space */
> +		n = tty_insert_flip_string(&data->port, rxdata, count);
> +		if (n != count)
> +			pr_err("w2sg00x4: did loose %lu characters\n",
> +				(unsigned long) (count - n));
> +		tty_flip_buffer_push(&data->port);
> +		return n;
> +	}
> +
> +	/* assume we have processed everything */
> +	return count;
> +}
> +
> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
> +

drop extra line, same everywhere

> +static void toggle_work(struct work_struct *work)
> +{
> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
> +					      work.work);
> +
> +	switch (data->state) {
> +	case W2SG_IDLE:
> +		if (data->requested == data->is_on)
> +			return;
> +
> +		w2sg_set_lna_power(data);	/* update LNA power state */
> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
> +		data->state = W2SG_PULSE;
> +
> +		pr_debug("w2sg: power gpio ON\n");
> +
> +		schedule_delayed_work(&data->work,
> +				      msecs_to_jiffies(10));
> +		break;
> +
> +	case W2SG_PULSE:
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->last_toggle = jiffies;
> +		data->state = W2SG_NOPULSE;
> +		data->is_on = !data->is_on;
> +
> +		pr_debug("w2sg: power gpio OFF\n");
> +
> +		schedule_delayed_work(&data->work,
> +				      msecs_to_jiffies(10));
> +		break;
> +
> +	case W2SG_NOPULSE:
> +		data->state = W2SG_IDLE;
> +
> +		pr_debug("w2sg: idle\n");
> +
> +		break;
> +
> +	}
> +}
> +
> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
> +{
> +	struct w2sg_data *data = pdata;
> +
> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
> +
> +	data->lna_blocked = blocked;
> +
> +	return w2sg_set_lna_power(data);
> +}
> +
> +static struct rfkill_ops w2sg0004_rfkill_ops = {
> +	.set_block = w2sg_rfkill_set_block,
> +};
> +
> +static struct serdev_device_ops serdev_ops = {
> +	.receive_buf = w2sg_uart_receive_buf,
> +};
> +
> +/*
> + * we are a man-in the middle between the user-space visible tty port
> + * and the serdev tty where the chip is connected.
> + * This allows us to recognise when the device should be powered on
> + * or off and handle the "false" state that data arrives while no
> + * users-space tty client exists.
> + */
> +
> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
> +{
> +	return w2sg_by_minor[minor];
> +}
> +
> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
> +{
> +	struct w2sg_data *data;
> +	int retval;
> +
> +	pr_debug("%s() tty = %p\n", __func__, tty);
> +
> +	data = w2sg_get_by_minor(tty->index);
> +
> +	if (!data)
> +		return -ENODEV;
> +
> +	retval = tty_standard_install(driver, tty);
> +	if (retval)
> +		goto error_init_termios;
> +
> +	tty->driver_data = data;
> +
> +	return 0;
> +
> +error_init_termios:
> +	tty_port_put(&data->port);
> +	return retval;
> +}
> +
> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
> +
> +	w2sg_set_power(data, ++data->open_count > 0);
> +
> +	return tty_port_open(&data->port, tty, file);
> +}
> +
> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	pr_debug("%s()\n", __func__);
> +
> +	w2sg_set_power(data, --data->open_count > 0);
> +
> +	tty_port_close(&data->port, tty, file);
> +}
> +
> +static int w2sg_tty_write(struct tty_struct *tty,
> +		const unsigned char *buffer, int count)
> +{
> +	struct w2sg_data *data = tty->driver_data;
> +
> +	/* simply pass down to UART */
> +	return serdev_device_write_buf(data->uart, buffer, count);
> +}
> +
> +static const struct tty_operations w2sg_serial_ops = {
> +	.install = w2sg_tty_install,
> +	.open = w2sg_tty_open,
> +	.close = w2sg_tty_close,
> +	.write = w2sg_tty_write,
> +};
> +
> +static const struct tty_port_operations w2sg_port_ops = {
> +};

drop the brackets? or use NULL below, not sure if this needs memory.

> +
> +static int w2sg_probe(struct serdev_device *serdev)
> +{
> +	struct w2sg_data *data;
> +	struct rfkill *rf_kill;
> +	int err;
> +	int minor;
> +	enum of_gpio_flags flags;
> +
> +	pr_debug("%s()\n", __func__);
> +
> +	minor = 0;
> +	if (w2sg_by_minor[minor]) {
> +		pr_err("w2sg minor is already in use!\n");
> +		return -ENODEV;
> +	}
> +
> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (data == NULL)
> +		return -ENOMEM;
> +
> +	w2sg_by_minor[minor] = data;
> +
> +	serdev_device_set_drvdata(serdev, data);
> +
> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
> +						     "enable-gpios", 0,
> +						     &flags);
> +	/* defer until we have all gpios */
> +	if (data->on_off_gpio == -EPROBE_DEFER)
> +		return -EPROBE_DEFER;
> +
> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
> +							"lna");
> +	if (IS_ERR(data->lna_regulator)) {
> +		/* defer until we can get the regulator */
> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;
> +
> +		data->lna_regulator = NULL;
> +	}
> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
> +
> +	data->lna_blocked = true;
> +	data->lna_is_off = true;
> +
> +	data->is_on = false;
> +	data->requested = false;
> +	data->state = W2SG_IDLE;
> +	data->last_toggle = jiffies;
> +	data->backoff = HZ;
> +
> +	data->uart = serdev;
> +
> +	INIT_DELAYED_WORK(&data->work, toggle_work);
> +
> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
> +				"w2sg0004-on-off");
> +	if (err < 0)
> +		goto out;
> +
> +	gpio_direction_output(data->on_off_gpio, false);
> +
> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
> +	serdev_device_open(data->uart);
> +
> +	serdev_device_set_baudrate(data->uart, 9600);
> +	serdev_device_set_flow_control(data->uart, false);
> +
> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
> +				&w2sg0004_rfkill_ops, data);
> +	if (rf_kill == NULL) {
> +		err = -ENOMEM;
> +		goto err_rfkill;
> +	}
> +
> +	err = rfkill_register(rf_kill);
> +	if (err) {
> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
> +		goto err_rfkill;
> +	}
> +
> +	data->rf_kill = rf_kill;
> +
> +	/* allocate the tty driver */
> +	data->tty_drv = alloc_tty_driver(1);
> +	if (!data->tty_drv)
> +		return -ENOMEM;
> +
> +	/* initialize the tty driver */
> +	data->tty_drv->owner = THIS_MODULE;
> +	data->tty_drv->driver_name = "w2sg0004";
> +	data->tty_drv->name = "ttyGPS";
> +	data->tty_drv->major = 0;
> +	data->tty_drv->minor_start = 0;
> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
> +	data->tty_drv->init_termios = tty_std_termios;
> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
> +					      HUPCL | CLOCAL;
> +	/*
> +	 * optional:
> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
> +					115200, 115200);
> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
> +	 */
> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
> +
> +	/* register the tty driver */
> +	err = tty_register_driver(data->tty_drv);
> +	if (err) {
> +		pr_err("%s - tty_register_driver failed(%d)\n",
> +			__func__, err);
> +		put_tty_driver(data->tty_drv);
> +		goto err_rfkill;
> +	}
> +
> +	tty_port_init(&data->port);
> +	data->port.ops = &w2sg_port_ops;
> +
> +	pr_debug("w2sg call tty_port_register_device\n");
> +
> +	data->dev = tty_port_register_device(&data->port,
> +			data->tty_drv, minor, &serdev->dev);
> +
> +	pr_debug("w2sg probed\n");
> +
> +	/* keep off until user space requests the device */
> +	w2sg_set_power(data, false);
> +
> +	return 0;
> +
> +err_rfkill:
> +	rfkill_destroy(rf_kill);
> +	serdev_device_close(data->uart);
> +out:
> +	return err;
> +}
> +
> +static void w2sg_remove(struct serdev_device *serdev)
> +{
> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
> +	int minor;
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	/* what is the right sequence to avoid problems? */
> +	serdev_device_close(data->uart);
> +
> +	minor = 0;
> +	tty_unregister_device(data->tty_drv, minor);
> +
> +	tty_unregister_driver(data->tty_drv);
> +}
> +
> +static int w2sg_suspend(struct device *dev)

__maybe_unused, or if PM is disabled you will get a warning.

> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	data->suspended = true;
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	w2sg_set_lna_power(data);	/* shuts down if needed */
> +
> +	if (data->state == W2SG_PULSE) {
> +		msleep(10);
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->last_toggle = jiffies;
> +		data->is_on = !data->is_on;
> +		data->state = W2SG_NOPULSE;
> +	}
> +
> +	if (data->state == W2SG_NOPULSE) {
> +		msleep(10);
> +		data->state = W2SG_IDLE;
> +	}
> +
> +	if (data->is_on) {
> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
> +			data->is_on, data->lna_is_off);
> +
> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
> +		msleep(10);
> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
> +		data->is_on = 0;
> +	}
> +
> +	return 0;
> +}
> +
> +static int w2sg_resume(struct device *dev)
> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	data->suspended = false;
> +
> +	pr_info("GPS resuming %d %d %d\n", data->requested,
> +		data->is_on, data->lna_is_off);
> +
> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id w2sg0004_of_match[] = {
> +	{ .compatible = "wi2wi,w2sg0004" },
> +	{ .compatible = "wi2wi,w2sg0084" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
> +
> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
> +
> +static struct serdev_device_driver w2sg_driver = {
> +	.probe		= w2sg_probe,
> +	.remove		= w2sg_remove,
> +	.driver = {
> +		.name	= "w2sg0004",
> +		.owner	= THIS_MODULE,
> +		.pm	= &w2sg_pm_ops,
> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
> +	},
> +};
> +
> +module_serdev_device_driver(w2sg_driver);
> +
> +MODULE_ALIAS("w2sg0004");

Is this needed?

> +
> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");

Who really wrote this?

> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
> +MODULE_LICENSE("GPL v2");
> 

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

* Re: [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-28 21:42       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-28 21:42 UTC (permalink / raw)
  To: Andrew F. Davis
  Cc: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman, Kevin Hilman,
	Andreas Färber, Thierry Reding, Jonathan Cameron, DTML,
	Linux Kernel Mailing List, linux-omap,
	Discussions about the Letux Kernel, kernel, Linux ARM,
	Marek Belisko, Neil Brown

Hi Andrew,
now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].

> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd@ti.com>:
> 
> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>> 
>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>> and turn on/off the module. It also detects if the module is turned on (sends data)
>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>> 
>> Additionally, rfkill block/unblock can be used to control an external LNA
>> (and power down the module if not needed).
>> 
>> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
>> but simplified and adapted to use the new serdev API introduced in 4.11.
>> 
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> ---
>> drivers/misc/Kconfig    |  10 +
>> drivers/misc/Makefile   |   1 +
>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++

I wonder if this shouldn't better go to

  drivers/gps

But this directory does not yet exist and has no overall maintainer.
So it could be left in drivers/misc and moved as soon as drivers/gps
is created elsewhere.

>> 3 files changed, 556 insertions(+)
>> create mode 100644 drivers/misc/w2sg0004.c
>> 
>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>> index 8136dc7e863d..09d171d68408 100644
>> --- a/drivers/misc/Kconfig
>> +++ b/drivers/misc/Kconfig
>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>> source "drivers/misc/genwqe/Kconfig"
>> source "drivers/misc/echo/Kconfig"
>> source "drivers/misc/cxl/Kconfig"
>> +
>> +config W2SG0004
>> +	tristate "W2SG00x4 on/off control"
>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>> +	help
>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>> +	  is opened/closed.
>> +	  It also provides a rfkill gps name to control the LNA power.
>> +
>> endmenu
>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>> index ad0e64fdba34..abcb667e0ff0 100644
>> --- a/drivers/misc/Makefile
>> +++ b/drivers/misc/Makefile
>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>> obj-y				+= mic/
>> obj-$(CONFIG_GENWQE)		+= genwqe/
>> obj-$(CONFIG_ECHO)		+= echo/
>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>> new file mode 100644
>> index 000000000000..12e14b5e0a99
>> --- /dev/null
>> +++ b/drivers/misc/w2sg0004.c
>> @@ -0,0 +1,545 @@
>> +// SPDX-License-Identifier: GPL-2.0
> 
> Damn this looks ugly, oh well :/

I could remove it for [PATCH v5] ... :\

> 
>> +/*
>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>> + *
> 
> 
> Think you still need copyright tag here somewhere.

At the bottom there is:

>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>> +MODULE_LICENSE("GPL v2");

Isn't that enough any more?

> 
>> + * This receiver has an ON/OFF pin which must be toggled to
>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>> + * will switch the device on if it is off, and off if it is on.
>> + *
>> + * To enable receiving on/off requests we register with the
>> + * UART power management notifications.
>> + *
>> + * It is not possible to directly detect the state of the device.
>> + * However when it is on it will send characters on a UART line
>> + * regularly.
>> + *
>> + * To detect that the power state is out of sync (e.g. if GPS
>> + * was enabled before a reboot), we register for UART data received
>> + * notifications.
>> + *
>> + * In addition we register as a rfkill client so that we can
>> + * control the LNA power.
>> + *
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/irq.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/rfkill.h>
>> +#include <linux/serdev.h>
>> +#include <linux/sched.h>
>> +#include <linux/slab.h>
>> +#include <linux/tty.h>
>> +#include <linux/tty_flip.h>
>> +#include <linux/workqueue.h>
>> +
>> +/*
>> + * There seems to be restrictions on how quickly we can toggle the
>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>> + * If we do it too soon it doesn't work.
>> + * So we have a state machine which uses the common work queue to ensure
>> + * clean transitions.
>> + * When a change is requested we record that request and only act on it
>> + * once the previous change has completed.
>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>> + * one change per second.
>> + */
>> +
>> +enum w2sg_state {
>> +	W2SG_IDLE,	/* is not changing state */
>> +	W2SG_PULSE,	/* activate on/off impulse */
>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>> +};
>> +
>> +struct w2sg_data {
>> +	struct		rfkill *rf_kill;
>> +	struct		regulator *lna_regulator;
>> +	int		lna_blocked;	/* rfkill block gps active */
>> +	int		lna_is_off;	/* LNA is currently off */
>> +	int		is_on;		/* current state (0/1) */
>> +	unsigned long	last_toggle;
>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>> +	int		on_off_gpio;	/* the on-off gpio number */
>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>> +	struct		device *dev;	/* from tty_port_register_device() */
>> +	struct		tty_port port;
>> +	int		open_count;	/* how often we were opened */
>> +	enum		w2sg_state state;
>> +	int		requested;	/* requested state (0/1) */
>> +	int		suspended;
>> +	struct delayed_work work;
>> +	int		discard_count;
>> +};
>> +
> 
> Kernel doc style.

Is this really needed here?

For pure driver internal structs (they are not kernel infrastructure API) I usually
consult the source code of a driver and never well formatted kernel doc. Hence I think
readability by programmers looking into the source file is more important than serving
kernel doc tools.

Yes, there are examples like:

https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113

But I find them more confusing than helpful because I have to jump between code lines
to match the comment with the data type.

> 
>> +static struct w2sg_data *w2sg_by_minor[1];
>> +
> 
> If you can only have one right now then just drop the array.

w2sg_get_by_minor() is prepared to access more than one record.
And there are plans to have more than one (but I don't know exactly
how to provide it).

Making it a non-array seems to be an unnecessary hurdle for such
improvements. And regarding memory footprint, a single-element
array is equivalent to a non-array.

> 
>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>> +{
>> +	int ret = 0;
>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>> +
>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>> +
>> +	if (off != data->lna_is_off) {
>> +		data->lna_is_off = off;
>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>> +			if (off)
>> +				regulator_disable(data->lna_regulator);
>> +			else
>> +				ret = regulator_enable(data->lna_regulator);
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static void w2sg_set_power(void *pdata, int val)
>> +{
>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>> +
>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>> +
>> +	if (val && !data->requested) {
>> +		data->requested = true;
>> +	} else if (!val && data->requested) {
>> +		data->backoff = HZ;
>> +		data->requested = false;
>> +	} else
>> +		return;
>> +
>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>> +
>> +	if (!data->suspended)
>> +		schedule_delayed_work(&data->work, 0);
>> +}
>> +
>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>> +
>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>> +				const unsigned char *rxdata,
>> +				size_t count)
>> +{
>> +	struct w2sg_data *data =
>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>> +
>> +	if (!data->requested && !data->is_on) {
>> +		/*
>> +		 * we have received characters while the w2sg
>> +		 * should have been be turned off
>> +		 */
>> +		data->discard_count += count;
>> +		if ((data->state == W2SG_IDLE) &&
>> +		    time_after(jiffies,
>> +		    data->last_toggle + data->backoff)) {
>> +			/* Should be off by now, time to toggle again */
>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>> +				data->discard_count);
>> +
>> +			data->discard_count = 0;
>> +
>> +			data->is_on = true;
>> +			data->backoff *= 2;
>> +			if (!data->suspended)
>> +				schedule_delayed_work(&data->work, 0);
>> +		}
>> +	} else if (data->open_count > 0) {
>> +		int n;
>> +
>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>> +			(unsigned long) count);
>> +
>> +		/* pass to user-space */
>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>> +		if (n != count)
>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>> +				(unsigned long) (count - n));
>> +		tty_flip_buffer_push(&data->port);
>> +		return n;
>> +	}
>> +
>> +	/* assume we have processed everything */
>> +	return count;
>> +}
>> +
>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>> +
> 
> drop extra line, same everywhere

Ok! Was just this and one more location.

> 
>> +static void toggle_work(struct work_struct *work)
>> +{
>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>> +					      work.work);
>> +
>> +	switch (data->state) {
>> +	case W2SG_IDLE:
>> +		if (data->requested == data->is_on)
>> +			return;
>> +
>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>> +		data->state = W2SG_PULSE;
>> +
>> +		pr_debug("w2sg: power gpio ON\n");
>> +
>> +		schedule_delayed_work(&data->work,
>> +				      msecs_to_jiffies(10));
>> +		break;
>> +
>> +	case W2SG_PULSE:
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->last_toggle = jiffies;
>> +		data->state = W2SG_NOPULSE;
>> +		data->is_on = !data->is_on;
>> +
>> +		pr_debug("w2sg: power gpio OFF\n");
>> +
>> +		schedule_delayed_work(&data->work,
>> +				      msecs_to_jiffies(10));
>> +		break;
>> +
>> +	case W2SG_NOPULSE:
>> +		data->state = W2SG_IDLE;
>> +
>> +		pr_debug("w2sg: idle\n");
>> +
>> +		break;
>> +
>> +	}
>> +}
>> +
>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>> +{
>> +	struct w2sg_data *data = pdata;
>> +
>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>> +
>> +	data->lna_blocked = blocked;
>> +
>> +	return w2sg_set_lna_power(data);
>> +}
>> +
>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>> +	.set_block = w2sg_rfkill_set_block,
>> +};
>> +
>> +static struct serdev_device_ops serdev_ops = {
>> +	.receive_buf = w2sg_uart_receive_buf,
>> +};
>> +
>> +/*
>> + * we are a man-in the middle between the user-space visible tty port
>> + * and the serdev tty where the chip is connected.
>> + * This allows us to recognise when the device should be powered on
>> + * or off and handle the "false" state that data arrives while no
>> + * users-space tty client exists.
>> + */
>> +
>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>> +{
>> +	return w2sg_by_minor[minor];
>> +}
>> +
>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>> +{
>> +	struct w2sg_data *data;
>> +	int retval;
>> +
>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>> +
>> +	data = w2sg_get_by_minor(tty->index);
>> +
>> +	if (!data)
>> +		return -ENODEV;
>> +
>> +	retval = tty_standard_install(driver, tty);
>> +	if (retval)
>> +		goto error_init_termios;
>> +
>> +	tty->driver_data = data;
>> +
>> +	return 0;
>> +
>> +error_init_termios:
>> +	tty_port_put(&data->port);
>> +	return retval;
>> +}
>> +
>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>> +
>> +	w2sg_set_power(data, ++data->open_count > 0);
>> +
>> +	return tty_port_open(&data->port, tty, file);
>> +}
>> +
>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	pr_debug("%s()\n", __func__);
>> +
>> +	w2sg_set_power(data, --data->open_count > 0);
>> +
>> +	tty_port_close(&data->port, tty, file);
>> +}
>> +
>> +static int w2sg_tty_write(struct tty_struct *tty,
>> +		const unsigned char *buffer, int count)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	/* simply pass down to UART */
>> +	return serdev_device_write_buf(data->uart, buffer, count);
>> +}
>> +
>> +static const struct tty_operations w2sg_serial_ops = {
>> +	.install = w2sg_tty_install,
>> +	.open = w2sg_tty_open,
>> +	.close = w2sg_tty_close,
>> +	.write = w2sg_tty_write,
>> +};
>> +
>> +static const struct tty_port_operations w2sg_port_ops = {
>> +};
> 
> drop the brackets? or use NULL below, not sure if this needs memory.

This allocates a struct tty_port_operations initialized with NULL for
all function pointers.

I am not sure if this is the same as providing no w2sg_port_ops at all.

Rather I think the serial core is only port == NULL safe but not
port->ops == NULL e.g.:

http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422

A test confirms (see comment below):

> 
>> +
>> +static int w2sg_probe(struct serdev_device *serdev)
>> +{
>> +	struct w2sg_data *data;
>> +	struct rfkill *rf_kill;
>> +	int err;
>> +	int minor;
>> +	enum of_gpio_flags flags;
>> +
>> +	pr_debug("%s()\n", __func__);
>> +
>> +	minor = 0;
>> +	if (w2sg_by_minor[minor]) {
>> +		pr_err("w2sg minor is already in use!\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>> +	if (data == NULL)
>> +		return -ENOMEM;
>> +
>> +	w2sg_by_minor[minor] = data;

While looking through this, I found a potential issue if allocating the
gpio fails with -EPROBE_DEFER.

Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
find it allocated on next deferred probe attempt.

So I'll move that to a position further down.

>> +
>> +	serdev_device_set_drvdata(serdev, data);
>> +
>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>> +						     "enable-gpios", 0,
>> +						     &flags);
>> +	/* defer until we have all gpios */
>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>> +		return -EPROBE_DEFER;
>> +
>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>> +							"lna");
>> +	if (IS_ERR(data->lna_regulator)) {
>> +		/* defer until we can get the regulator */
>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>> +			return -EPROBE_DEFER;
>> +
>> +		data->lna_regulator = NULL;
>> +	}
>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>> +
>> +	data->lna_blocked = true;
>> +	data->lna_is_off = true;
>> +
>> +	data->is_on = false;
>> +	data->requested = false;
>> +	data->state = W2SG_IDLE;
>> +	data->last_toggle = jiffies;
>> +	data->backoff = HZ;
>> +
>> +	data->uart = serdev;
>> +
>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>> +
>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>> +				"w2sg0004-on-off");
>> +	if (err < 0)
>> +		goto out;
>> +
>> +	gpio_direction_output(data->on_off_gpio, false);
>> +
>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>> +	serdev_device_open(data->uart);
>> +
>> +	serdev_device_set_baudrate(data->uart, 9600);
>> +	serdev_device_set_flow_control(data->uart, false);
>> +
>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>> +				&w2sg0004_rfkill_ops, data);
>> +	if (rf_kill == NULL) {
>> +		err = -ENOMEM;
>> +		goto err_rfkill;
>> +	}
>> +
>> +	err = rfkill_register(rf_kill);
>> +	if (err) {
>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>> +		goto err_rfkill;
>> +	}
>> +
>> +	data->rf_kill = rf_kill;
>> +
>> +	/* allocate the tty driver */
>> +	data->tty_drv = alloc_tty_driver(1);
>> +	if (!data->tty_drv)
>> +		return -ENOMEM;
>> +
>> +	/* initialize the tty driver */
>> +	data->tty_drv->owner = THIS_MODULE;
>> +	data->tty_drv->driver_name = "w2sg0004";
>> +	data->tty_drv->name = "ttyGPS";
>> +	data->tty_drv->major = 0;
>> +	data->tty_drv->minor_start = 0;
>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>> +	data->tty_drv->init_termios = tty_std_termios;
>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>> +					      HUPCL | CLOCAL;
>> +	/*
>> +	 * optional:
>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>> +					115200, 115200);
>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>> +	 */
>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>> +
>> +	/* register the tty driver */
>> +	err = tty_register_driver(data->tty_drv);
>> +	if (err) {
>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>> +			__func__, err);
>> +		put_tty_driver(data->tty_drv);
>> +		goto err_rfkill;
>> +	}
>> +
>> +	tty_port_init(&data->port);
>> +	data->port.ops = &w2sg_port_ops;

A test setting this to NULL leads to a kernel oops in:

[ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)

So we must define this completely empty struct w2sg_port_ops
and pass a reference.

>> +
>> +	pr_debug("w2sg call tty_port_register_device\n");
>> +
>> +	data->dev = tty_port_register_device(&data->port,
>> +			data->tty_drv, minor, &serdev->dev);
>> +
>> +	pr_debug("w2sg probed\n");
>> +
>> +	/* keep off until user space requests the device */
>> +	w2sg_set_power(data, false);
>> +
>> +	return 0;
>> +
>> +err_rfkill:
>> +	rfkill_destroy(rf_kill);
>> +	serdev_device_close(data->uart);
>> +out:
>> +	return err;
>> +}
>> +
>> +static void w2sg_remove(struct serdev_device *serdev)
>> +{
>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>> +	int minor;
>> +
>> +	cancel_delayed_work_sync(&data->work);
>> +
>> +	/* what is the right sequence to avoid problems? */
>> +	serdev_device_close(data->uart);
>> +
>> +	minor = 0;
>> +	tty_unregister_device(data->tty_drv, minor);
>> +
>> +	tty_unregister_driver(data->tty_drv);
>> +}
>> +
>> +static int w2sg_suspend(struct device *dev)
> 
> __maybe_unused, or if PM is disabled you will get a warning.

Ok.

> 
>> +{
>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>> +
>> +	data->suspended = true;
>> +
>> +	cancel_delayed_work_sync(&data->work);
>> +
>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>> +
>> +	if (data->state == W2SG_PULSE) {
>> +		msleep(10);
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->last_toggle = jiffies;
>> +		data->is_on = !data->is_on;
>> +		data->state = W2SG_NOPULSE;
>> +	}
>> +
>> +	if (data->state == W2SG_NOPULSE) {
>> +		msleep(10);
>> +		data->state = W2SG_IDLE;
>> +	}
>> +
>> +	if (data->is_on) {
>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>> +			data->is_on, data->lna_is_off);
>> +
>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>> +		msleep(10);
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->is_on = 0;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int w2sg_resume(struct device *dev)
>> +{
>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>> +
>> +	data->suspended = false;
>> +
>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>> +		data->is_on, data->lna_is_off);
>> +
>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id w2sg0004_of_match[] = {
>> +	{ .compatible = "wi2wi,w2sg0004" },
>> +	{ .compatible = "wi2wi,w2sg0084" },
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>> +
>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>> +
>> +static struct serdev_device_driver w2sg_driver = {
>> +	.probe		= w2sg_probe,
>> +	.remove		= w2sg_remove,
>> +	.driver = {
>> +		.name	= "w2sg0004",
>> +		.owner	= THIS_MODULE,
>> +		.pm	= &w2sg_pm_ops,
>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>> +	},
>> +};
>> +
>> +module_serdev_device_driver(w2sg_driver);
>> +
>> +MODULE_ALIAS("w2sg0004");
> 
> Is this needed?

Seems to be redundant.
After removal the driver is still loaded automatically after boot:

root@letux:~# modprobe -c | fgrep w2sg
alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
root@letux:~# lsmod | fgrep w2sg
w2sg0004               16384  2 
root@letux:~# 

> 
>> +
>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
> 
> Who really wrote this?

Good question.

The problem is that with cleaning up the code and rewriting history over such
a long time, it is no more visible.

But I have searched through our older branches:

Neil Brown had developed the first version for v3.7 in 2013: 
http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69

This is where the MODULE_AUTHOR line comes from.

Later, Marek Belisko worked on the UART driver, pinmux and interrupts.

And I have
* added lna-regulator and rfkill ca. v3.12
* added device tree support ca. v3.15
* moved to drivers/misc
* ported the code to serdev API ca. v4.11
* submitted to LKML and worked in comments by reviewers ca. 4.15

So this is the first version that is close to be acceptable.

Neil had also submitted different versions to LKML but I am not
sure if he still is active anywhere.

I have also checked a diff between the v3.7 version and the
current one and there are ca. 30-40% of lines original code by
Neil.

So I'd say:

* Neil is the original author and a significant amount of untouched code lines and comments are still there
* he designed the overall driver architecture
* Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
* Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
* I did add some important features but not the core code and driver architecture
* I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"

How should we best reflect this in the AUTHOR macro?

> 
>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>> +MODULE_LICENSE("GPL v2");
>> 


BR and thanks,
Nikolaus Schaller

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

* Re: [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-28 21:42       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-28 21:42 UTC (permalink / raw)
  To: Andrew F. Davis
  Cc: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman, Kevin Hilman,
	Andreas Färber, Thierry Reding, Jonathan Cameron, DTML,
	Linux Kernel Mailing List, linux-omap,
	Discussions about the Letux Kernel,
	kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf, Linux ARM,
	Marek Belisko

Hi Andrew,
now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].

> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd-l0cyMroinI0@public.gmane.org>:
> 
> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>> 
>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>> and turn on/off the module. It also detects if the module is turned on (sends data)
>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>> 
>> Additionally, rfkill block/unblock can be used to control an external LNA
>> (and power down the module if not needed).
>> 
>> The driver concept is based on code developed by NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>
>> but simplified and adapted to use the new serdev API introduced in 4.11.
>> 
>> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
>> ---
>> drivers/misc/Kconfig    |  10 +
>> drivers/misc/Makefile   |   1 +
>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++

I wonder if this shouldn't better go to

  drivers/gps

But this directory does not yet exist and has no overall maintainer.
So it could be left in drivers/misc and moved as soon as drivers/gps
is created elsewhere.

>> 3 files changed, 556 insertions(+)
>> create mode 100644 drivers/misc/w2sg0004.c
>> 
>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>> index 8136dc7e863d..09d171d68408 100644
>> --- a/drivers/misc/Kconfig
>> +++ b/drivers/misc/Kconfig
>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>> source "drivers/misc/genwqe/Kconfig"
>> source "drivers/misc/echo/Kconfig"
>> source "drivers/misc/cxl/Kconfig"
>> +
>> +config W2SG0004
>> +	tristate "W2SG00x4 on/off control"
>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>> +	help
>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>> +	  is opened/closed.
>> +	  It also provides a rfkill gps name to control the LNA power.
>> +
>> endmenu
>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>> index ad0e64fdba34..abcb667e0ff0 100644
>> --- a/drivers/misc/Makefile
>> +++ b/drivers/misc/Makefile
>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>> obj-y				+= mic/
>> obj-$(CONFIG_GENWQE)		+= genwqe/
>> obj-$(CONFIG_ECHO)		+= echo/
>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>> new file mode 100644
>> index 000000000000..12e14b5e0a99
>> --- /dev/null
>> +++ b/drivers/misc/w2sg0004.c
>> @@ -0,0 +1,545 @@
>> +// SPDX-License-Identifier: GPL-2.0
> 
> Damn this looks ugly, oh well :/

I could remove it for [PATCH v5] ... :\

> 
>> +/*
>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>> + *
> 
> 
> Think you still need copyright tag here somewhere.

At the bottom there is:

>> +MODULE_AUTHOR("NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>");
>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>> +MODULE_LICENSE("GPL v2");

Isn't that enough any more?

> 
>> + * This receiver has an ON/OFF pin which must be toggled to
>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>> + * will switch the device on if it is off, and off if it is on.
>> + *
>> + * To enable receiving on/off requests we register with the
>> + * UART power management notifications.
>> + *
>> + * It is not possible to directly detect the state of the device.
>> + * However when it is on it will send characters on a UART line
>> + * regularly.
>> + *
>> + * To detect that the power state is out of sync (e.g. if GPS
>> + * was enabled before a reboot), we register for UART data received
>> + * notifications.
>> + *
>> + * In addition we register as a rfkill client so that we can
>> + * control the LNA power.
>> + *
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/irq.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/rfkill.h>
>> +#include <linux/serdev.h>
>> +#include <linux/sched.h>
>> +#include <linux/slab.h>
>> +#include <linux/tty.h>
>> +#include <linux/tty_flip.h>
>> +#include <linux/workqueue.h>
>> +
>> +/*
>> + * There seems to be restrictions on how quickly we can toggle the
>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>> + * If we do it too soon it doesn't work.
>> + * So we have a state machine which uses the common work queue to ensure
>> + * clean transitions.
>> + * When a change is requested we record that request and only act on it
>> + * once the previous change has completed.
>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>> + * one change per second.
>> + */
>> +
>> +enum w2sg_state {
>> +	W2SG_IDLE,	/* is not changing state */
>> +	W2SG_PULSE,	/* activate on/off impulse */
>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>> +};
>> +
>> +struct w2sg_data {
>> +	struct		rfkill *rf_kill;
>> +	struct		regulator *lna_regulator;
>> +	int		lna_blocked;	/* rfkill block gps active */
>> +	int		lna_is_off;	/* LNA is currently off */
>> +	int		is_on;		/* current state (0/1) */
>> +	unsigned long	last_toggle;
>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>> +	int		on_off_gpio;	/* the on-off gpio number */
>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>> +	struct		device *dev;	/* from tty_port_register_device() */
>> +	struct		tty_port port;
>> +	int		open_count;	/* how often we were opened */
>> +	enum		w2sg_state state;
>> +	int		requested;	/* requested state (0/1) */
>> +	int		suspended;
>> +	struct delayed_work work;
>> +	int		discard_count;
>> +};
>> +
> 
> Kernel doc style.

Is this really needed here?

For pure driver internal structs (they are not kernel infrastructure API) I usually
consult the source code of a driver and never well formatted kernel doc. Hence I think
readability by programmers looking into the source file is more important than serving
kernel doc tools.

Yes, there are examples like:

https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113

But I find them more confusing than helpful because I have to jump between code lines
to match the comment with the data type.

> 
>> +static struct w2sg_data *w2sg_by_minor[1];
>> +
> 
> If you can only have one right now then just drop the array.

w2sg_get_by_minor() is prepared to access more than one record.
And there are plans to have more than one (but I don't know exactly
how to provide it).

Making it a non-array seems to be an unnecessary hurdle for such
improvements. And regarding memory footprint, a single-element
array is equivalent to a non-array.

> 
>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>> +{
>> +	int ret = 0;
>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>> +
>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>> +
>> +	if (off != data->lna_is_off) {
>> +		data->lna_is_off = off;
>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>> +			if (off)
>> +				regulator_disable(data->lna_regulator);
>> +			else
>> +				ret = regulator_enable(data->lna_regulator);
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static void w2sg_set_power(void *pdata, int val)
>> +{
>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>> +
>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>> +
>> +	if (val && !data->requested) {
>> +		data->requested = true;
>> +	} else if (!val && data->requested) {
>> +		data->backoff = HZ;
>> +		data->requested = false;
>> +	} else
>> +		return;
>> +
>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>> +
>> +	if (!data->suspended)
>> +		schedule_delayed_work(&data->work, 0);
>> +}
>> +
>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>> +
>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>> +				const unsigned char *rxdata,
>> +				size_t count)
>> +{
>> +	struct w2sg_data *data =
>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>> +
>> +	if (!data->requested && !data->is_on) {
>> +		/*
>> +		 * we have received characters while the w2sg
>> +		 * should have been be turned off
>> +		 */
>> +		data->discard_count += count;
>> +		if ((data->state == W2SG_IDLE) &&
>> +		    time_after(jiffies,
>> +		    data->last_toggle + data->backoff)) {
>> +			/* Should be off by now, time to toggle again */
>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>> +				data->discard_count);
>> +
>> +			data->discard_count = 0;
>> +
>> +			data->is_on = true;
>> +			data->backoff *= 2;
>> +			if (!data->suspended)
>> +				schedule_delayed_work(&data->work, 0);
>> +		}
>> +	} else if (data->open_count > 0) {
>> +		int n;
>> +
>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>> +			(unsigned long) count);
>> +
>> +		/* pass to user-space */
>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>> +		if (n != count)
>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>> +				(unsigned long) (count - n));
>> +		tty_flip_buffer_push(&data->port);
>> +		return n;
>> +	}
>> +
>> +	/* assume we have processed everything */
>> +	return count;
>> +}
>> +
>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>> +
> 
> drop extra line, same everywhere

Ok! Was just this and one more location.

> 
>> +static void toggle_work(struct work_struct *work)
>> +{
>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>> +					      work.work);
>> +
>> +	switch (data->state) {
>> +	case W2SG_IDLE:
>> +		if (data->requested == data->is_on)
>> +			return;
>> +
>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>> +		data->state = W2SG_PULSE;
>> +
>> +		pr_debug("w2sg: power gpio ON\n");
>> +
>> +		schedule_delayed_work(&data->work,
>> +				      msecs_to_jiffies(10));
>> +		break;
>> +
>> +	case W2SG_PULSE:
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->last_toggle = jiffies;
>> +		data->state = W2SG_NOPULSE;
>> +		data->is_on = !data->is_on;
>> +
>> +		pr_debug("w2sg: power gpio OFF\n");
>> +
>> +		schedule_delayed_work(&data->work,
>> +				      msecs_to_jiffies(10));
>> +		break;
>> +
>> +	case W2SG_NOPULSE:
>> +		data->state = W2SG_IDLE;
>> +
>> +		pr_debug("w2sg: idle\n");
>> +
>> +		break;
>> +
>> +	}
>> +}
>> +
>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>> +{
>> +	struct w2sg_data *data = pdata;
>> +
>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>> +
>> +	data->lna_blocked = blocked;
>> +
>> +	return w2sg_set_lna_power(data);
>> +}
>> +
>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>> +	.set_block = w2sg_rfkill_set_block,
>> +};
>> +
>> +static struct serdev_device_ops serdev_ops = {
>> +	.receive_buf = w2sg_uart_receive_buf,
>> +};
>> +
>> +/*
>> + * we are a man-in the middle between the user-space visible tty port
>> + * and the serdev tty where the chip is connected.
>> + * This allows us to recognise when the device should be powered on
>> + * or off and handle the "false" state that data arrives while no
>> + * users-space tty client exists.
>> + */
>> +
>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>> +{
>> +	return w2sg_by_minor[minor];
>> +}
>> +
>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>> +{
>> +	struct w2sg_data *data;
>> +	int retval;
>> +
>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>> +
>> +	data = w2sg_get_by_minor(tty->index);
>> +
>> +	if (!data)
>> +		return -ENODEV;
>> +
>> +	retval = tty_standard_install(driver, tty);
>> +	if (retval)
>> +		goto error_init_termios;
>> +
>> +	tty->driver_data = data;
>> +
>> +	return 0;
>> +
>> +error_init_termios:
>> +	tty_port_put(&data->port);
>> +	return retval;
>> +}
>> +
>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>> +
>> +	w2sg_set_power(data, ++data->open_count > 0);
>> +
>> +	return tty_port_open(&data->port, tty, file);
>> +}
>> +
>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	pr_debug("%s()\n", __func__);
>> +
>> +	w2sg_set_power(data, --data->open_count > 0);
>> +
>> +	tty_port_close(&data->port, tty, file);
>> +}
>> +
>> +static int w2sg_tty_write(struct tty_struct *tty,
>> +		const unsigned char *buffer, int count)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	/* simply pass down to UART */
>> +	return serdev_device_write_buf(data->uart, buffer, count);
>> +}
>> +
>> +static const struct tty_operations w2sg_serial_ops = {
>> +	.install = w2sg_tty_install,
>> +	.open = w2sg_tty_open,
>> +	.close = w2sg_tty_close,
>> +	.write = w2sg_tty_write,
>> +};
>> +
>> +static const struct tty_port_operations w2sg_port_ops = {
>> +};
> 
> drop the brackets? or use NULL below, not sure if this needs memory.

This allocates a struct tty_port_operations initialized with NULL for
all function pointers.

I am not sure if this is the same as providing no w2sg_port_ops at all.

Rather I think the serial core is only port == NULL safe but not
port->ops == NULL e.g.:

http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422

A test confirms (see comment below):

> 
>> +
>> +static int w2sg_probe(struct serdev_device *serdev)
>> +{
>> +	struct w2sg_data *data;
>> +	struct rfkill *rf_kill;
>> +	int err;
>> +	int minor;
>> +	enum of_gpio_flags flags;
>> +
>> +	pr_debug("%s()\n", __func__);
>> +
>> +	minor = 0;
>> +	if (w2sg_by_minor[minor]) {
>> +		pr_err("w2sg minor is already in use!\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>> +	if (data == NULL)
>> +		return -ENOMEM;
>> +
>> +	w2sg_by_minor[minor] = data;

While looking through this, I found a potential issue if allocating the
gpio fails with -EPROBE_DEFER.

Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
find it allocated on next deferred probe attempt.

So I'll move that to a position further down.

>> +
>> +	serdev_device_set_drvdata(serdev, data);
>> +
>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>> +						     "enable-gpios", 0,
>> +						     &flags);
>> +	/* defer until we have all gpios */
>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>> +		return -EPROBE_DEFER;
>> +
>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>> +							"lna");
>> +	if (IS_ERR(data->lna_regulator)) {
>> +		/* defer until we can get the regulator */
>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>> +			return -EPROBE_DEFER;
>> +
>> +		data->lna_regulator = NULL;
>> +	}
>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>> +
>> +	data->lna_blocked = true;
>> +	data->lna_is_off = true;
>> +
>> +	data->is_on = false;
>> +	data->requested = false;
>> +	data->state = W2SG_IDLE;
>> +	data->last_toggle = jiffies;
>> +	data->backoff = HZ;
>> +
>> +	data->uart = serdev;
>> +
>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>> +
>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>> +				"w2sg0004-on-off");
>> +	if (err < 0)
>> +		goto out;
>> +
>> +	gpio_direction_output(data->on_off_gpio, false);
>> +
>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>> +	serdev_device_open(data->uart);
>> +
>> +	serdev_device_set_baudrate(data->uart, 9600);
>> +	serdev_device_set_flow_control(data->uart, false);
>> +
>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>> +				&w2sg0004_rfkill_ops, data);
>> +	if (rf_kill == NULL) {
>> +		err = -ENOMEM;
>> +		goto err_rfkill;
>> +	}
>> +
>> +	err = rfkill_register(rf_kill);
>> +	if (err) {
>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>> +		goto err_rfkill;
>> +	}
>> +
>> +	data->rf_kill = rf_kill;
>> +
>> +	/* allocate the tty driver */
>> +	data->tty_drv = alloc_tty_driver(1);
>> +	if (!data->tty_drv)
>> +		return -ENOMEM;
>> +
>> +	/* initialize the tty driver */
>> +	data->tty_drv->owner = THIS_MODULE;
>> +	data->tty_drv->driver_name = "w2sg0004";
>> +	data->tty_drv->name = "ttyGPS";
>> +	data->tty_drv->major = 0;
>> +	data->tty_drv->minor_start = 0;
>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>> +	data->tty_drv->init_termios = tty_std_termios;
>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>> +					      HUPCL | CLOCAL;
>> +	/*
>> +	 * optional:
>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>> +					115200, 115200);
>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>> +	 */
>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>> +
>> +	/* register the tty driver */
>> +	err = tty_register_driver(data->tty_drv);
>> +	if (err) {
>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>> +			__func__, err);
>> +		put_tty_driver(data->tty_drv);
>> +		goto err_rfkill;
>> +	}
>> +
>> +	tty_port_init(&data->port);
>> +	data->port.ops = &w2sg_port_ops;

A test setting this to NULL leads to a kernel oops in:

[ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)

So we must define this completely empty struct w2sg_port_ops
and pass a reference.

>> +
>> +	pr_debug("w2sg call tty_port_register_device\n");
>> +
>> +	data->dev = tty_port_register_device(&data->port,
>> +			data->tty_drv, minor, &serdev->dev);
>> +
>> +	pr_debug("w2sg probed\n");
>> +
>> +	/* keep off until user space requests the device */
>> +	w2sg_set_power(data, false);
>> +
>> +	return 0;
>> +
>> +err_rfkill:
>> +	rfkill_destroy(rf_kill);
>> +	serdev_device_close(data->uart);
>> +out:
>> +	return err;
>> +}
>> +
>> +static void w2sg_remove(struct serdev_device *serdev)
>> +{
>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>> +	int minor;
>> +
>> +	cancel_delayed_work_sync(&data->work);
>> +
>> +	/* what is the right sequence to avoid problems? */
>> +	serdev_device_close(data->uart);
>> +
>> +	minor = 0;
>> +	tty_unregister_device(data->tty_drv, minor);
>> +
>> +	tty_unregister_driver(data->tty_drv);
>> +}
>> +
>> +static int w2sg_suspend(struct device *dev)
> 
> __maybe_unused, or if PM is disabled you will get a warning.

Ok.

> 
>> +{
>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>> +
>> +	data->suspended = true;
>> +
>> +	cancel_delayed_work_sync(&data->work);
>> +
>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>> +
>> +	if (data->state == W2SG_PULSE) {
>> +		msleep(10);
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->last_toggle = jiffies;
>> +		data->is_on = !data->is_on;
>> +		data->state = W2SG_NOPULSE;
>> +	}
>> +
>> +	if (data->state == W2SG_NOPULSE) {
>> +		msleep(10);
>> +		data->state = W2SG_IDLE;
>> +	}
>> +
>> +	if (data->is_on) {
>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>> +			data->is_on, data->lna_is_off);
>> +
>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>> +		msleep(10);
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->is_on = 0;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int w2sg_resume(struct device *dev)
>> +{
>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>> +
>> +	data->suspended = false;
>> +
>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>> +		data->is_on, data->lna_is_off);
>> +
>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id w2sg0004_of_match[] = {
>> +	{ .compatible = "wi2wi,w2sg0004" },
>> +	{ .compatible = "wi2wi,w2sg0084" },
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>> +
>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>> +
>> +static struct serdev_device_driver w2sg_driver = {
>> +	.probe		= w2sg_probe,
>> +	.remove		= w2sg_remove,
>> +	.driver = {
>> +		.name	= "w2sg0004",
>> +		.owner	= THIS_MODULE,
>> +		.pm	= &w2sg_pm_ops,
>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>> +	},
>> +};
>> +
>> +module_serdev_device_driver(w2sg_driver);
>> +
>> +MODULE_ALIAS("w2sg0004");
> 
> Is this needed?

Seems to be redundant.
After removal the driver is still loaded automatically after boot:

root@letux:~# modprobe -c | fgrep w2sg
alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
root@letux:~# lsmod | fgrep w2sg
w2sg0004               16384  2 
root@letux:~# 

> 
>> +
>> +MODULE_AUTHOR("NeilBrown <neilb-l3A5Bk7waGM@public.gmane.org>");
> 
> Who really wrote this?

Good question.

The problem is that with cleaning up the code and rewriting history over such
a long time, it is no more visible.

But I have searched through our older branches:

Neil Brown had developed the first version for v3.7 in 2013: 
http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69

This is where the MODULE_AUTHOR line comes from.

Later, Marek Belisko worked on the UART driver, pinmux and interrupts.

And I have
* added lna-regulator and rfkill ca. v3.12
* added device tree support ca. v3.15
* moved to drivers/misc
* ported the code to serdev API ca. v4.11
* submitted to LKML and worked in comments by reviewers ca. 4.15

So this is the first version that is close to be acceptable.

Neil had also submitted different versions to LKML but I am not
sure if he still is active anywhere.

I have also checked a diff between the v3.7 version and the
current one and there are ca. 30-40% of lines original code by
Neil.

So I'd say:

* Neil is the original author and a significant amount of untouched code lines and comments are still there
* he designed the overall driver architecture
* Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
* Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
* I did add some important features but not the core code and driver architecture
* I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"

How should we best reflect this in the AUTHOR macro?

> 
>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>> +MODULE_LICENSE("GPL v2");
>> 


BR and thanks,
Nikolaus Schaller

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-28 21:42       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-11-28 21:42 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Andrew,
now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].

> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd@ti.com>:
> 
> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>> 
>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>> and turn on/off the module. It also detects if the module is turned on (sends data)
>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>> 
>> Additionally, rfkill block/unblock can be used to control an external LNA
>> (and power down the module if not needed).
>> 
>> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
>> but simplified and adapted to use the new serdev API introduced in 4.11.
>> 
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> ---
>> drivers/misc/Kconfig    |  10 +
>> drivers/misc/Makefile   |   1 +
>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++

I wonder if this shouldn't better go to

  drivers/gps

But this directory does not yet exist and has no overall maintainer.
So it could be left in drivers/misc and moved as soon as drivers/gps
is created elsewhere.

>> 3 files changed, 556 insertions(+)
>> create mode 100644 drivers/misc/w2sg0004.c
>> 
>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>> index 8136dc7e863d..09d171d68408 100644
>> --- a/drivers/misc/Kconfig
>> +++ b/drivers/misc/Kconfig
>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>> source "drivers/misc/genwqe/Kconfig"
>> source "drivers/misc/echo/Kconfig"
>> source "drivers/misc/cxl/Kconfig"
>> +
>> +config W2SG0004
>> +	tristate "W2SG00x4 on/off control"
>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>> +	help
>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>> +	  is opened/closed.
>> +	  It also provides a rfkill gps name to control the LNA power.
>> +
>> endmenu
>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>> index ad0e64fdba34..abcb667e0ff0 100644
>> --- a/drivers/misc/Makefile
>> +++ b/drivers/misc/Makefile
>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>> obj-y				+= mic/
>> obj-$(CONFIG_GENWQE)		+= genwqe/
>> obj-$(CONFIG_ECHO)		+= echo/
>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>> new file mode 100644
>> index 000000000000..12e14b5e0a99
>> --- /dev/null
>> +++ b/drivers/misc/w2sg0004.c
>> @@ -0,0 +1,545 @@
>> +// SPDX-License-Identifier: GPL-2.0
> 
> Damn this looks ugly, oh well :/

I could remove it for [PATCH v5] ... :\

> 
>> +/*
>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>> + *
> 
> 
> Think you still need copyright tag here somewhere.

At the bottom there is:

>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>> +MODULE_LICENSE("GPL v2");

Isn't that enough any more?

> 
>> + * This receiver has an ON/OFF pin which must be toggled to
>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>> + * will switch the device on if it is off, and off if it is on.
>> + *
>> + * To enable receiving on/off requests we register with the
>> + * UART power management notifications.
>> + *
>> + * It is not possible to directly detect the state of the device.
>> + * However when it is on it will send characters on a UART line
>> + * regularly.
>> + *
>> + * To detect that the power state is out of sync (e.g. if GPS
>> + * was enabled before a reboot), we register for UART data received
>> + * notifications.
>> + *
>> + * In addition we register as a rfkill client so that we can
>> + * control the LNA power.
>> + *
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/irq.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/rfkill.h>
>> +#include <linux/serdev.h>
>> +#include <linux/sched.h>
>> +#include <linux/slab.h>
>> +#include <linux/tty.h>
>> +#include <linux/tty_flip.h>
>> +#include <linux/workqueue.h>
>> +
>> +/*
>> + * There seems to be restrictions on how quickly we can toggle the
>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>> + * If we do it too soon it doesn't work.
>> + * So we have a state machine which uses the common work queue to ensure
>> + * clean transitions.
>> + * When a change is requested we record that request and only act on it
>> + * once the previous change has completed.
>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>> + * one change per second.
>> + */
>> +
>> +enum w2sg_state {
>> +	W2SG_IDLE,	/* is not changing state */
>> +	W2SG_PULSE,	/* activate on/off impulse */
>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>> +};
>> +
>> +struct w2sg_data {
>> +	struct		rfkill *rf_kill;
>> +	struct		regulator *lna_regulator;
>> +	int		lna_blocked;	/* rfkill block gps active */
>> +	int		lna_is_off;	/* LNA is currently off */
>> +	int		is_on;		/* current state (0/1) */
>> +	unsigned long	last_toggle;
>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>> +	int		on_off_gpio;	/* the on-off gpio number */
>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>> +	struct		device *dev;	/* from tty_port_register_device() */
>> +	struct		tty_port port;
>> +	int		open_count;	/* how often we were opened */
>> +	enum		w2sg_state state;
>> +	int		requested;	/* requested state (0/1) */
>> +	int		suspended;
>> +	struct delayed_work work;
>> +	int		discard_count;
>> +};
>> +
> 
> Kernel doc style.

Is this really needed here?

For pure driver internal structs (they are not kernel infrastructure API) I usually
consult the source code of a driver and never well formatted kernel doc. Hence I think
readability by programmers looking into the source file is more important than serving
kernel doc tools.

Yes, there are examples like:

https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113

But I find them more confusing than helpful because I have to jump between code lines
to match the comment with the data type.

> 
>> +static struct w2sg_data *w2sg_by_minor[1];
>> +
> 
> If you can only have one right now then just drop the array.

w2sg_get_by_minor() is prepared to access more than one record.
And there are plans to have more than one (but I don't know exactly
how to provide it).

Making it a non-array seems to be an unnecessary hurdle for such
improvements. And regarding memory footprint, a single-element
array is equivalent to a non-array.

> 
>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>> +{
>> +	int ret = 0;
>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>> +
>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>> +
>> +	if (off != data->lna_is_off) {
>> +		data->lna_is_off = off;
>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>> +			if (off)
>> +				regulator_disable(data->lna_regulator);
>> +			else
>> +				ret = regulator_enable(data->lna_regulator);
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static void w2sg_set_power(void *pdata, int val)
>> +{
>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>> +
>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>> +
>> +	if (val && !data->requested) {
>> +		data->requested = true;
>> +	} else if (!val && data->requested) {
>> +		data->backoff = HZ;
>> +		data->requested = false;
>> +	} else
>> +		return;
>> +
>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>> +
>> +	if (!data->suspended)
>> +		schedule_delayed_work(&data->work, 0);
>> +}
>> +
>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>> +
>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>> +				const unsigned char *rxdata,
>> +				size_t count)
>> +{
>> +	struct w2sg_data *data =
>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>> +
>> +	if (!data->requested && !data->is_on) {
>> +		/*
>> +		 * we have received characters while the w2sg
>> +		 * should have been be turned off
>> +		 */
>> +		data->discard_count += count;
>> +		if ((data->state == W2SG_IDLE) &&
>> +		    time_after(jiffies,
>> +		    data->last_toggle + data->backoff)) {
>> +			/* Should be off by now, time to toggle again */
>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>> +				data->discard_count);
>> +
>> +			data->discard_count = 0;
>> +
>> +			data->is_on = true;
>> +			data->backoff *= 2;
>> +			if (!data->suspended)
>> +				schedule_delayed_work(&data->work, 0);
>> +		}
>> +	} else if (data->open_count > 0) {
>> +		int n;
>> +
>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>> +			(unsigned long) count);
>> +
>> +		/* pass to user-space */
>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>> +		if (n != count)
>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>> +				(unsigned long) (count - n));
>> +		tty_flip_buffer_push(&data->port);
>> +		return n;
>> +	}
>> +
>> +	/* assume we have processed everything */
>> +	return count;
>> +}
>> +
>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>> +
> 
> drop extra line, same everywhere

Ok! Was just this and one more location.

> 
>> +static void toggle_work(struct work_struct *work)
>> +{
>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>> +					      work.work);
>> +
>> +	switch (data->state) {
>> +	case W2SG_IDLE:
>> +		if (data->requested == data->is_on)
>> +			return;
>> +
>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>> +		data->state = W2SG_PULSE;
>> +
>> +		pr_debug("w2sg: power gpio ON\n");
>> +
>> +		schedule_delayed_work(&data->work,
>> +				      msecs_to_jiffies(10));
>> +		break;
>> +
>> +	case W2SG_PULSE:
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->last_toggle = jiffies;
>> +		data->state = W2SG_NOPULSE;
>> +		data->is_on = !data->is_on;
>> +
>> +		pr_debug("w2sg: power gpio OFF\n");
>> +
>> +		schedule_delayed_work(&data->work,
>> +				      msecs_to_jiffies(10));
>> +		break;
>> +
>> +	case W2SG_NOPULSE:
>> +		data->state = W2SG_IDLE;
>> +
>> +		pr_debug("w2sg: idle\n");
>> +
>> +		break;
>> +
>> +	}
>> +}
>> +
>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>> +{
>> +	struct w2sg_data *data = pdata;
>> +
>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>> +
>> +	data->lna_blocked = blocked;
>> +
>> +	return w2sg_set_lna_power(data);
>> +}
>> +
>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>> +	.set_block = w2sg_rfkill_set_block,
>> +};
>> +
>> +static struct serdev_device_ops serdev_ops = {
>> +	.receive_buf = w2sg_uart_receive_buf,
>> +};
>> +
>> +/*
>> + * we are a man-in the middle between the user-space visible tty port
>> + * and the serdev tty where the chip is connected.
>> + * This allows us to recognise when the device should be powered on
>> + * or off and handle the "false" state that data arrives while no
>> + * users-space tty client exists.
>> + */
>> +
>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>> +{
>> +	return w2sg_by_minor[minor];
>> +}
>> +
>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>> +{
>> +	struct w2sg_data *data;
>> +	int retval;
>> +
>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>> +
>> +	data = w2sg_get_by_minor(tty->index);
>> +
>> +	if (!data)
>> +		return -ENODEV;
>> +
>> +	retval = tty_standard_install(driver, tty);
>> +	if (retval)
>> +		goto error_init_termios;
>> +
>> +	tty->driver_data = data;
>> +
>> +	return 0;
>> +
>> +error_init_termios:
>> +	tty_port_put(&data->port);
>> +	return retval;
>> +}
>> +
>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>> +
>> +	w2sg_set_power(data, ++data->open_count > 0);
>> +
>> +	return tty_port_open(&data->port, tty, file);
>> +}
>> +
>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	pr_debug("%s()\n", __func__);
>> +
>> +	w2sg_set_power(data, --data->open_count > 0);
>> +
>> +	tty_port_close(&data->port, tty, file);
>> +}
>> +
>> +static int w2sg_tty_write(struct tty_struct *tty,
>> +		const unsigned char *buffer, int count)
>> +{
>> +	struct w2sg_data *data = tty->driver_data;
>> +
>> +	/* simply pass down to UART */
>> +	return serdev_device_write_buf(data->uart, buffer, count);
>> +}
>> +
>> +static const struct tty_operations w2sg_serial_ops = {
>> +	.install = w2sg_tty_install,
>> +	.open = w2sg_tty_open,
>> +	.close = w2sg_tty_close,
>> +	.write = w2sg_tty_write,
>> +};
>> +
>> +static const struct tty_port_operations w2sg_port_ops = {
>> +};
> 
> drop the brackets? or use NULL below, not sure if this needs memory.

This allocates a struct tty_port_operations initialized with NULL for
all function pointers.

I am not sure if this is the same as providing no w2sg_port_ops at all.

Rather I think the serial core is only port == NULL safe but not
port->ops == NULL e.g.:

http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422

A test confirms (see comment below):

> 
>> +
>> +static int w2sg_probe(struct serdev_device *serdev)
>> +{
>> +	struct w2sg_data *data;
>> +	struct rfkill *rf_kill;
>> +	int err;
>> +	int minor;
>> +	enum of_gpio_flags flags;
>> +
>> +	pr_debug("%s()\n", __func__);
>> +
>> +	minor = 0;
>> +	if (w2sg_by_minor[minor]) {
>> +		pr_err("w2sg minor is already in use!\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>> +	if (data == NULL)
>> +		return -ENOMEM;
>> +
>> +	w2sg_by_minor[minor] = data;

While looking through this, I found a potential issue if allocating the
gpio fails with -EPROBE_DEFER.

Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
find it allocated on next deferred probe attempt.

So I'll move that to a position further down.

>> +
>> +	serdev_device_set_drvdata(serdev, data);
>> +
>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>> +						     "enable-gpios", 0,
>> +						     &flags);
>> +	/* defer until we have all gpios */
>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>> +		return -EPROBE_DEFER;
>> +
>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>> +							"lna");
>> +	if (IS_ERR(data->lna_regulator)) {
>> +		/* defer until we can get the regulator */
>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>> +			return -EPROBE_DEFER;
>> +
>> +		data->lna_regulator = NULL;
>> +	}
>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>> +
>> +	data->lna_blocked = true;
>> +	data->lna_is_off = true;
>> +
>> +	data->is_on = false;
>> +	data->requested = false;
>> +	data->state = W2SG_IDLE;
>> +	data->last_toggle = jiffies;
>> +	data->backoff = HZ;
>> +
>> +	data->uart = serdev;
>> +
>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>> +
>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>> +				"w2sg0004-on-off");
>> +	if (err < 0)
>> +		goto out;
>> +
>> +	gpio_direction_output(data->on_off_gpio, false);
>> +
>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>> +	serdev_device_open(data->uart);
>> +
>> +	serdev_device_set_baudrate(data->uart, 9600);
>> +	serdev_device_set_flow_control(data->uart, false);
>> +
>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>> +				&w2sg0004_rfkill_ops, data);
>> +	if (rf_kill == NULL) {
>> +		err = -ENOMEM;
>> +		goto err_rfkill;
>> +	}
>> +
>> +	err = rfkill_register(rf_kill);
>> +	if (err) {
>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>> +		goto err_rfkill;
>> +	}
>> +
>> +	data->rf_kill = rf_kill;
>> +
>> +	/* allocate the tty driver */
>> +	data->tty_drv = alloc_tty_driver(1);
>> +	if (!data->tty_drv)
>> +		return -ENOMEM;
>> +
>> +	/* initialize the tty driver */
>> +	data->tty_drv->owner = THIS_MODULE;
>> +	data->tty_drv->driver_name = "w2sg0004";
>> +	data->tty_drv->name = "ttyGPS";
>> +	data->tty_drv->major = 0;
>> +	data->tty_drv->minor_start = 0;
>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>> +	data->tty_drv->init_termios = tty_std_termios;
>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>> +					      HUPCL | CLOCAL;
>> +	/*
>> +	 * optional:
>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>> +					115200, 115200);
>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>> +	 */
>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>> +
>> +	/* register the tty driver */
>> +	err = tty_register_driver(data->tty_drv);
>> +	if (err) {
>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>> +			__func__, err);
>> +		put_tty_driver(data->tty_drv);
>> +		goto err_rfkill;
>> +	}
>> +
>> +	tty_port_init(&data->port);
>> +	data->port.ops = &w2sg_port_ops;

A test setting this to NULL leads to a kernel oops in:

[ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)

So we must define this completely empty struct w2sg_port_ops
and pass a reference.

>> +
>> +	pr_debug("w2sg call tty_port_register_device\n");
>> +
>> +	data->dev = tty_port_register_device(&data->port,
>> +			data->tty_drv, minor, &serdev->dev);
>> +
>> +	pr_debug("w2sg probed\n");
>> +
>> +	/* keep off until user space requests the device */
>> +	w2sg_set_power(data, false);
>> +
>> +	return 0;
>> +
>> +err_rfkill:
>> +	rfkill_destroy(rf_kill);
>> +	serdev_device_close(data->uart);
>> +out:
>> +	return err;
>> +}
>> +
>> +static void w2sg_remove(struct serdev_device *serdev)
>> +{
>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>> +	int minor;
>> +
>> +	cancel_delayed_work_sync(&data->work);
>> +
>> +	/* what is the right sequence to avoid problems? */
>> +	serdev_device_close(data->uart);
>> +
>> +	minor = 0;
>> +	tty_unregister_device(data->tty_drv, minor);
>> +
>> +	tty_unregister_driver(data->tty_drv);
>> +}
>> +
>> +static int w2sg_suspend(struct device *dev)
> 
> __maybe_unused, or if PM is disabled you will get a warning.

Ok.

> 
>> +{
>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>> +
>> +	data->suspended = true;
>> +
>> +	cancel_delayed_work_sync(&data->work);
>> +
>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>> +
>> +	if (data->state == W2SG_PULSE) {
>> +		msleep(10);
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->last_toggle = jiffies;
>> +		data->is_on = !data->is_on;
>> +		data->state = W2SG_NOPULSE;
>> +	}
>> +
>> +	if (data->state == W2SG_NOPULSE) {
>> +		msleep(10);
>> +		data->state = W2SG_IDLE;
>> +	}
>> +
>> +	if (data->is_on) {
>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>> +			data->is_on, data->lna_is_off);
>> +
>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>> +		msleep(10);
>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>> +		data->is_on = 0;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int w2sg_resume(struct device *dev)
>> +{
>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>> +
>> +	data->suspended = false;
>> +
>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>> +		data->is_on, data->lna_is_off);
>> +
>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id w2sg0004_of_match[] = {
>> +	{ .compatible = "wi2wi,w2sg0004" },
>> +	{ .compatible = "wi2wi,w2sg0084" },
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>> +
>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>> +
>> +static struct serdev_device_driver w2sg_driver = {
>> +	.probe		= w2sg_probe,
>> +	.remove		= w2sg_remove,
>> +	.driver = {
>> +		.name	= "w2sg0004",
>> +		.owner	= THIS_MODULE,
>> +		.pm	= &w2sg_pm_ops,
>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>> +	},
>> +};
>> +
>> +module_serdev_device_driver(w2sg_driver);
>> +
>> +MODULE_ALIAS("w2sg0004");
> 
> Is this needed?

Seems to be redundant.
After removal the driver is still loaded automatically after boot:

root at letux:~# modprobe -c | fgrep w2sg
alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
root at letux:~# lsmod | fgrep w2sg
w2sg0004               16384  2 
root at letux:~# 

> 
>> +
>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
> 
> Who really wrote this?

Good question.

The problem is that with cleaning up the code and rewriting history over such
a long time, it is no more visible.

But I have searched through our older branches:

Neil Brown had developed the first version for v3.7 in 2013: 
http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69

This is where the MODULE_AUTHOR line comes from.

Later, Marek Belisko worked on the UART driver, pinmux and interrupts.

And I have
* added lna-regulator and rfkill ca. v3.12
* added device tree support ca. v3.15
* moved to drivers/misc
* ported the code to serdev API ca. v4.11
* submitted to LKML and worked in comments by reviewers ca. 4.15

So this is the first version that is close to be acceptable.

Neil had also submitted different versions to LKML but I am not
sure if he still is active anywhere.

I have also checked a diff between the v3.7 version and the
current one and there are ca. 30-40% of lines original code by
Neil.

So I'd say:

* Neil is the original author and a significant amount of untouched code lines and comments are still there
* he designed the overall driver architecture
* Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
* Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
* I did add some important features but not the core code and driver architecture
* I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"

How should we best reflect this in the AUTHOR macro?

> 
>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>> +MODULE_LICENSE("GPL v2");
>> 


BR and thanks,
Nikolaus Schaller

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

* Re: [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
  2017-11-28 21:42       ` H. Nikolaus Schaller
  (?)
@ 2017-11-29 14:47         ` Andrew F. Davis
  -1 siblings, 0 replies; 33+ messages in thread
From: Andrew F. Davis @ 2017-11-29 14:47 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
	Russell King, Arnd Bergmann, Greg Kroah-Hartman, Kevin Hilman,
	Andreas Färber, Thierry Reding, Jonathan Cameron, DTML,
	Linux Kernel Mailing List, linux-omap,
	Discussions about the Letux Kernel, kernel, Linux ARM,
	Marek Belisko, Neil Brown

On 11/28/2017 03:42 PM, H. Nikolaus Schaller wrote:
> Hi Andrew,
> now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].
> 
>> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd@ti.com>:
>>
>> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>>>
>>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>>> and turn on/off the module. It also detects if the module is turned on (sends data)
>>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>>>
>>> Additionally, rfkill block/unblock can be used to control an external LNA
>>> (and power down the module if not needed).
>>>
>>> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
>>> but simplified and adapted to use the new serdev API introduced in 4.11.
>>>
>>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>>> ---
>>> drivers/misc/Kconfig    |  10 +
>>> drivers/misc/Makefile   |   1 +
>>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
> 
> I wonder if this shouldn't better go to
> 
>   drivers/gps
> 
> But this directory does not yet exist and has no overall maintainer.
> So it could be left in drivers/misc and moved as soon as drivers/gps
> is created elsewhere.
> 

Making that directory would imply the existence of a GPS driver
framework, until one comes about (or if you would like to create one), I
think misc is the most appropriate spot for now.

>>> 3 files changed, 556 insertions(+)
>>> create mode 100644 drivers/misc/w2sg0004.c
>>>
>>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>>> index 8136dc7e863d..09d171d68408 100644
>>> --- a/drivers/misc/Kconfig
>>> +++ b/drivers/misc/Kconfig
>>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>>> source "drivers/misc/genwqe/Kconfig"
>>> source "drivers/misc/echo/Kconfig"
>>> source "drivers/misc/cxl/Kconfig"
>>> +
>>> +config W2SG0004
>>> +	tristate "W2SG00x4 on/off control"
>>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>>> +	help
>>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>>> +	  is opened/closed.
>>> +	  It also provides a rfkill gps name to control the LNA power.
>>> +
>>> endmenu
>>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>>> index ad0e64fdba34..abcb667e0ff0 100644
>>> --- a/drivers/misc/Makefile
>>> +++ b/drivers/misc/Makefile
>>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>>> obj-y				+= mic/
>>> obj-$(CONFIG_GENWQE)		+= genwqe/
>>> obj-$(CONFIG_ECHO)		+= echo/
>>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>>> new file mode 100644
>>> index 000000000000..12e14b5e0a99
>>> --- /dev/null
>>> +++ b/drivers/misc/w2sg0004.c
>>> @@ -0,0 +1,545 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>
>> Damn this looks ugly, oh well :/
> 
> I could remove it for [PATCH v5] ... :\
> 

Nah, you should leave it, this comment was just me venting, Greg KH has
spoken, so this is the correct way now.

>>
>>> +/*
>>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>>> + *
>>
>>
>> Think you still need copyright tag here somewhere.
> 
> At the bottom there is:
> 
>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>> +MODULE_LICENSE("GPL v2");
> 
> Isn't that enough any more?
> 

Not sure.. someone needs to hold the copyright to this file and it
really should be close to the top.

>>
>>> + * This receiver has an ON/OFF pin which must be toggled to
>>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>>> + * will switch the device on if it is off, and off if it is on.
>>> + *
>>> + * To enable receiving on/off requests we register with the
>>> + * UART power management notifications.
>>> + *
>>> + * It is not possible to directly detect the state of the device.
>>> + * However when it is on it will send characters on a UART line
>>> + * regularly.
>>> + *
>>> + * To detect that the power state is out of sync (e.g. if GPS
>>> + * was enabled before a reboot), we register for UART data received
>>> + * notifications.
>>> + *
>>> + * In addition we register as a rfkill client so that we can
>>> + * control the LNA power.
>>> + *
>>> + */
>>> +
>>> +#include <linux/delay.h>
>>> +#include <linux/err.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/irq.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_irq.h>
>>> +#include <linux/of_gpio.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/rfkill.h>
>>> +#include <linux/serdev.h>
>>> +#include <linux/sched.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/tty.h>
>>> +#include <linux/tty_flip.h>
>>> +#include <linux/workqueue.h>
>>> +
>>> +/*
>>> + * There seems to be restrictions on how quickly we can toggle the
>>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>>> + * If we do it too soon it doesn't work.
>>> + * So we have a state machine which uses the common work queue to ensure
>>> + * clean transitions.
>>> + * When a change is requested we record that request and only act on it
>>> + * once the previous change has completed.
>>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>>> + * one change per second.
>>> + */
>>> +
>>> +enum w2sg_state {
>>> +	W2SG_IDLE,	/* is not changing state */
>>> +	W2SG_PULSE,	/* activate on/off impulse */
>>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>>> +};
>>> +
>>> +struct w2sg_data {
>>> +	struct		rfkill *rf_kill;
>>> +	struct		regulator *lna_regulator;
>>> +	int		lna_blocked;	/* rfkill block gps active */
>>> +	int		lna_is_off;	/* LNA is currently off */
>>> +	int		is_on;		/* current state (0/1) */
>>> +	unsigned long	last_toggle;
>>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>>> +	int		on_off_gpio;	/* the on-off gpio number */
>>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>>> +	struct		device *dev;	/* from tty_port_register_device() */
>>> +	struct		tty_port port;
>>> +	int		open_count;	/* how often we were opened */
>>> +	enum		w2sg_state state;
>>> +	int		requested;	/* requested state (0/1) */
>>> +	int		suspended;
>>> +	struct delayed_work work;
>>> +	int		discard_count;
>>> +};
>>> +
>>
>> Kernel doc style.
> 
> Is this really needed here?
> 
> For pure driver internal structs (they are not kernel infrastructure API) I usually
> consult the source code of a driver and never well formatted kernel doc. Hence I think
> readability by programmers looking into the source file is more important than serving
> kernel doc tools.
> 
> Yes, there are examples like:
> 
> https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113
> 
> But I find them more confusing than helpful because I have to jump between code lines
> to match the comment with the data type.
> 

I have no strong preference here as long as you think this is the most
readable way.

>>
>>> +static struct w2sg_data *w2sg_by_minor[1];
>>> +
>>
>> If you can only have one right now then just drop the array.
> 
> w2sg_get_by_minor() is prepared to access more than one record.
> And there are plans to have more than one (but I don't know exactly
> how to provide it).
> 

Just add it back when you get that functionality.

> Making it a non-array seems to be an unnecessary hurdle for such
> improvements. And regarding memory footprint, a single-element
> array is equivalent to a non-array.
> 

This comment was not about memory footprint, it's about sane code practices.

>>
>>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>>> +{
>>> +	int ret = 0;
>>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>>> +
>>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>>> +
>>> +	if (off != data->lna_is_off) {
>>> +		data->lna_is_off = off;
>>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>>> +			if (off)
>>> +				regulator_disable(data->lna_regulator);
>>> +			else
>>> +				ret = regulator_enable(data->lna_regulator);
>>> +		}
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static void w2sg_set_power(void *pdata, int val)
>>> +{
>>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>>> +
>>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>>> +
>>> +	if (val && !data->requested) {
>>> +		data->requested = true;
>>> +	} else if (!val && data->requested) {
>>> +		data->backoff = HZ;
>>> +		data->requested = false;
>>> +	} else
>>> +		return;
>>> +
>>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>>> +
>>> +	if (!data->suspended)
>>> +		schedule_delayed_work(&data->work, 0);
>>> +}
>>> +
>>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>>> +
>>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>>> +				const unsigned char *rxdata,
>>> +				size_t count)
>>> +{
>>> +	struct w2sg_data *data =
>>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>>> +
>>> +	if (!data->requested && !data->is_on) {
>>> +		/*
>>> +		 * we have received characters while the w2sg
>>> +		 * should have been be turned off
>>> +		 */
>>> +		data->discard_count += count;
>>> +		if ((data->state == W2SG_IDLE) &&
>>> +		    time_after(jiffies,
>>> +		    data->last_toggle + data->backoff)) {
>>> +			/* Should be off by now, time to toggle again */
>>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>>> +				data->discard_count);
>>> +
>>> +			data->discard_count = 0;
>>> +
>>> +			data->is_on = true;
>>> +			data->backoff *= 2;
>>> +			if (!data->suspended)
>>> +				schedule_delayed_work(&data->work, 0);
>>> +		}
>>> +	} else if (data->open_count > 0) {
>>> +		int n;
>>> +
>>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>>> +			(unsigned long) count);
>>> +
>>> +		/* pass to user-space */
>>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>>> +		if (n != count)
>>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>>> +				(unsigned long) (count - n));
>>> +		tty_flip_buffer_push(&data->port);
>>> +		return n;
>>> +	}
>>> +
>>> +	/* assume we have processed everything */
>>> +	return count;
>>> +}
>>> +
>>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>>> +
>>
>> drop extra line, same everywhere
> 
> Ok! Was just this and one more location.
> 
>>
>>> +static void toggle_work(struct work_struct *work)
>>> +{
>>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>>> +					      work.work);
>>> +
>>> +	switch (data->state) {
>>> +	case W2SG_IDLE:
>>> +		if (data->requested == data->is_on)
>>> +			return;
>>> +
>>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>> +		data->state = W2SG_PULSE;
>>> +
>>> +		pr_debug("w2sg: power gpio ON\n");
>>> +
>>> +		schedule_delayed_work(&data->work,
>>> +				      msecs_to_jiffies(10));
>>> +		break;
>>> +
>>> +	case W2SG_PULSE:
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->last_toggle = jiffies;
>>> +		data->state = W2SG_NOPULSE;
>>> +		data->is_on = !data->is_on;
>>> +
>>> +		pr_debug("w2sg: power gpio OFF\n");
>>> +
>>> +		schedule_delayed_work(&data->work,
>>> +				      msecs_to_jiffies(10));
>>> +		break;
>>> +
>>> +	case W2SG_NOPULSE:
>>> +		data->state = W2SG_IDLE;
>>> +
>>> +		pr_debug("w2sg: idle\n");
>>> +
>>> +		break;
>>> +
>>> +	}
>>> +}
>>> +
>>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>>> +{
>>> +	struct w2sg_data *data = pdata;
>>> +
>>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>>> +
>>> +	data->lna_blocked = blocked;
>>> +
>>> +	return w2sg_set_lna_power(data);
>>> +}
>>> +
>>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>>> +	.set_block = w2sg_rfkill_set_block,
>>> +};
>>> +
>>> +static struct serdev_device_ops serdev_ops = {
>>> +	.receive_buf = w2sg_uart_receive_buf,
>>> +};
>>> +
>>> +/*
>>> + * we are a man-in the middle between the user-space visible tty port
>>> + * and the serdev tty where the chip is connected.
>>> + * This allows us to recognise when the device should be powered on
>>> + * or off and handle the "false" state that data arrives while no
>>> + * users-space tty client exists.
>>> + */
>>> +
>>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>>> +{
>>> +	return w2sg_by_minor[minor];
>>> +}
>>> +
>>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>>> +{
>>> +	struct w2sg_data *data;
>>> +	int retval;
>>> +
>>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>>> +
>>> +	data = w2sg_get_by_minor(tty->index);
>>> +
>>> +	if (!data)
>>> +		return -ENODEV;
>>> +
>>> +	retval = tty_standard_install(driver, tty);
>>> +	if (retval)
>>> +		goto error_init_termios;
>>> +
>>> +	tty->driver_data = data;
>>> +
>>> +	return 0;
>>> +
>>> +error_init_termios:
>>> +	tty_port_put(&data->port);
>>> +	return retval;
>>> +}
>>> +
>>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>>> +
>>> +	w2sg_set_power(data, ++data->open_count > 0);
>>> +
>>> +	return tty_port_open(&data->port, tty, file);
>>> +}
>>> +
>>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	pr_debug("%s()\n", __func__);
>>> +
>>> +	w2sg_set_power(data, --data->open_count > 0);
>>> +
>>> +	tty_port_close(&data->port, tty, file);
>>> +}
>>> +
>>> +static int w2sg_tty_write(struct tty_struct *tty,
>>> +		const unsigned char *buffer, int count)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	/* simply pass down to UART */
>>> +	return serdev_device_write_buf(data->uart, buffer, count);
>>> +}
>>> +
>>> +static const struct tty_operations w2sg_serial_ops = {
>>> +	.install = w2sg_tty_install,
>>> +	.open = w2sg_tty_open,
>>> +	.close = w2sg_tty_close,
>>> +	.write = w2sg_tty_write,
>>> +};
>>> +
>>> +static const struct tty_port_operations w2sg_port_ops = {
>>> +};
>>
>> drop the brackets? or use NULL below, not sure if this needs memory.
> 
> This allocates a struct tty_port_operations initialized with NULL for
> all function pointers.
> 

Then just drop the brackets, or allocate it with kzalloc.

> I am not sure if this is the same as providing no w2sg_port_ops at all.
> 
> Rather I think the serial core is only port == NULL safe but not
> port->ops == NULL e.g.:
> 
> http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422
> 
> A test confirms (see comment below):
> 
>>
>>> +
>>> +static int w2sg_probe(struct serdev_device *serdev)
>>> +{
>>> +	struct w2sg_data *data;
>>> +	struct rfkill *rf_kill;
>>> +	int err;
>>> +	int minor;
>>> +	enum of_gpio_flags flags;
>>> +
>>> +	pr_debug("%s()\n", __func__);
>>> +
>>> +	minor = 0;
>>> +	if (w2sg_by_minor[minor]) {
>>> +		pr_err("w2sg minor is already in use!\n");
>>> +		return -ENODEV;
>>> +	}
>>> +
>>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>>> +	if (data == NULL)
>>> +		return -ENOMEM;
>>> +
>>> +	w2sg_by_minor[minor] = data;
> 
> While looking through this, I found a potential issue if allocating the
> gpio fails with -EPROBE_DEFER.
> 
> Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
> find it allocated on next deferred probe attempt.
> 
> So I'll move that to a position further down.
> 
>>> +
>>> +	serdev_device_set_drvdata(serdev, data);
>>> +
>>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>>> +						     "enable-gpios", 0,
>>> +						     &flags);
>>> +	/* defer until we have all gpios */
>>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>>> +		return -EPROBE_DEFER;
>>> +
>>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>>> +							"lna");
>>> +	if (IS_ERR(data->lna_regulator)) {
>>> +		/* defer until we can get the regulator */
>>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>>> +			return -EPROBE_DEFER;
>>> +
>>> +		data->lna_regulator = NULL;
>>> +	}
>>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>>> +
>>> +	data->lna_blocked = true;
>>> +	data->lna_is_off = true;
>>> +
>>> +	data->is_on = false;
>>> +	data->requested = false;
>>> +	data->state = W2SG_IDLE;
>>> +	data->last_toggle = jiffies;
>>> +	data->backoff = HZ;
>>> +
>>> +	data->uart = serdev;
>>> +
>>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>>> +
>>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>>> +				"w2sg0004-on-off");
>>> +	if (err < 0)
>>> +		goto out;
>>> +
>>> +	gpio_direction_output(data->on_off_gpio, false);
>>> +
>>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>>> +	serdev_device_open(data->uart);
>>> +
>>> +	serdev_device_set_baudrate(data->uart, 9600);
>>> +	serdev_device_set_flow_control(data->uart, false);
>>> +
>>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>>> +				&w2sg0004_rfkill_ops, data);
>>> +	if (rf_kill == NULL) {
>>> +		err = -ENOMEM;
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	err = rfkill_register(rf_kill);
>>> +	if (err) {
>>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	data->rf_kill = rf_kill;
>>> +
>>> +	/* allocate the tty driver */
>>> +	data->tty_drv = alloc_tty_driver(1);
>>> +	if (!data->tty_drv)
>>> +		return -ENOMEM;
>>> +
>>> +	/* initialize the tty driver */
>>> +	data->tty_drv->owner = THIS_MODULE;
>>> +	data->tty_drv->driver_name = "w2sg0004";
>>> +	data->tty_drv->name = "ttyGPS";
>>> +	data->tty_drv->major = 0;
>>> +	data->tty_drv->minor_start = 0;
>>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>>> +	data->tty_drv->init_termios = tty_std_termios;
>>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>>> +					      HUPCL | CLOCAL;
>>> +	/*
>>> +	 * optional:
>>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>>> +					115200, 115200);
>>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>>> +	 */
>>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>>> +
>>> +	/* register the tty driver */
>>> +	err = tty_register_driver(data->tty_drv);
>>> +	if (err) {
>>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>>> +			__func__, err);
>>> +		put_tty_driver(data->tty_drv);
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	tty_port_init(&data->port);
>>> +	data->port.ops = &w2sg_port_ops;
> 
> A test setting this to NULL leads to a kernel oops in:
> 
> [ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)
> 
> So we must define this completely empty struct w2sg_port_ops
> and pass a reference.
> 
>>> +
>>> +	pr_debug("w2sg call tty_port_register_device\n");
>>> +
>>> +	data->dev = tty_port_register_device(&data->port,
>>> +			data->tty_drv, minor, &serdev->dev);
>>> +
>>> +	pr_debug("w2sg probed\n");
>>> +
>>> +	/* keep off until user space requests the device */
>>> +	w2sg_set_power(data, false);
>>> +
>>> +	return 0;
>>> +
>>> +err_rfkill:
>>> +	rfkill_destroy(rf_kill);
>>> +	serdev_device_close(data->uart);
>>> +out:
>>> +	return err;
>>> +}
>>> +
>>> +static void w2sg_remove(struct serdev_device *serdev)
>>> +{
>>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>>> +	int minor;
>>> +
>>> +	cancel_delayed_work_sync(&data->work);
>>> +
>>> +	/* what is the right sequence to avoid problems? */
>>> +	serdev_device_close(data->uart);
>>> +
>>> +	minor = 0;
>>> +	tty_unregister_device(data->tty_drv, minor);
>>> +
>>> +	tty_unregister_driver(data->tty_drv);
>>> +}
>>> +
>>> +static int w2sg_suspend(struct device *dev)
>>
>> __maybe_unused, or if PM is disabled you will get a warning.
> 
> Ok.
> 
>>
>>> +{
>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>> +
>>> +	data->suspended = true;
>>> +
>>> +	cancel_delayed_work_sync(&data->work);
>>> +
>>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>>> +
>>> +	if (data->state == W2SG_PULSE) {
>>> +		msleep(10);
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->last_toggle = jiffies;
>>> +		data->is_on = !data->is_on;
>>> +		data->state = W2SG_NOPULSE;
>>> +	}
>>> +
>>> +	if (data->state == W2SG_NOPULSE) {
>>> +		msleep(10);
>>> +		data->state = W2SG_IDLE;
>>> +	}
>>> +
>>> +	if (data->is_on) {
>>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>>> +			data->is_on, data->lna_is_off);
>>> +
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>> +		msleep(10);
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->is_on = 0;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int w2sg_resume(struct device *dev)
>>> +{
>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>> +
>>> +	data->suspended = false;
>>> +
>>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>>> +		data->is_on, data->lna_is_off);
>>> +
>>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct of_device_id w2sg0004_of_match[] = {
>>> +	{ .compatible = "wi2wi,w2sg0004" },
>>> +	{ .compatible = "wi2wi,w2sg0084" },
>>> +	{},
>>> +};
>>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>>> +
>>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>>> +
>>> +static struct serdev_device_driver w2sg_driver = {
>>> +	.probe		= w2sg_probe,
>>> +	.remove		= w2sg_remove,
>>> +	.driver = {
>>> +		.name	= "w2sg0004",
>>> +		.owner	= THIS_MODULE,
>>> +		.pm	= &w2sg_pm_ops,
>>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>>> +	},
>>> +};
>>> +
>>> +module_serdev_device_driver(w2sg_driver);
>>> +
>>> +MODULE_ALIAS("w2sg0004");
>>
>> Is this needed?
> 
> Seems to be redundant.
> After removal the driver is still loaded automatically after boot:
> 
> root@letux:~# modprobe -c | fgrep w2sg
> alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
> alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
> alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
> alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
> root@letux:~# lsmod | fgrep w2sg
> w2sg0004               16384  2 
> root@letux:~# 
> 
>>
>>> +
>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>
>> Who really wrote this?
> 
> Good question.
> 
> The problem is that with cleaning up the code and rewriting history over such
> a long time, it is no more visible.
> 
> But I have searched through our older branches:
> 
> Neil Brown had developed the first version for v3.7 in 2013: 
> http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69
> 
> This is where the MODULE_AUTHOR line comes from.
> 
> Later, Marek Belisko worked on the UART driver, pinmux and interrupts.
> 
> And I have
> * added lna-regulator and rfkill ca. v3.12
> * added device tree support ca. v3.15
> * moved to drivers/misc
> * ported the code to serdev API ca. v4.11
> * submitted to LKML and worked in comments by reviewers ca. 4.15
> 
> So this is the first version that is close to be acceptable.
> 
> Neil had also submitted different versions to LKML but I am not
> sure if he still is active anywhere.
> 
> I have also checked a diff between the v3.7 version and the
> current one and there are ca. 30-40% of lines original code by
> Neil.
> 
> So I'd say:
> 
> * Neil is the original author and a significant amount of untouched code lines and comments are still there
> * he designed the overall driver architecture
> * Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
> * Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
> * I did add some important features but not the core code and driver architecture
> * I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"
> 
> How should we best reflect this in the AUTHOR macro?
> 

Easy:

MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");

you can have more than one :)

>>
>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>> +MODULE_LICENSE("GPL v2");
>>>
> 
> 
> BR and thanks,
> Nikolaus Schaller
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

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

* Re: [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-29 14:47         ` Andrew F. Davis
  0 siblings, 0 replies; 33+ messages in thread
From: Andrew F. Davis @ 2017-11-29 14:47 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Mark Rutland, DTML, linux-omap, Arnd Bergmann, Tony Lindgren,
	Greg Kroah-Hartman, kernel, Russell King,
	Linux Kernel Mailing List, Neil Brown, Rob Herring, Linux ARM,
	Benoît Cousson, Kevin Hilman, Marek Belisko,
	Discussions about the Letux Kernel, Thierry Reding,
	Andreas Färber, Jonathan Cameron

On 11/28/2017 03:42 PM, H. Nikolaus Schaller wrote:
> Hi Andrew,
> now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].
> 
>> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd@ti.com>:
>>
>> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>>>
>>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>>> and turn on/off the module. It also detects if the module is turned on (sends data)
>>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>>>
>>> Additionally, rfkill block/unblock can be used to control an external LNA
>>> (and power down the module if not needed).
>>>
>>> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
>>> but simplified and adapted to use the new serdev API introduced in 4.11.
>>>
>>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>>> ---
>>> drivers/misc/Kconfig    |  10 +
>>> drivers/misc/Makefile   |   1 +
>>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
> 
> I wonder if this shouldn't better go to
> 
>   drivers/gps
> 
> But this directory does not yet exist and has no overall maintainer.
> So it could be left in drivers/misc and moved as soon as drivers/gps
> is created elsewhere.
> 

Making that directory would imply the existence of a GPS driver
framework, until one comes about (or if you would like to create one), I
think misc is the most appropriate spot for now.

>>> 3 files changed, 556 insertions(+)
>>> create mode 100644 drivers/misc/w2sg0004.c
>>>
>>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>>> index 8136dc7e863d..09d171d68408 100644
>>> --- a/drivers/misc/Kconfig
>>> +++ b/drivers/misc/Kconfig
>>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>>> source "drivers/misc/genwqe/Kconfig"
>>> source "drivers/misc/echo/Kconfig"
>>> source "drivers/misc/cxl/Kconfig"
>>> +
>>> +config W2SG0004
>>> +	tristate "W2SG00x4 on/off control"
>>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>>> +	help
>>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>>> +	  is opened/closed.
>>> +	  It also provides a rfkill gps name to control the LNA power.
>>> +
>>> endmenu
>>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>>> index ad0e64fdba34..abcb667e0ff0 100644
>>> --- a/drivers/misc/Makefile
>>> +++ b/drivers/misc/Makefile
>>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>>> obj-y				+= mic/
>>> obj-$(CONFIG_GENWQE)		+= genwqe/
>>> obj-$(CONFIG_ECHO)		+= echo/
>>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>>> new file mode 100644
>>> index 000000000000..12e14b5e0a99
>>> --- /dev/null
>>> +++ b/drivers/misc/w2sg0004.c
>>> @@ -0,0 +1,545 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>
>> Damn this looks ugly, oh well :/
> 
> I could remove it for [PATCH v5] ... :\
> 

Nah, you should leave it, this comment was just me venting, Greg KH has
spoken, so this is the correct way now.

>>
>>> +/*
>>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>>> + *
>>
>>
>> Think you still need copyright tag here somewhere.
> 
> At the bottom there is:
> 
>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>> +MODULE_LICENSE("GPL v2");
> 
> Isn't that enough any more?
> 

Not sure.. someone needs to hold the copyright to this file and it
really should be close to the top.

>>
>>> + * This receiver has an ON/OFF pin which must be toggled to
>>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>>> + * will switch the device on if it is off, and off if it is on.
>>> + *
>>> + * To enable receiving on/off requests we register with the
>>> + * UART power management notifications.
>>> + *
>>> + * It is not possible to directly detect the state of the device.
>>> + * However when it is on it will send characters on a UART line
>>> + * regularly.
>>> + *
>>> + * To detect that the power state is out of sync (e.g. if GPS
>>> + * was enabled before a reboot), we register for UART data received
>>> + * notifications.
>>> + *
>>> + * In addition we register as a rfkill client so that we can
>>> + * control the LNA power.
>>> + *
>>> + */
>>> +
>>> +#include <linux/delay.h>
>>> +#include <linux/err.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/irq.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_irq.h>
>>> +#include <linux/of_gpio.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/rfkill.h>
>>> +#include <linux/serdev.h>
>>> +#include <linux/sched.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/tty.h>
>>> +#include <linux/tty_flip.h>
>>> +#include <linux/workqueue.h>
>>> +
>>> +/*
>>> + * There seems to be restrictions on how quickly we can toggle the
>>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>>> + * If we do it too soon it doesn't work.
>>> + * So we have a state machine which uses the common work queue to ensure
>>> + * clean transitions.
>>> + * When a change is requested we record that request and only act on it
>>> + * once the previous change has completed.
>>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>>> + * one change per second.
>>> + */
>>> +
>>> +enum w2sg_state {
>>> +	W2SG_IDLE,	/* is not changing state */
>>> +	W2SG_PULSE,	/* activate on/off impulse */
>>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>>> +};
>>> +
>>> +struct w2sg_data {
>>> +	struct		rfkill *rf_kill;
>>> +	struct		regulator *lna_regulator;
>>> +	int		lna_blocked;	/* rfkill block gps active */
>>> +	int		lna_is_off;	/* LNA is currently off */
>>> +	int		is_on;		/* current state (0/1) */
>>> +	unsigned long	last_toggle;
>>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>>> +	int		on_off_gpio;	/* the on-off gpio number */
>>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>>> +	struct		device *dev;	/* from tty_port_register_device() */
>>> +	struct		tty_port port;
>>> +	int		open_count;	/* how often we were opened */
>>> +	enum		w2sg_state state;
>>> +	int		requested;	/* requested state (0/1) */
>>> +	int		suspended;
>>> +	struct delayed_work work;
>>> +	int		discard_count;
>>> +};
>>> +
>>
>> Kernel doc style.
> 
> Is this really needed here?
> 
> For pure driver internal structs (they are not kernel infrastructure API) I usually
> consult the source code of a driver and never well formatted kernel doc. Hence I think
> readability by programmers looking into the source file is more important than serving
> kernel doc tools.
> 
> Yes, there are examples like:
> 
> https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113
> 
> But I find them more confusing than helpful because I have to jump between code lines
> to match the comment with the data type.
> 

I have no strong preference here as long as you think this is the most
readable way.

>>
>>> +static struct w2sg_data *w2sg_by_minor[1];
>>> +
>>
>> If you can only have one right now then just drop the array.
> 
> w2sg_get_by_minor() is prepared to access more than one record.
> And there are plans to have more than one (but I don't know exactly
> how to provide it).
> 

Just add it back when you get that functionality.

> Making it a non-array seems to be an unnecessary hurdle for such
> improvements. And regarding memory footprint, a single-element
> array is equivalent to a non-array.
> 

This comment was not about memory footprint, it's about sane code practices.

>>
>>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>>> +{
>>> +	int ret = 0;
>>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>>> +
>>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>>> +
>>> +	if (off != data->lna_is_off) {
>>> +		data->lna_is_off = off;
>>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>>> +			if (off)
>>> +				regulator_disable(data->lna_regulator);
>>> +			else
>>> +				ret = regulator_enable(data->lna_regulator);
>>> +		}
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static void w2sg_set_power(void *pdata, int val)
>>> +{
>>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>>> +
>>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>>> +
>>> +	if (val && !data->requested) {
>>> +		data->requested = true;
>>> +	} else if (!val && data->requested) {
>>> +		data->backoff = HZ;
>>> +		data->requested = false;
>>> +	} else
>>> +		return;
>>> +
>>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>>> +
>>> +	if (!data->suspended)
>>> +		schedule_delayed_work(&data->work, 0);
>>> +}
>>> +
>>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>>> +
>>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>>> +				const unsigned char *rxdata,
>>> +				size_t count)
>>> +{
>>> +	struct w2sg_data *data =
>>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>>> +
>>> +	if (!data->requested && !data->is_on) {
>>> +		/*
>>> +		 * we have received characters while the w2sg
>>> +		 * should have been be turned off
>>> +		 */
>>> +		data->discard_count += count;
>>> +		if ((data->state == W2SG_IDLE) &&
>>> +		    time_after(jiffies,
>>> +		    data->last_toggle + data->backoff)) {
>>> +			/* Should be off by now, time to toggle again */
>>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>>> +				data->discard_count);
>>> +
>>> +			data->discard_count = 0;
>>> +
>>> +			data->is_on = true;
>>> +			data->backoff *= 2;
>>> +			if (!data->suspended)
>>> +				schedule_delayed_work(&data->work, 0);
>>> +		}
>>> +	} else if (data->open_count > 0) {
>>> +		int n;
>>> +
>>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>>> +			(unsigned long) count);
>>> +
>>> +		/* pass to user-space */
>>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>>> +		if (n != count)
>>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>>> +				(unsigned long) (count - n));
>>> +		tty_flip_buffer_push(&data->port);
>>> +		return n;
>>> +	}
>>> +
>>> +	/* assume we have processed everything */
>>> +	return count;
>>> +}
>>> +
>>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>>> +
>>
>> drop extra line, same everywhere
> 
> Ok! Was just this and one more location.
> 
>>
>>> +static void toggle_work(struct work_struct *work)
>>> +{
>>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>>> +					      work.work);
>>> +
>>> +	switch (data->state) {
>>> +	case W2SG_IDLE:
>>> +		if (data->requested == data->is_on)
>>> +			return;
>>> +
>>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>> +		data->state = W2SG_PULSE;
>>> +
>>> +		pr_debug("w2sg: power gpio ON\n");
>>> +
>>> +		schedule_delayed_work(&data->work,
>>> +				      msecs_to_jiffies(10));
>>> +		break;
>>> +
>>> +	case W2SG_PULSE:
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->last_toggle = jiffies;
>>> +		data->state = W2SG_NOPULSE;
>>> +		data->is_on = !data->is_on;
>>> +
>>> +		pr_debug("w2sg: power gpio OFF\n");
>>> +
>>> +		schedule_delayed_work(&data->work,
>>> +				      msecs_to_jiffies(10));
>>> +		break;
>>> +
>>> +	case W2SG_NOPULSE:
>>> +		data->state = W2SG_IDLE;
>>> +
>>> +		pr_debug("w2sg: idle\n");
>>> +
>>> +		break;
>>> +
>>> +	}
>>> +}
>>> +
>>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>>> +{
>>> +	struct w2sg_data *data = pdata;
>>> +
>>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>>> +
>>> +	data->lna_blocked = blocked;
>>> +
>>> +	return w2sg_set_lna_power(data);
>>> +}
>>> +
>>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>>> +	.set_block = w2sg_rfkill_set_block,
>>> +};
>>> +
>>> +static struct serdev_device_ops serdev_ops = {
>>> +	.receive_buf = w2sg_uart_receive_buf,
>>> +};
>>> +
>>> +/*
>>> + * we are a man-in the middle between the user-space visible tty port
>>> + * and the serdev tty where the chip is connected.
>>> + * This allows us to recognise when the device should be powered on
>>> + * or off and handle the "false" state that data arrives while no
>>> + * users-space tty client exists.
>>> + */
>>> +
>>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>>> +{
>>> +	return w2sg_by_minor[minor];
>>> +}
>>> +
>>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>>> +{
>>> +	struct w2sg_data *data;
>>> +	int retval;
>>> +
>>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>>> +
>>> +	data = w2sg_get_by_minor(tty->index);
>>> +
>>> +	if (!data)
>>> +		return -ENODEV;
>>> +
>>> +	retval = tty_standard_install(driver, tty);
>>> +	if (retval)
>>> +		goto error_init_termios;
>>> +
>>> +	tty->driver_data = data;
>>> +
>>> +	return 0;
>>> +
>>> +error_init_termios:
>>> +	tty_port_put(&data->port);
>>> +	return retval;
>>> +}
>>> +
>>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>>> +
>>> +	w2sg_set_power(data, ++data->open_count > 0);
>>> +
>>> +	return tty_port_open(&data->port, tty, file);
>>> +}
>>> +
>>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	pr_debug("%s()\n", __func__);
>>> +
>>> +	w2sg_set_power(data, --data->open_count > 0);
>>> +
>>> +	tty_port_close(&data->port, tty, file);
>>> +}
>>> +
>>> +static int w2sg_tty_write(struct tty_struct *tty,
>>> +		const unsigned char *buffer, int count)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	/* simply pass down to UART */
>>> +	return serdev_device_write_buf(data->uart, buffer, count);
>>> +}
>>> +
>>> +static const struct tty_operations w2sg_serial_ops = {
>>> +	.install = w2sg_tty_install,
>>> +	.open = w2sg_tty_open,
>>> +	.close = w2sg_tty_close,
>>> +	.write = w2sg_tty_write,
>>> +};
>>> +
>>> +static const struct tty_port_operations w2sg_port_ops = {
>>> +};
>>
>> drop the brackets? or use NULL below, not sure if this needs memory.
> 
> This allocates a struct tty_port_operations initialized with NULL for
> all function pointers.
> 

Then just drop the brackets, or allocate it with kzalloc.

> I am not sure if this is the same as providing no w2sg_port_ops at all.
> 
> Rather I think the serial core is only port == NULL safe but not
> port->ops == NULL e.g.:
> 
> http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422
> 
> A test confirms (see comment below):
> 
>>
>>> +
>>> +static int w2sg_probe(struct serdev_device *serdev)
>>> +{
>>> +	struct w2sg_data *data;
>>> +	struct rfkill *rf_kill;
>>> +	int err;
>>> +	int minor;
>>> +	enum of_gpio_flags flags;
>>> +
>>> +	pr_debug("%s()\n", __func__);
>>> +
>>> +	minor = 0;
>>> +	if (w2sg_by_minor[minor]) {
>>> +		pr_err("w2sg minor is already in use!\n");
>>> +		return -ENODEV;
>>> +	}
>>> +
>>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>>> +	if (data == NULL)
>>> +		return -ENOMEM;
>>> +
>>> +	w2sg_by_minor[minor] = data;
> 
> While looking through this, I found a potential issue if allocating the
> gpio fails with -EPROBE_DEFER.
> 
> Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
> find it allocated on next deferred probe attempt.
> 
> So I'll move that to a position further down.
> 
>>> +
>>> +	serdev_device_set_drvdata(serdev, data);
>>> +
>>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>>> +						     "enable-gpios", 0,
>>> +						     &flags);
>>> +	/* defer until we have all gpios */
>>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>>> +		return -EPROBE_DEFER;
>>> +
>>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>>> +							"lna");
>>> +	if (IS_ERR(data->lna_regulator)) {
>>> +		/* defer until we can get the regulator */
>>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>>> +			return -EPROBE_DEFER;
>>> +
>>> +		data->lna_regulator = NULL;
>>> +	}
>>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>>> +
>>> +	data->lna_blocked = true;
>>> +	data->lna_is_off = true;
>>> +
>>> +	data->is_on = false;
>>> +	data->requested = false;
>>> +	data->state = W2SG_IDLE;
>>> +	data->last_toggle = jiffies;
>>> +	data->backoff = HZ;
>>> +
>>> +	data->uart = serdev;
>>> +
>>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>>> +
>>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>>> +				"w2sg0004-on-off");
>>> +	if (err < 0)
>>> +		goto out;
>>> +
>>> +	gpio_direction_output(data->on_off_gpio, false);
>>> +
>>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>>> +	serdev_device_open(data->uart);
>>> +
>>> +	serdev_device_set_baudrate(data->uart, 9600);
>>> +	serdev_device_set_flow_control(data->uart, false);
>>> +
>>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>>> +				&w2sg0004_rfkill_ops, data);
>>> +	if (rf_kill == NULL) {
>>> +		err = -ENOMEM;
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	err = rfkill_register(rf_kill);
>>> +	if (err) {
>>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	data->rf_kill = rf_kill;
>>> +
>>> +	/* allocate the tty driver */
>>> +	data->tty_drv = alloc_tty_driver(1);
>>> +	if (!data->tty_drv)
>>> +		return -ENOMEM;
>>> +
>>> +	/* initialize the tty driver */
>>> +	data->tty_drv->owner = THIS_MODULE;
>>> +	data->tty_drv->driver_name = "w2sg0004";
>>> +	data->tty_drv->name = "ttyGPS";
>>> +	data->tty_drv->major = 0;
>>> +	data->tty_drv->minor_start = 0;
>>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>>> +	data->tty_drv->init_termios = tty_std_termios;
>>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>>> +					      HUPCL | CLOCAL;
>>> +	/*
>>> +	 * optional:
>>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>>> +					115200, 115200);
>>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>>> +	 */
>>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>>> +
>>> +	/* register the tty driver */
>>> +	err = tty_register_driver(data->tty_drv);
>>> +	if (err) {
>>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>>> +			__func__, err);
>>> +		put_tty_driver(data->tty_drv);
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	tty_port_init(&data->port);
>>> +	data->port.ops = &w2sg_port_ops;
> 
> A test setting this to NULL leads to a kernel oops in:
> 
> [ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)
> 
> So we must define this completely empty struct w2sg_port_ops
> and pass a reference.
> 
>>> +
>>> +	pr_debug("w2sg call tty_port_register_device\n");
>>> +
>>> +	data->dev = tty_port_register_device(&data->port,
>>> +			data->tty_drv, minor, &serdev->dev);
>>> +
>>> +	pr_debug("w2sg probed\n");
>>> +
>>> +	/* keep off until user space requests the device */
>>> +	w2sg_set_power(data, false);
>>> +
>>> +	return 0;
>>> +
>>> +err_rfkill:
>>> +	rfkill_destroy(rf_kill);
>>> +	serdev_device_close(data->uart);
>>> +out:
>>> +	return err;
>>> +}
>>> +
>>> +static void w2sg_remove(struct serdev_device *serdev)
>>> +{
>>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>>> +	int minor;
>>> +
>>> +	cancel_delayed_work_sync(&data->work);
>>> +
>>> +	/* what is the right sequence to avoid problems? */
>>> +	serdev_device_close(data->uart);
>>> +
>>> +	minor = 0;
>>> +	tty_unregister_device(data->tty_drv, minor);
>>> +
>>> +	tty_unregister_driver(data->tty_drv);
>>> +}
>>> +
>>> +static int w2sg_suspend(struct device *dev)
>>
>> __maybe_unused, or if PM is disabled you will get a warning.
> 
> Ok.
> 
>>
>>> +{
>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>> +
>>> +	data->suspended = true;
>>> +
>>> +	cancel_delayed_work_sync(&data->work);
>>> +
>>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>>> +
>>> +	if (data->state == W2SG_PULSE) {
>>> +		msleep(10);
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->last_toggle = jiffies;
>>> +		data->is_on = !data->is_on;
>>> +		data->state = W2SG_NOPULSE;
>>> +	}
>>> +
>>> +	if (data->state == W2SG_NOPULSE) {
>>> +		msleep(10);
>>> +		data->state = W2SG_IDLE;
>>> +	}
>>> +
>>> +	if (data->is_on) {
>>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>>> +			data->is_on, data->lna_is_off);
>>> +
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>> +		msleep(10);
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->is_on = 0;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int w2sg_resume(struct device *dev)
>>> +{
>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>> +
>>> +	data->suspended = false;
>>> +
>>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>>> +		data->is_on, data->lna_is_off);
>>> +
>>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct of_device_id w2sg0004_of_match[] = {
>>> +	{ .compatible = "wi2wi,w2sg0004" },
>>> +	{ .compatible = "wi2wi,w2sg0084" },
>>> +	{},
>>> +};
>>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>>> +
>>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>>> +
>>> +static struct serdev_device_driver w2sg_driver = {
>>> +	.probe		= w2sg_probe,
>>> +	.remove		= w2sg_remove,
>>> +	.driver = {
>>> +		.name	= "w2sg0004",
>>> +		.owner	= THIS_MODULE,
>>> +		.pm	= &w2sg_pm_ops,
>>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>>> +	},
>>> +};
>>> +
>>> +module_serdev_device_driver(w2sg_driver);
>>> +
>>> +MODULE_ALIAS("w2sg0004");
>>
>> Is this needed?
> 
> Seems to be redundant.
> After removal the driver is still loaded automatically after boot:
> 
> root@letux:~# modprobe -c | fgrep w2sg
> alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
> alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
> alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
> alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
> root@letux:~# lsmod | fgrep w2sg
> w2sg0004               16384  2 
> root@letux:~# 
> 
>>
>>> +
>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>
>> Who really wrote this?
> 
> Good question.
> 
> The problem is that with cleaning up the code and rewriting history over such
> a long time, it is no more visible.
> 
> But I have searched through our older branches:
> 
> Neil Brown had developed the first version for v3.7 in 2013: 
> http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69
> 
> This is where the MODULE_AUTHOR line comes from.
> 
> Later, Marek Belisko worked on the UART driver, pinmux and interrupts.
> 
> And I have
> * added lna-regulator and rfkill ca. v3.12
> * added device tree support ca. v3.15
> * moved to drivers/misc
> * ported the code to serdev API ca. v4.11
> * submitted to LKML and worked in comments by reviewers ca. 4.15
> 
> So this is the first version that is close to be acceptable.
> 
> Neil had also submitted different versions to LKML but I am not
> sure if he still is active anywhere.
> 
> I have also checked a diff between the v3.7 version and the
> current one and there are ca. 30-40% of lines original code by
> Neil.
> 
> So I'd say:
> 
> * Neil is the original author and a significant amount of untouched code lines and comments are still there
> * he designed the overall driver architecture
> * Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
> * Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
> * I did add some important features but not the core code and driver architecture
> * I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"
> 
> How should we best reflect this in the AUTHOR macro?
> 

Easy:

MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");

you can have more than one :)

>>
>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>> +MODULE_LICENSE("GPL v2");
>>>
> 
> 
> BR and thanks,
> Nikolaus Schaller
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

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

* [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-11-29 14:47         ` Andrew F. Davis
  0 siblings, 0 replies; 33+ messages in thread
From: Andrew F. Davis @ 2017-11-29 14:47 UTC (permalink / raw)
  To: linux-arm-kernel

On 11/28/2017 03:42 PM, H. Nikolaus Schaller wrote:
> Hi Andrew,
> now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].
> 
>> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd@ti.com>:
>>
>> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>>>
>>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>>> and turn on/off the module. It also detects if the module is turned on (sends data)
>>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>>>
>>> Additionally, rfkill block/unblock can be used to control an external LNA
>>> (and power down the module if not needed).
>>>
>>> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
>>> but simplified and adapted to use the new serdev API introduced in 4.11.
>>>
>>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>>> ---
>>> drivers/misc/Kconfig    |  10 +
>>> drivers/misc/Makefile   |   1 +
>>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
> 
> I wonder if this shouldn't better go to
> 
>   drivers/gps
> 
> But this directory does not yet exist and has no overall maintainer.
> So it could be left in drivers/misc and moved as soon as drivers/gps
> is created elsewhere.
> 

Making that directory would imply the existence of a GPS driver
framework, until one comes about (or if you would like to create one), I
think misc is the most appropriate spot for now.

>>> 3 files changed, 556 insertions(+)
>>> create mode 100644 drivers/misc/w2sg0004.c
>>>
>>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>>> index 8136dc7e863d..09d171d68408 100644
>>> --- a/drivers/misc/Kconfig
>>> +++ b/drivers/misc/Kconfig
>>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>>> source "drivers/misc/genwqe/Kconfig"
>>> source "drivers/misc/echo/Kconfig"
>>> source "drivers/misc/cxl/Kconfig"
>>> +
>>> +config W2SG0004
>>> +	tristate "W2SG00x4 on/off control"
>>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>>> +	help
>>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>>> +	  is opened/closed.
>>> +	  It also provides a rfkill gps name to control the LNA power.
>>> +
>>> endmenu
>>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>>> index ad0e64fdba34..abcb667e0ff0 100644
>>> --- a/drivers/misc/Makefile
>>> +++ b/drivers/misc/Makefile
>>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>>> obj-y				+= mic/
>>> obj-$(CONFIG_GENWQE)		+= genwqe/
>>> obj-$(CONFIG_ECHO)		+= echo/
>>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>>> new file mode 100644
>>> index 000000000000..12e14b5e0a99
>>> --- /dev/null
>>> +++ b/drivers/misc/w2sg0004.c
>>> @@ -0,0 +1,545 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>
>> Damn this looks ugly, oh well :/
> 
> I could remove it for [PATCH v5] ... :\
> 

Nah, you should leave it, this comment was just me venting, Greg KH has
spoken, so this is the correct way now.

>>
>>> +/*
>>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>>> + *
>>
>>
>> Think you still need copyright tag here somewhere.
> 
> At the bottom there is:
> 
>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>> +MODULE_LICENSE("GPL v2");
> 
> Isn't that enough any more?
> 

Not sure.. someone needs to hold the copyright to this file and it
really should be close to the top.

>>
>>> + * This receiver has an ON/OFF pin which must be toggled to
>>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>>> + * will switch the device on if it is off, and off if it is on.
>>> + *
>>> + * To enable receiving on/off requests we register with the
>>> + * UART power management notifications.
>>> + *
>>> + * It is not possible to directly detect the state of the device.
>>> + * However when it is on it will send characters on a UART line
>>> + * regularly.
>>> + *
>>> + * To detect that the power state is out of sync (e.g. if GPS
>>> + * was enabled before a reboot), we register for UART data received
>>> + * notifications.
>>> + *
>>> + * In addition we register as a rfkill client so that we can
>>> + * control the LNA power.
>>> + *
>>> + */
>>> +
>>> +#include <linux/delay.h>
>>> +#include <linux/err.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/irq.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_irq.h>
>>> +#include <linux/of_gpio.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/rfkill.h>
>>> +#include <linux/serdev.h>
>>> +#include <linux/sched.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/tty.h>
>>> +#include <linux/tty_flip.h>
>>> +#include <linux/workqueue.h>
>>> +
>>> +/*
>>> + * There seems to be restrictions on how quickly we can toggle the
>>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>>> + * If we do it too soon it doesn't work.
>>> + * So we have a state machine which uses the common work queue to ensure
>>> + * clean transitions.
>>> + * When a change is requested we record that request and only act on it
>>> + * once the previous change has completed.
>>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>>> + * one change per second.
>>> + */
>>> +
>>> +enum w2sg_state {
>>> +	W2SG_IDLE,	/* is not changing state */
>>> +	W2SG_PULSE,	/* activate on/off impulse */
>>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>>> +};
>>> +
>>> +struct w2sg_data {
>>> +	struct		rfkill *rf_kill;
>>> +	struct		regulator *lna_regulator;
>>> +	int		lna_blocked;	/* rfkill block gps active */
>>> +	int		lna_is_off;	/* LNA is currently off */
>>> +	int		is_on;		/* current state (0/1) */
>>> +	unsigned long	last_toggle;
>>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>>> +	int		on_off_gpio;	/* the on-off gpio number */
>>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>>> +	struct		device *dev;	/* from tty_port_register_device() */
>>> +	struct		tty_port port;
>>> +	int		open_count;	/* how often we were opened */
>>> +	enum		w2sg_state state;
>>> +	int		requested;	/* requested state (0/1) */
>>> +	int		suspended;
>>> +	struct delayed_work work;
>>> +	int		discard_count;
>>> +};
>>> +
>>
>> Kernel doc style.
> 
> Is this really needed here?
> 
> For pure driver internal structs (they are not kernel infrastructure API) I usually
> consult the source code of a driver and never well formatted kernel doc. Hence I think
> readability by programmers looking into the source file is more important than serving
> kernel doc tools.
> 
> Yes, there are examples like:
> 
> https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113
> 
> But I find them more confusing than helpful because I have to jump between code lines
> to match the comment with the data type.
> 

I have no strong preference here as long as you think this is the most
readable way.

>>
>>> +static struct w2sg_data *w2sg_by_minor[1];
>>> +
>>
>> If you can only have one right now then just drop the array.
> 
> w2sg_get_by_minor() is prepared to access more than one record.
> And there are plans to have more than one (but I don't know exactly
> how to provide it).
> 

Just add it back when you get that functionality.

> Making it a non-array seems to be an unnecessary hurdle for such
> improvements. And regarding memory footprint, a single-element
> array is equivalent to a non-array.
> 

This comment was not about memory footprint, it's about sane code practices.

>>
>>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>>> +{
>>> +	int ret = 0;
>>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>>> +
>>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>>> +
>>> +	if (off != data->lna_is_off) {
>>> +		data->lna_is_off = off;
>>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>>> +			if (off)
>>> +				regulator_disable(data->lna_regulator);
>>> +			else
>>> +				ret = regulator_enable(data->lna_regulator);
>>> +		}
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static void w2sg_set_power(void *pdata, int val)
>>> +{
>>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>>> +
>>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>>> +
>>> +	if (val && !data->requested) {
>>> +		data->requested = true;
>>> +	} else if (!val && data->requested) {
>>> +		data->backoff = HZ;
>>> +		data->requested = false;
>>> +	} else
>>> +		return;
>>> +
>>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>>> +
>>> +	if (!data->suspended)
>>> +		schedule_delayed_work(&data->work, 0);
>>> +}
>>> +
>>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>>> +
>>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>>> +				const unsigned char *rxdata,
>>> +				size_t count)
>>> +{
>>> +	struct w2sg_data *data =
>>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>>> +
>>> +	if (!data->requested && !data->is_on) {
>>> +		/*
>>> +		 * we have received characters while the w2sg
>>> +		 * should have been be turned off
>>> +		 */
>>> +		data->discard_count += count;
>>> +		if ((data->state == W2SG_IDLE) &&
>>> +		    time_after(jiffies,
>>> +		    data->last_toggle + data->backoff)) {
>>> +			/* Should be off by now, time to toggle again */
>>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>>> +				data->discard_count);
>>> +
>>> +			data->discard_count = 0;
>>> +
>>> +			data->is_on = true;
>>> +			data->backoff *= 2;
>>> +			if (!data->suspended)
>>> +				schedule_delayed_work(&data->work, 0);
>>> +		}
>>> +	} else if (data->open_count > 0) {
>>> +		int n;
>>> +
>>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>>> +			(unsigned long) count);
>>> +
>>> +		/* pass to user-space */
>>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>>> +		if (n != count)
>>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>>> +				(unsigned long) (count - n));
>>> +		tty_flip_buffer_push(&data->port);
>>> +		return n;
>>> +	}
>>> +
>>> +	/* assume we have processed everything */
>>> +	return count;
>>> +}
>>> +
>>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>>> +
>>
>> drop extra line, same everywhere
> 
> Ok! Was just this and one more location.
> 
>>
>>> +static void toggle_work(struct work_struct *work)
>>> +{
>>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>>> +					      work.work);
>>> +
>>> +	switch (data->state) {
>>> +	case W2SG_IDLE:
>>> +		if (data->requested == data->is_on)
>>> +			return;
>>> +
>>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>> +		data->state = W2SG_PULSE;
>>> +
>>> +		pr_debug("w2sg: power gpio ON\n");
>>> +
>>> +		schedule_delayed_work(&data->work,
>>> +				      msecs_to_jiffies(10));
>>> +		break;
>>> +
>>> +	case W2SG_PULSE:
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->last_toggle = jiffies;
>>> +		data->state = W2SG_NOPULSE;
>>> +		data->is_on = !data->is_on;
>>> +
>>> +		pr_debug("w2sg: power gpio OFF\n");
>>> +
>>> +		schedule_delayed_work(&data->work,
>>> +				      msecs_to_jiffies(10));
>>> +		break;
>>> +
>>> +	case W2SG_NOPULSE:
>>> +		data->state = W2SG_IDLE;
>>> +
>>> +		pr_debug("w2sg: idle\n");
>>> +
>>> +		break;
>>> +
>>> +	}
>>> +}
>>> +
>>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>>> +{
>>> +	struct w2sg_data *data = pdata;
>>> +
>>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>>> +
>>> +	data->lna_blocked = blocked;
>>> +
>>> +	return w2sg_set_lna_power(data);
>>> +}
>>> +
>>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>>> +	.set_block = w2sg_rfkill_set_block,
>>> +};
>>> +
>>> +static struct serdev_device_ops serdev_ops = {
>>> +	.receive_buf = w2sg_uart_receive_buf,
>>> +};
>>> +
>>> +/*
>>> + * we are a man-in the middle between the user-space visible tty port
>>> + * and the serdev tty where the chip is connected.
>>> + * This allows us to recognise when the device should be powered on
>>> + * or off and handle the "false" state that data arrives while no
>>> + * users-space tty client exists.
>>> + */
>>> +
>>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>>> +{
>>> +	return w2sg_by_minor[minor];
>>> +}
>>> +
>>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>>> +{
>>> +	struct w2sg_data *data;
>>> +	int retval;
>>> +
>>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>>> +
>>> +	data = w2sg_get_by_minor(tty->index);
>>> +
>>> +	if (!data)
>>> +		return -ENODEV;
>>> +
>>> +	retval = tty_standard_install(driver, tty);
>>> +	if (retval)
>>> +		goto error_init_termios;
>>> +
>>> +	tty->driver_data = data;
>>> +
>>> +	return 0;
>>> +
>>> +error_init_termios:
>>> +	tty_port_put(&data->port);
>>> +	return retval;
>>> +}
>>> +
>>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>>> +
>>> +	w2sg_set_power(data, ++data->open_count > 0);
>>> +
>>> +	return tty_port_open(&data->port, tty, file);
>>> +}
>>> +
>>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	pr_debug("%s()\n", __func__);
>>> +
>>> +	w2sg_set_power(data, --data->open_count > 0);
>>> +
>>> +	tty_port_close(&data->port, tty, file);
>>> +}
>>> +
>>> +static int w2sg_tty_write(struct tty_struct *tty,
>>> +		const unsigned char *buffer, int count)
>>> +{
>>> +	struct w2sg_data *data = tty->driver_data;
>>> +
>>> +	/* simply pass down to UART */
>>> +	return serdev_device_write_buf(data->uart, buffer, count);
>>> +}
>>> +
>>> +static const struct tty_operations w2sg_serial_ops = {
>>> +	.install = w2sg_tty_install,
>>> +	.open = w2sg_tty_open,
>>> +	.close = w2sg_tty_close,
>>> +	.write = w2sg_tty_write,
>>> +};
>>> +
>>> +static const struct tty_port_operations w2sg_port_ops = {
>>> +};
>>
>> drop the brackets? or use NULL below, not sure if this needs memory.
> 
> This allocates a struct tty_port_operations initialized with NULL for
> all function pointers.
> 

Then just drop the brackets, or allocate it with kzalloc.

> I am not sure if this is the same as providing no w2sg_port_ops at all.
> 
> Rather I think the serial core is only port == NULL safe but not
> port->ops == NULL e.g.:
> 
> http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422
> 
> A test confirms (see comment below):
> 
>>
>>> +
>>> +static int w2sg_probe(struct serdev_device *serdev)
>>> +{
>>> +	struct w2sg_data *data;
>>> +	struct rfkill *rf_kill;
>>> +	int err;
>>> +	int minor;
>>> +	enum of_gpio_flags flags;
>>> +
>>> +	pr_debug("%s()\n", __func__);
>>> +
>>> +	minor = 0;
>>> +	if (w2sg_by_minor[minor]) {
>>> +		pr_err("w2sg minor is already in use!\n");
>>> +		return -ENODEV;
>>> +	}
>>> +
>>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>>> +	if (data == NULL)
>>> +		return -ENOMEM;
>>> +
>>> +	w2sg_by_minor[minor] = data;
> 
> While looking through this, I found a potential issue if allocating the
> gpio fails with -EPROBE_DEFER.
> 
> Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
> find it allocated on next deferred probe attempt.
> 
> So I'll move that to a position further down.
> 
>>> +
>>> +	serdev_device_set_drvdata(serdev, data);
>>> +
>>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>>> +						     "enable-gpios", 0,
>>> +						     &flags);
>>> +	/* defer until we have all gpios */
>>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>>> +		return -EPROBE_DEFER;
>>> +
>>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>>> +							"lna");
>>> +	if (IS_ERR(data->lna_regulator)) {
>>> +		/* defer until we can get the regulator */
>>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>>> +			return -EPROBE_DEFER;
>>> +
>>> +		data->lna_regulator = NULL;
>>> +	}
>>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>>> +
>>> +	data->lna_blocked = true;
>>> +	data->lna_is_off = true;
>>> +
>>> +	data->is_on = false;
>>> +	data->requested = false;
>>> +	data->state = W2SG_IDLE;
>>> +	data->last_toggle = jiffies;
>>> +	data->backoff = HZ;
>>> +
>>> +	data->uart = serdev;
>>> +
>>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>>> +
>>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>>> +				"w2sg0004-on-off");
>>> +	if (err < 0)
>>> +		goto out;
>>> +
>>> +	gpio_direction_output(data->on_off_gpio, false);
>>> +
>>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>>> +	serdev_device_open(data->uart);
>>> +
>>> +	serdev_device_set_baudrate(data->uart, 9600);
>>> +	serdev_device_set_flow_control(data->uart, false);
>>> +
>>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>>> +				&w2sg0004_rfkill_ops, data);
>>> +	if (rf_kill == NULL) {
>>> +		err = -ENOMEM;
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	err = rfkill_register(rf_kill);
>>> +	if (err) {
>>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	data->rf_kill = rf_kill;
>>> +
>>> +	/* allocate the tty driver */
>>> +	data->tty_drv = alloc_tty_driver(1);
>>> +	if (!data->tty_drv)
>>> +		return -ENOMEM;
>>> +
>>> +	/* initialize the tty driver */
>>> +	data->tty_drv->owner = THIS_MODULE;
>>> +	data->tty_drv->driver_name = "w2sg0004";
>>> +	data->tty_drv->name = "ttyGPS";
>>> +	data->tty_drv->major = 0;
>>> +	data->tty_drv->minor_start = 0;
>>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>>> +	data->tty_drv->init_termios = tty_std_termios;
>>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>>> +					      HUPCL | CLOCAL;
>>> +	/*
>>> +	 * optional:
>>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>>> +					115200, 115200);
>>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>>> +	 */
>>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>>> +
>>> +	/* register the tty driver */
>>> +	err = tty_register_driver(data->tty_drv);
>>> +	if (err) {
>>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>>> +			__func__, err);
>>> +		put_tty_driver(data->tty_drv);
>>> +		goto err_rfkill;
>>> +	}
>>> +
>>> +	tty_port_init(&data->port);
>>> +	data->port.ops = &w2sg_port_ops;
> 
> A test setting this to NULL leads to a kernel oops in:
> 
> [ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)
> 
> So we must define this completely empty struct w2sg_port_ops
> and pass a reference.
> 
>>> +
>>> +	pr_debug("w2sg call tty_port_register_device\n");
>>> +
>>> +	data->dev = tty_port_register_device(&data->port,
>>> +			data->tty_drv, minor, &serdev->dev);
>>> +
>>> +	pr_debug("w2sg probed\n");
>>> +
>>> +	/* keep off until user space requests the device */
>>> +	w2sg_set_power(data, false);
>>> +
>>> +	return 0;
>>> +
>>> +err_rfkill:
>>> +	rfkill_destroy(rf_kill);
>>> +	serdev_device_close(data->uart);
>>> +out:
>>> +	return err;
>>> +}
>>> +
>>> +static void w2sg_remove(struct serdev_device *serdev)
>>> +{
>>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>>> +	int minor;
>>> +
>>> +	cancel_delayed_work_sync(&data->work);
>>> +
>>> +	/* what is the right sequence to avoid problems? */
>>> +	serdev_device_close(data->uart);
>>> +
>>> +	minor = 0;
>>> +	tty_unregister_device(data->tty_drv, minor);
>>> +
>>> +	tty_unregister_driver(data->tty_drv);
>>> +}
>>> +
>>> +static int w2sg_suspend(struct device *dev)
>>
>> __maybe_unused, or if PM is disabled you will get a warning.
> 
> Ok.
> 
>>
>>> +{
>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>> +
>>> +	data->suspended = true;
>>> +
>>> +	cancel_delayed_work_sync(&data->work);
>>> +
>>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>>> +
>>> +	if (data->state == W2SG_PULSE) {
>>> +		msleep(10);
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->last_toggle = jiffies;
>>> +		data->is_on = !data->is_on;
>>> +		data->state = W2SG_NOPULSE;
>>> +	}
>>> +
>>> +	if (data->state == W2SG_NOPULSE) {
>>> +		msleep(10);
>>> +		data->state = W2SG_IDLE;
>>> +	}
>>> +
>>> +	if (data->is_on) {
>>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>>> +			data->is_on, data->lna_is_off);
>>> +
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>> +		msleep(10);
>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>> +		data->is_on = 0;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int w2sg_resume(struct device *dev)
>>> +{
>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>> +
>>> +	data->suspended = false;
>>> +
>>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>>> +		data->is_on, data->lna_is_off);
>>> +
>>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct of_device_id w2sg0004_of_match[] = {
>>> +	{ .compatible = "wi2wi,w2sg0004" },
>>> +	{ .compatible = "wi2wi,w2sg0084" },
>>> +	{},
>>> +};
>>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>>> +
>>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>>> +
>>> +static struct serdev_device_driver w2sg_driver = {
>>> +	.probe		= w2sg_probe,
>>> +	.remove		= w2sg_remove,
>>> +	.driver = {
>>> +		.name	= "w2sg0004",
>>> +		.owner	= THIS_MODULE,
>>> +		.pm	= &w2sg_pm_ops,
>>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>>> +	},
>>> +};
>>> +
>>> +module_serdev_device_driver(w2sg_driver);
>>> +
>>> +MODULE_ALIAS("w2sg0004");
>>
>> Is this needed?
> 
> Seems to be redundant.
> After removal the driver is still loaded automatically after boot:
> 
> root at letux:~# modprobe -c | fgrep w2sg
> alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
> alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
> alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
> alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
> root at letux:~# lsmod | fgrep w2sg
> w2sg0004               16384  2 
> root at letux:~# 
> 
>>
>>> +
>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>
>> Who really wrote this?
> 
> Good question.
> 
> The problem is that with cleaning up the code and rewriting history over such
> a long time, it is no more visible.
> 
> But I have searched through our older branches:
> 
> Neil Brown had developed the first version for v3.7 in 2013: 
> http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69
> 
> This is where the MODULE_AUTHOR line comes from.
> 
> Later, Marek Belisko worked on the UART driver, pinmux and interrupts.
> 
> And I have
> * added lna-regulator and rfkill ca. v3.12
> * added device tree support ca. v3.15
> * moved to drivers/misc
> * ported the code to serdev API ca. v4.11
> * submitted to LKML and worked in comments by reviewers ca. 4.15
> 
> So this is the first version that is close to be acceptable.
> 
> Neil had also submitted different versions to LKML but I am not
> sure if he still is active anywhere.
> 
> I have also checked a diff between the v3.7 version and the
> current one and there are ca. 30-40% of lines original code by
> Neil.
> 
> So I'd say:
> 
> * Neil is the original author and a significant amount of untouched code lines and comments are still there
> * he designed the overall driver architecture
> * Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
> * Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
> * I did add some important features but not the core code and driver architecture
> * I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"
> 
> How should we best reflect this in the AUTHOR macro?
> 

Easy:

MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");

you can have more than one :)

>>
>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>> +MODULE_LICENSE("GPL v2");
>>>
> 
> 
> BR and thanks,
> Nikolaus Schaller
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

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

* Re: [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
  2017-11-29 14:47         ` Andrew F. Davis
@ 2017-12-01  7:49           ` H. Nikolaus Schaller
  -1 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-12-01  7:49 UTC (permalink / raw)
  To: Andrew F. Davis
  Cc: Mark Rutland, DTML, linux-omap, Arnd Bergmann, Tony Lindgren,
	Greg Kroah-Hartman, kernel, Russell King,
	Linux Kernel Mailing List, Neil Brown, Rob Herring, Linux ARM,
	Benoît Cousson, Kevin Hilman, Marek Belisko,
	Discussions about the Letux Kernel, Thierry Reding,
	Andreas Färber, Jonathan Cameron

Hi,

> Am 29.11.2017 um 15:47 schrieb Andrew F. Davis <afd@ti.com>:
> 
> On 11/28/2017 03:42 PM, H. Nikolaus Schaller wrote:
>> Hi Andrew,
>> now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].
>> 
>>> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd@ti.com>:
>>> 
>>> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>>>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>>>> 
>>>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>>>> and turn on/off the module. It also detects if the module is turned on (sends data)
>>>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>>>> 
>>>> Additionally, rfkill block/unblock can be used to control an external LNA
>>>> (and power down the module if not needed).
>>>> 
>>>> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
>>>> but simplified and adapted to use the new serdev API introduced in 4.11.
>>>> 
>>>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>>>> ---
>>>> drivers/misc/Kconfig    |  10 +
>>>> drivers/misc/Makefile   |   1 +
>>>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
>> 
>> I wonder if this shouldn't better go to
>> 
>>  drivers/gps
>> 
>> But this directory does not yet exist and has no overall maintainer.
>> So it could be left in drivers/misc and moved as soon as drivers/gps
>> is created elsewhere.
>> 
> 
> Making that directory would imply the existence of a GPS driver
> framework, until one comes about (or if you would like to create one), I
> think misc is the most appropriate spot for now.

Understood and fine with it.
I just wanted to have asked and clarified.

> 
>>>> 3 files changed, 556 insertions(+)
>>>> create mode 100644 drivers/misc/w2sg0004.c
>>>> 
>>>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>>>> index 8136dc7e863d..09d171d68408 100644
>>>> --- a/drivers/misc/Kconfig
>>>> +++ b/drivers/misc/Kconfig
>>>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>>>> source "drivers/misc/genwqe/Kconfig"
>>>> source "drivers/misc/echo/Kconfig"
>>>> source "drivers/misc/cxl/Kconfig"
>>>> +
>>>> +config W2SG0004
>>>> +	tristate "W2SG00x4 on/off control"
>>>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>>>> +	help
>>>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>>>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>>>> +	  is opened/closed.
>>>> +	  It also provides a rfkill gps name to control the LNA power.
>>>> +
>>>> endmenu
>>>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>>>> index ad0e64fdba34..abcb667e0ff0 100644
>>>> --- a/drivers/misc/Makefile
>>>> +++ b/drivers/misc/Makefile
>>>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>>>> obj-y				+= mic/
>>>> obj-$(CONFIG_GENWQE)		+= genwqe/
>>>> obj-$(CONFIG_ECHO)		+= echo/
>>>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>>>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>>>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>>>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>>>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>>>> new file mode 100644
>>>> index 000000000000..12e14b5e0a99
>>>> --- /dev/null
>>>> +++ b/drivers/misc/w2sg0004.c
>>>> @@ -0,0 +1,545 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>> 
>>> Damn this looks ugly, oh well :/
>> 
>> I could remove it for [PATCH v5] ... :\
>> 
> 
> Nah, you should leave it, this comment was just me venting, Greg KH has
> spoken, so this is the correct way now.

Yes, sigh...

> 
>>> 
>>>> +/*
>>>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>>>> + *
>>> 
>>> 
>>> Think you still need copyright tag here somewhere.
>> 
>> At the bottom there is:
>> 
>>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>>> +MODULE_LICENSE("GPL v2");
>> 
>> Isn't that enough any more?
>> 
> 
> Not sure.. someone needs to hold the copyright to this file and it
> really should be close to the top.

Ok, I'll add some (C) line(s) matching the author(s).

> 
>>> 
>>>> + * This receiver has an ON/OFF pin which must be toggled to
>>>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>>>> + * will switch the device on if it is off, and off if it is on.
>>>> + *
>>>> + * To enable receiving on/off requests we register with the
>>>> + * UART power management notifications.
>>>> + *
>>>> + * It is not possible to directly detect the state of the device.
>>>> + * However when it is on it will send characters on a UART line
>>>> + * regularly.
>>>> + *
>>>> + * To detect that the power state is out of sync (e.g. if GPS
>>>> + * was enabled before a reboot), we register for UART data received
>>>> + * notifications.
>>>> + *
>>>> + * In addition we register as a rfkill client so that we can
>>>> + * control the LNA power.
>>>> + *
>>>> + */
>>>> +
>>>> +#include <linux/delay.h>
>>>> +#include <linux/err.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/irq.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_irq.h>
>>>> +#include <linux/of_gpio.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/regulator/consumer.h>
>>>> +#include <linux/rfkill.h>
>>>> +#include <linux/serdev.h>
>>>> +#include <linux/sched.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/tty.h>
>>>> +#include <linux/tty_flip.h>
>>>> +#include <linux/workqueue.h>
>>>> +
>>>> +/*
>>>> + * There seems to be restrictions on how quickly we can toggle the
>>>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>>>> + * If we do it too soon it doesn't work.
>>>> + * So we have a state machine which uses the common work queue to ensure
>>>> + * clean transitions.
>>>> + * When a change is requested we record that request and only act on it
>>>> + * once the previous change has completed.
>>>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>>>> + * one change per second.
>>>> + */
>>>> +
>>>> +enum w2sg_state {
>>>> +	W2SG_IDLE,	/* is not changing state */
>>>> +	W2SG_PULSE,	/* activate on/off impulse */
>>>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>>>> +};
>>>> +
>>>> +struct w2sg_data {
>>>> +	struct		rfkill *rf_kill;
>>>> +	struct		regulator *lna_regulator;
>>>> +	int		lna_blocked;	/* rfkill block gps active */
>>>> +	int		lna_is_off;	/* LNA is currently off */
>>>> +	int		is_on;		/* current state (0/1) */
>>>> +	unsigned long	last_toggle;
>>>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>>>> +	int		on_off_gpio;	/* the on-off gpio number */
>>>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>>>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>>>> +	struct		device *dev;	/* from tty_port_register_device() */
>>>> +	struct		tty_port port;
>>>> +	int		open_count;	/* how often we were opened */
>>>> +	enum		w2sg_state state;
>>>> +	int		requested;	/* requested state (0/1) */
>>>> +	int		suspended;
>>>> +	struct delayed_work work;
>>>> +	int		discard_count;
>>>> +};
>>>> +
>>> 
>>> Kernel doc style.
>> 
>> Is this really needed here?
>> 
>> For pure driver internal structs (they are not kernel infrastructure API) I usually
>> consult the source code of a driver and never well formatted kernel doc. Hence I think
>> readability by programmers looking into the source file is more important than serving
>> kernel doc tools.
>> 
>> Yes, there are examples like:
>> 
>> https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113
>> 
>> But I find them more confusing than helpful because I have to jump between code lines
>> to match the comment with the data type.
>> 
> 
> I have no strong preference here as long as you think this is the most
> readable way.

Ok, then let's leave it as it is.

> 
>>> 
>>>> +static struct w2sg_data *w2sg_by_minor[1];
>>>> +
>>> 
>>> If you can only have one right now then just drop the array.
>> 
>> w2sg_get_by_minor() is prepared to access more than one record.
>> And there are plans to have more than one (but I don't know exactly
>> how to provide it).
>> 
> 
> Just add it back when you get that functionality.

I have added a BUG_ON if anyone tries to access an index different from 0.
Why BUG_ON? Because there is no way to cure this situation safely.

> 
>> Making it a non-array seems to be an unnecessary hurdle for such
>> improvements. And regarding memory footprint, a single-element
>> array is equivalent to a non-array.
>> 
> 
> This comment was not about memory footprint, it's about sane code practices.

Well, I would consider using a 1-element array when the plan is to use an n-element
array as more future-proof...

But I have changed it for v5.

> 
>>> 
>>>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>>>> +{
>>>> +	int ret = 0;
>>>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>>>> +
>>>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>>>> +
>>>> +	if (off != data->lna_is_off) {
>>>> +		data->lna_is_off = off;
>>>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>>>> +			if (off)
>>>> +				regulator_disable(data->lna_regulator);
>>>> +			else
>>>> +				ret = regulator_enable(data->lna_regulator);
>>>> +		}
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void w2sg_set_power(void *pdata, int val)
>>>> +{
>>>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>>>> +
>>>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>>>> +
>>>> +	if (val && !data->requested) {
>>>> +		data->requested = true;
>>>> +	} else if (!val && data->requested) {
>>>> +		data->backoff = HZ;
>>>> +		data->requested = false;
>>>> +	} else
>>>> +		return;
>>>> +
>>>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>>>> +
>>>> +	if (!data->suspended)
>>>> +		schedule_delayed_work(&data->work, 0);
>>>> +}
>>>> +
>>>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>>>> +
>>>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>>>> +				const unsigned char *rxdata,
>>>> +				size_t count)
>>>> +{
>>>> +	struct w2sg_data *data =
>>>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>>>> +
>>>> +	if (!data->requested && !data->is_on) {
>>>> +		/*
>>>> +		 * we have received characters while the w2sg
>>>> +		 * should have been be turned off
>>>> +		 */
>>>> +		data->discard_count += count;
>>>> +		if ((data->state == W2SG_IDLE) &&
>>>> +		    time_after(jiffies,
>>>> +		    data->last_toggle + data->backoff)) {
>>>> +			/* Should be off by now, time to toggle again */
>>>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>>>> +				data->discard_count);
>>>> +
>>>> +			data->discard_count = 0;
>>>> +
>>>> +			data->is_on = true;
>>>> +			data->backoff *= 2;
>>>> +			if (!data->suspended)
>>>> +				schedule_delayed_work(&data->work, 0);
>>>> +		}
>>>> +	} else if (data->open_count > 0) {
>>>> +		int n;
>>>> +
>>>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>>>> +			(unsigned long) count);
>>>> +
>>>> +		/* pass to user-space */
>>>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>>>> +		if (n != count)
>>>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>>>> +				(unsigned long) (count - n));
>>>> +		tty_flip_buffer_push(&data->port);
>>>> +		return n;
>>>> +	}
>>>> +
>>>> +	/* assume we have processed everything */
>>>> +	return count;
>>>> +}
>>>> +
>>>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>>>> +
>>> 
>>> drop extra line, same everywhere
>> 
>> Ok! Was just this and one more location.
>> 
>>> 
>>>> +static void toggle_work(struct work_struct *work)
>>>> +{
>>>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>>>> +					      work.work);
>>>> +
>>>> +	switch (data->state) {
>>>> +	case W2SG_IDLE:
>>>> +		if (data->requested == data->is_on)
>>>> +			return;
>>>> +
>>>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>>> +		data->state = W2SG_PULSE;
>>>> +
>>>> +		pr_debug("w2sg: power gpio ON\n");
>>>> +
>>>> +		schedule_delayed_work(&data->work,
>>>> +				      msecs_to_jiffies(10));
>>>> +		break;
>>>> +
>>>> +	case W2SG_PULSE:
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>>> +		data->last_toggle = jiffies;
>>>> +		data->state = W2SG_NOPULSE;
>>>> +		data->is_on = !data->is_on;
>>>> +
>>>> +		pr_debug("w2sg: power gpio OFF\n");
>>>> +
>>>> +		schedule_delayed_work(&data->work,
>>>> +				      msecs_to_jiffies(10));
>>>> +		break;
>>>> +
>>>> +	case W2SG_NOPULSE:
>>>> +		data->state = W2SG_IDLE;
>>>> +
>>>> +		pr_debug("w2sg: idle\n");
>>>> +
>>>> +		break;
>>>> +
>>>> +	}
>>>> +}
>>>> +
>>>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>>>> +{
>>>> +	struct w2sg_data *data = pdata;
>>>> +
>>>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>>>> +
>>>> +	data->lna_blocked = blocked;
>>>> +
>>>> +	return w2sg_set_lna_power(data);
>>>> +}
>>>> +
>>>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>>>> +	.set_block = w2sg_rfkill_set_block,
>>>> +};
>>>> +
>>>> +static struct serdev_device_ops serdev_ops = {
>>>> +	.receive_buf = w2sg_uart_receive_buf,
>>>> +};
>>>> +
>>>> +/*
>>>> + * we are a man-in the middle between the user-space visible tty port
>>>> + * and the serdev tty where the chip is connected.
>>>> + * This allows us to recognise when the device should be powered on
>>>> + * or off and handle the "false" state that data arrives while no
>>>> + * users-space tty client exists.
>>>> + */
>>>> +
>>>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>>>> +{
>>>> +	return w2sg_by_minor[minor];
>>>> +}
>>>> +
>>>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>>>> +{
>>>> +	struct w2sg_data *data;
>>>> +	int retval;
>>>> +
>>>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>>>> +
>>>> +	data = w2sg_get_by_minor(tty->index);
>>>> +
>>>> +	if (!data)
>>>> +		return -ENODEV;
>>>> +
>>>> +	retval = tty_standard_install(driver, tty);
>>>> +	if (retval)
>>>> +		goto error_init_termios;
>>>> +
>>>> +	tty->driver_data = data;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +error_init_termios:
>>>> +	tty_port_put(&data->port);
>>>> +	return retval;
>>>> +}
>>>> +
>>>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>>>> +{
>>>> +	struct w2sg_data *data = tty->driver_data;
>>>> +
>>>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>>>> +
>>>> +	w2sg_set_power(data, ++data->open_count > 0);
>>>> +
>>>> +	return tty_port_open(&data->port, tty, file);
>>>> +}
>>>> +
>>>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>>>> +{
>>>> +	struct w2sg_data *data = tty->driver_data;
>>>> +
>>>> +	pr_debug("%s()\n", __func__);
>>>> +
>>>> +	w2sg_set_power(data, --data->open_count > 0);
>>>> +
>>>> +	tty_port_close(&data->port, tty, file);
>>>> +}
>>>> +
>>>> +static int w2sg_tty_write(struct tty_struct *tty,
>>>> +		const unsigned char *buffer, int count)
>>>> +{
>>>> +	struct w2sg_data *data = tty->driver_data;
>>>> +
>>>> +	/* simply pass down to UART */
>>>> +	return serdev_device_write_buf(data->uart, buffer, count);
>>>> +}
>>>> +
>>>> +static const struct tty_operations w2sg_serial_ops = {
>>>> +	.install = w2sg_tty_install,
>>>> +	.open = w2sg_tty_open,
>>>> +	.close = w2sg_tty_close,
>>>> +	.write = w2sg_tty_write,
>>>> +};
>>>> +
>>>> +static const struct tty_port_operations w2sg_port_ops = {
>>>> +};
>>> 
>>> drop the brackets? or use NULL below, not sure if this needs memory.
>> 
>> This allocates a struct tty_port_operations initialized with NULL for
>> all function pointers.
>> 
> 
> Then just drop the brackets, or allocate it with kzalloc.
> 
>> I am not sure if this is the same as providing no w2sg_port_ops at all.
>> 
>> Rather I think the serial core is only port == NULL safe but not
>> port->ops == NULL e.g.:
>> 
>> http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422
>> 
>> A test confirms (see comment below):
>> 
>>> 
>>>> +
>>>> +static int w2sg_probe(struct serdev_device *serdev)
>>>> +{
>>>> +	struct w2sg_data *data;
>>>> +	struct rfkill *rf_kill;
>>>> +	int err;
>>>> +	int minor;
>>>> +	enum of_gpio_flags flags;
>>>> +
>>>> +	pr_debug("%s()\n", __func__);
>>>> +
>>>> +	minor = 0;
>>>> +	if (w2sg_by_minor[minor]) {
>>>> +		pr_err("w2sg minor is already in use!\n");
>>>> +		return -ENODEV;
>>>> +	}
>>>> +
>>>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>>>> +	if (data == NULL)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	w2sg_by_minor[minor] = data;
>> 
>> While looking through this, I found a potential issue if allocating the
>> gpio fails with -EPROBE_DEFER.
>> 
>> Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
>> find it allocated on next deferred probe attempt.
>> 
>> So I'll move that to a position further down.
>> 
>>>> +
>>>> +	serdev_device_set_drvdata(serdev, data);
>>>> +
>>>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>>>> +						     "enable-gpios", 0,
>>>> +						     &flags);
>>>> +	/* defer until we have all gpios */
>>>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>>>> +		return -EPROBE_DEFER;
>>>> +
>>>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>>>> +							"lna");
>>>> +	if (IS_ERR(data->lna_regulator)) {
>>>> +		/* defer until we can get the regulator */
>>>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>>>> +			return -EPROBE_DEFER;
>>>> +
>>>> +		data->lna_regulator = NULL;
>>>> +	}
>>>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>>>> +
>>>> +	data->lna_blocked = true;
>>>> +	data->lna_is_off = true;
>>>> +
>>>> +	data->is_on = false;
>>>> +	data->requested = false;
>>>> +	data->state = W2SG_IDLE;
>>>> +	data->last_toggle = jiffies;
>>>> +	data->backoff = HZ;
>>>> +
>>>> +	data->uart = serdev;
>>>> +
>>>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>>>> +
>>>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>>>> +				"w2sg0004-on-off");
>>>> +	if (err < 0)
>>>> +		goto out;
>>>> +
>>>> +	gpio_direction_output(data->on_off_gpio, false);
>>>> +
>>>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>>>> +	serdev_device_open(data->uart);
>>>> +
>>>> +	serdev_device_set_baudrate(data->uart, 9600);
>>>> +	serdev_device_set_flow_control(data->uart, false);
>>>> +
>>>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>>>> +				&w2sg0004_rfkill_ops, data);
>>>> +	if (rf_kill == NULL) {
>>>> +		err = -ENOMEM;
>>>> +		goto err_rfkill;
>>>> +	}
>>>> +
>>>> +	err = rfkill_register(rf_kill);
>>>> +	if (err) {
>>>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>>>> +		goto err_rfkill;
>>>> +	}
>>>> +
>>>> +	data->rf_kill = rf_kill;
>>>> +
>>>> +	/* allocate the tty driver */
>>>> +	data->tty_drv = alloc_tty_driver(1);
>>>> +	if (!data->tty_drv)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	/* initialize the tty driver */
>>>> +	data->tty_drv->owner = THIS_MODULE;
>>>> +	data->tty_drv->driver_name = "w2sg0004";
>>>> +	data->tty_drv->name = "ttyGPS";
>>>> +	data->tty_drv->major = 0;
>>>> +	data->tty_drv->minor_start = 0;
>>>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>>>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>>>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>>>> +	data->tty_drv->init_termios = tty_std_termios;
>>>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>>>> +					      HUPCL | CLOCAL;
>>>> +	/*
>>>> +	 * optional:
>>>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>>>> +					115200, 115200);
>>>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>>>> +	 */
>>>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>>>> +
>>>> +	/* register the tty driver */
>>>> +	err = tty_register_driver(data->tty_drv);
>>>> +	if (err) {
>>>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>>>> +			__func__, err);
>>>> +		put_tty_driver(data->tty_drv);
>>>> +		goto err_rfkill;
>>>> +	}
>>>> +
>>>> +	tty_port_init(&data->port);
>>>> +	data->port.ops = &w2sg_port_ops;
>> 
>> A test setting this to NULL leads to a kernel oops in:
>> 
>> [ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)
>> 
>> So we must define this completely empty struct w2sg_port_ops
>> and pass a reference.
>> 
>>>> +
>>>> +	pr_debug("w2sg call tty_port_register_device\n");
>>>> +
>>>> +	data->dev = tty_port_register_device(&data->port,
>>>> +			data->tty_drv, minor, &serdev->dev);
>>>> +
>>>> +	pr_debug("w2sg probed\n");
>>>> +
>>>> +	/* keep off until user space requests the device */
>>>> +	w2sg_set_power(data, false);
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_rfkill:
>>>> +	rfkill_destroy(rf_kill);
>>>> +	serdev_device_close(data->uart);
>>>> +out:
>>>> +	return err;
>>>> +}
>>>> +
>>>> +static void w2sg_remove(struct serdev_device *serdev)
>>>> +{
>>>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>>>> +	int minor;
>>>> +
>>>> +	cancel_delayed_work_sync(&data->work);
>>>> +
>>>> +	/* what is the right sequence to avoid problems? */
>>>> +	serdev_device_close(data->uart);
>>>> +
>>>> +	minor = 0;
>>>> +	tty_unregister_device(data->tty_drv, minor);
>>>> +
>>>> +	tty_unregister_driver(data->tty_drv);
>>>> +}
>>>> +
>>>> +static int w2sg_suspend(struct device *dev)
>>> 
>>> __maybe_unused, or if PM is disabled you will get a warning.
>> 
>> Ok.
>> 
>>> 
>>>> +{
>>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>>> +
>>>> +	data->suspended = true;
>>>> +
>>>> +	cancel_delayed_work_sync(&data->work);
>>>> +
>>>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>>>> +
>>>> +	if (data->state == W2SG_PULSE) {
>>>> +		msleep(10);
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>>> +		data->last_toggle = jiffies;
>>>> +		data->is_on = !data->is_on;
>>>> +		data->state = W2SG_NOPULSE;
>>>> +	}
>>>> +
>>>> +	if (data->state == W2SG_NOPULSE) {
>>>> +		msleep(10);
>>>> +		data->state = W2SG_IDLE;
>>>> +	}
>>>> +
>>>> +	if (data->is_on) {
>>>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>>>> +			data->is_on, data->lna_is_off);
>>>> +
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>>> +		msleep(10);
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>>> +		data->is_on = 0;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int w2sg_resume(struct device *dev)
>>>> +{
>>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>>> +
>>>> +	data->suspended = false;
>>>> +
>>>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>>>> +		data->is_on, data->lna_is_off);
>>>> +
>>>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id w2sg0004_of_match[] = {
>>>> +	{ .compatible = "wi2wi,w2sg0004" },
>>>> +	{ .compatible = "wi2wi,w2sg0084" },
>>>> +	{},
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>>>> +
>>>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>>>> +
>>>> +static struct serdev_device_driver w2sg_driver = {
>>>> +	.probe		= w2sg_probe,
>>>> +	.remove		= w2sg_remove,
>>>> +	.driver = {
>>>> +		.name	= "w2sg0004",
>>>> +		.owner	= THIS_MODULE,
>>>> +		.pm	= &w2sg_pm_ops,
>>>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>>>> +	},
>>>> +};
>>>> +
>>>> +module_serdev_device_driver(w2sg_driver);
>>>> +
>>>> +MODULE_ALIAS("w2sg0004");
>>> 
>>> Is this needed?
>> 
>> Seems to be redundant.
>> After removal the driver is still loaded automatically after boot:
>> 
>> root@letux:~# modprobe -c | fgrep w2sg
>> alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
>> alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
>> alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
>> alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
>> root@letux:~# lsmod | fgrep w2sg
>> w2sg0004               16384  2 
>> root@letux:~# 
>> 
>>> 
>>>> +
>>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>> 
>>> Who really wrote this?
>> 
>> Good question.
>> 
>> The problem is that with cleaning up the code and rewriting history over such
>> a long time, it is no more visible.
>> 
>> But I have searched through our older branches:
>> 
>> Neil Brown had developed the first version for v3.7 in 2013: 
>> http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69
>> 
>> This is where the MODULE_AUTHOR line comes from.
>> 
>> Later, Marek Belisko worked on the UART driver, pinmux and interrupts.
>> 
>> And I have
>> * added lna-regulator and rfkill ca. v3.12
>> * added device tree support ca. v3.15
>> * moved to drivers/misc
>> * ported the code to serdev API ca. v4.11
>> * submitted to LKML and worked in comments by reviewers ca. 4.15
>> 
>> So this is the first version that is close to be acceptable.
>> 
>> Neil had also submitted different versions to LKML but I am not
>> sure if he still is active anywhere.
>> 
>> I have also checked a diff between the v3.7 version and the
>> current one and there are ca. 30-40% of lines original code by
>> Neil.
>> 
>> So I'd say:
>> 
>> * Neil is the original author and a significant amount of untouched code lines and comments are still there
>> * he designed the overall driver architecture
>> * Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
>> * Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
>> * I did add some important features but not the core code and driver architecture
>> * I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"
>> 
>> How should we best reflect this in the AUTHOR macro?
>> 
> 
> Easy:
> 
> MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
> MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
> 
> you can have more than one :)

Ah, that is much easier to solve than I had thought...

> 
>>> 
>>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>>> +MODULE_LICENSE("GPL v2");
>>>> 

[PATCH v5] now coming.

BR and thanks,
Nikolaus

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

* [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver
@ 2017-12-01  7:49           ` H. Nikolaus Schaller
  0 siblings, 0 replies; 33+ messages in thread
From: H. Nikolaus Schaller @ 2017-12-01  7:49 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

> Am 29.11.2017 um 15:47 schrieb Andrew F. Davis <afd@ti.com>:
> 
> On 11/28/2017 03:42 PM, H. Nikolaus Schaller wrote:
>> Hi Andrew,
>> now as 4.15-rc1 is out I find time to continue this work and prepare [PATCH v5].
>> 
>>> Am 23.11.2017 um 17:06 schrieb Andrew F. Davis <afd@ti.com>:
>>> 
>>> On 11/15/2017 03:37 PM, H. Nikolaus Schaller wrote:
>>>> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
>>>> 
>>>> Use serdev API hooks to monitor and forward the UART traffic to /dev/ttyGPSn
>>>> and turn on/off the module. It also detects if the module is turned on (sends data)
>>>> but should be off, e.g. if it was already turned on during boot or power-on-reset.
>>>> 
>>>> Additionally, rfkill block/unblock can be used to control an external LNA
>>>> (and power down the module if not needed).
>>>> 
>>>> The driver concept is based on code developed by NeilBrown <neilb@suse.de>
>>>> but simplified and adapted to use the new serdev API introduced in 4.11.
>>>> 
>>>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>>>> ---
>>>> drivers/misc/Kconfig    |  10 +
>>>> drivers/misc/Makefile   |   1 +
>>>> drivers/misc/w2sg0004.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++
>> 
>> I wonder if this shouldn't better go to
>> 
>>  drivers/gps
>> 
>> But this directory does not yet exist and has no overall maintainer.
>> So it could be left in drivers/misc and moved as soon as drivers/gps
>> is created elsewhere.
>> 
> 
> Making that directory would imply the existence of a GPS driver
> framework, until one comes about (or if you would like to create one), I
> think misc is the most appropriate spot for now.

Understood and fine with it.
I just wanted to have asked and clarified.

> 
>>>> 3 files changed, 556 insertions(+)
>>>> create mode 100644 drivers/misc/w2sg0004.c
>>>> 
>>>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>>>> index 8136dc7e863d..09d171d68408 100644
>>>> --- a/drivers/misc/Kconfig
>>>> +++ b/drivers/misc/Kconfig
>>>> @@ -518,4 +518,14 @@ source "drivers/misc/mic/Kconfig"
>>>> source "drivers/misc/genwqe/Kconfig"
>>>> source "drivers/misc/echo/Kconfig"
>>>> source "drivers/misc/cxl/Kconfig"
>>>> +
>>>> +config W2SG0004
>>>> +	tristate "W2SG00x4 on/off control"
>>>> +	depends on GPIOLIB && SERIAL_DEV_BUS
>>>> +	help
>>>> +          Enable on/off control of W2SG00x4 GPS moduled connected
>>>> +	  to some SoC UART to allow powering up/down if the /dev/ttyGPSn
>>>> +	  is opened/closed.
>>>> +	  It also provides a rfkill gps name to control the LNA power.
>>>> +
>>>> endmenu
>>>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>>>> index ad0e64fdba34..abcb667e0ff0 100644
>>>> --- a/drivers/misc/Makefile
>>>> +++ b/drivers/misc/Makefile
>>>> @@ -51,6 +51,7 @@ obj-$(CONFIG_SRAM_EXEC)		+= sram-exec.o
>>>> obj-y				+= mic/
>>>> obj-$(CONFIG_GENWQE)		+= genwqe/
>>>> obj-$(CONFIG_ECHO)		+= echo/
>>>> +obj-$(CONFIG_W2SG0004)		+= w2sg0004.o
>>>> obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
>>>> obj-$(CONFIG_CXL_BASE)		+= cxl/
>>>> obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
>>>> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
>>>> new file mode 100644
>>>> index 000000000000..12e14b5e0a99
>>>> --- /dev/null
>>>> +++ b/drivers/misc/w2sg0004.c
>>>> @@ -0,0 +1,545 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>> 
>>> Damn this looks ugly, oh well :/
>> 
>> I could remove it for [PATCH v5] ... :\
>> 
> 
> Nah, you should leave it, this comment was just me venting, Greg KH has
> spoken, so this is the correct way now.

Yes, sigh...

> 
>>> 
>>>> +/*
>>>> + * Driver for power controlling the w2sg0004/w2sg0084 GPS receiver.
>>>> + *
>>> 
>>> 
>>> Think you still need copyright tag here somewhere.
>> 
>> At the bottom there is:
>> 
>>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>>> +MODULE_LICENSE("GPL v2");
>> 
>> Isn't that enough any more?
>> 
> 
> Not sure.. someone needs to hold the copyright to this file and it
> really should be close to the top.

Ok, I'll add some (C) line(s) matching the author(s).

> 
>>> 
>>>> + * This receiver has an ON/OFF pin which must be toggled to
>>>> + * turn the device 'on' of 'off'.  A high->low->high toggle
>>>> + * will switch the device on if it is off, and off if it is on.
>>>> + *
>>>> + * To enable receiving on/off requests we register with the
>>>> + * UART power management notifications.
>>>> + *
>>>> + * It is not possible to directly detect the state of the device.
>>>> + * However when it is on it will send characters on a UART line
>>>> + * regularly.
>>>> + *
>>>> + * To detect that the power state is out of sync (e.g. if GPS
>>>> + * was enabled before a reboot), we register for UART data received
>>>> + * notifications.
>>>> + *
>>>> + * In addition we register as a rfkill client so that we can
>>>> + * control the LNA power.
>>>> + *
>>>> + */
>>>> +
>>>> +#include <linux/delay.h>
>>>> +#include <linux/err.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/irq.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_irq.h>
>>>> +#include <linux/of_gpio.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/regulator/consumer.h>
>>>> +#include <linux/rfkill.h>
>>>> +#include <linux/serdev.h>
>>>> +#include <linux/sched.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/tty.h>
>>>> +#include <linux/tty_flip.h>
>>>> +#include <linux/workqueue.h>
>>>> +
>>>> +/*
>>>> + * There seems to be restrictions on how quickly we can toggle the
>>>> + * on/off line.  data sheets says "two rtc ticks", whatever that means.
>>>> + * If we do it too soon it doesn't work.
>>>> + * So we have a state machine which uses the common work queue to ensure
>>>> + * clean transitions.
>>>> + * When a change is requested we record that request and only act on it
>>>> + * once the previous change has completed.
>>>> + * A change involves a 10ms low pulse, and a 990ms raised level, so only
>>>> + * one change per second.
>>>> + */
>>>> +
>>>> +enum w2sg_state {
>>>> +	W2SG_IDLE,	/* is not changing state */
>>>> +	W2SG_PULSE,	/* activate on/off impulse */
>>>> +	W2SG_NOPULSE	/* deactivate on/off impulse */
>>>> +};
>>>> +
>>>> +struct w2sg_data {
>>>> +	struct		rfkill *rf_kill;
>>>> +	struct		regulator *lna_regulator;
>>>> +	int		lna_blocked;	/* rfkill block gps active */
>>>> +	int		lna_is_off;	/* LNA is currently off */
>>>> +	int		is_on;		/* current state (0/1) */
>>>> +	unsigned long	last_toggle;
>>>> +	unsigned long	backoff;	/* time to wait since last_toggle */
>>>> +	int		on_off_gpio;	/* the on-off gpio number */
>>>> +	struct		serdev_device *uart;	/* uart connected to the chip */
>>>> +	struct		tty_driver *tty_drv;	/* this is the user space tty */
>>>> +	struct		device *dev;	/* from tty_port_register_device() */
>>>> +	struct		tty_port port;
>>>> +	int		open_count;	/* how often we were opened */
>>>> +	enum		w2sg_state state;
>>>> +	int		requested;	/* requested state (0/1) */
>>>> +	int		suspended;
>>>> +	struct delayed_work work;
>>>> +	int		discard_count;
>>>> +};
>>>> +
>>> 
>>> Kernel doc style.
>> 
>> Is this really needed here?
>> 
>> For pure driver internal structs (they are not kernel infrastructure API) I usually
>> consult the source code of a driver and never well formatted kernel doc. Hence I think
>> readability by programmers looking into the source file is more important than serving
>> kernel doc tools.
>> 
>> Yes, there are examples like:
>> 
>> https://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/iio/accel/mma8452.c#L113
>> 
>> But I find them more confusing than helpful because I have to jump between code lines
>> to match the comment with the data type.
>> 
> 
> I have no strong preference here as long as you think this is the most
> readable way.

Ok, then let's leave it as it is.

> 
>>> 
>>>> +static struct w2sg_data *w2sg_by_minor[1];
>>>> +
>>> 
>>> If you can only have one right now then just drop the array.
>> 
>> w2sg_get_by_minor() is prepared to access more than one record.
>> And there are plans to have more than one (but I don't know exactly
>> how to provide it).
>> 
> 
> Just add it back when you get that functionality.

I have added a BUG_ON if anyone tries to access an index different from 0.
Why BUG_ON? Because there is no way to cure this situation safely.

> 
>> Making it a non-array seems to be an unnecessary hurdle for such
>> improvements. And regarding memory footprint, a single-element
>> array is equivalent to a non-array.
>> 
> 
> This comment was not about memory footprint, it's about sane code practices.

Well, I would consider using a 1-element array when the plan is to use an n-element
array as more future-proof...

But I have changed it for v5.

> 
>>> 
>>>> +static int w2sg_set_lna_power(struct w2sg_data *data)
>>>> +{
>>>> +	int ret = 0;
>>>> +	int off = data->suspended || !data->requested || data->lna_blocked;
>>>> +
>>>> +	pr_debug("%s: %s\n", __func__, off ? "off" : "on");
>>>> +
>>>> +	if (off != data->lna_is_off) {
>>>> +		data->lna_is_off = off;
>>>> +		if (!IS_ERR_OR_NULL(data->lna_regulator)) {
>>>> +			if (off)
>>>> +				regulator_disable(data->lna_regulator);
>>>> +			else
>>>> +				ret = regulator_enable(data->lna_regulator);
>>>> +		}
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void w2sg_set_power(void *pdata, int val)
>>>> +{
>>>> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
>>>> +
>>>> +	pr_debug("%s to state=%d (requested=%d)\n", __func__, val, data->requested);
>>>> +
>>>> +	if (val && !data->requested) {
>>>> +		data->requested = true;
>>>> +	} else if (!val && data->requested) {
>>>> +		data->backoff = HZ;
>>>> +		data->requested = false;
>>>> +	} else
>>>> +		return;
>>>> +
>>>> +	pr_debug("w2sg00x4 scheduled for %d\n", data->requested);
>>>> +
>>>> +	if (!data->suspended)
>>>> +		schedule_delayed_work(&data->work, 0);
>>>> +}
>>>> +
>>>> +/* called each time data is received by the UART (i.e. sent by the w2sg0004) */
>>>> +
>>>> +static int w2sg_uart_receive_buf(struct serdev_device *serdev,
>>>> +				const unsigned char *rxdata,
>>>> +				size_t count)
>>>> +{
>>>> +	struct w2sg_data *data =
>>>> +		(struct w2sg_data *) serdev_device_get_drvdata(serdev);
>>>> +
>>>> +	if (!data->requested && !data->is_on) {
>>>> +		/*
>>>> +		 * we have received characters while the w2sg
>>>> +		 * should have been be turned off
>>>> +		 */
>>>> +		data->discard_count += count;
>>>> +		if ((data->state == W2SG_IDLE) &&
>>>> +		    time_after(jiffies,
>>>> +		    data->last_toggle + data->backoff)) {
>>>> +			/* Should be off by now, time to toggle again */
>>>> +			pr_debug("w2sg00x4 has sent %d characters data although it should be off!\n",
>>>> +				data->discard_count);
>>>> +
>>>> +			data->discard_count = 0;
>>>> +
>>>> +			data->is_on = true;
>>>> +			data->backoff *= 2;
>>>> +			if (!data->suspended)
>>>> +				schedule_delayed_work(&data->work, 0);
>>>> +		}
>>>> +	} else if (data->open_count > 0) {
>>>> +		int n;
>>>> +
>>>> +		pr_debug("w2sg00x4: push %lu chars to tty port\n",
>>>> +			(unsigned long) count);
>>>> +
>>>> +		/* pass to user-space */
>>>> +		n = tty_insert_flip_string(&data->port, rxdata, count);
>>>> +		if (n != count)
>>>> +			pr_err("w2sg00x4: did loose %lu characters\n",
>>>> +				(unsigned long) (count - n));
>>>> +		tty_flip_buffer_push(&data->port);
>>>> +		return n;
>>>> +	}
>>>> +
>>>> +	/* assume we have processed everything */
>>>> +	return count;
>>>> +}
>>>> +
>>>> +/* try to toggle the power state by sending a pulse to the on-off GPIO */
>>>> +
>>> 
>>> drop extra line, same everywhere
>> 
>> Ok! Was just this and one more location.
>> 
>>> 
>>>> +static void toggle_work(struct work_struct *work)
>>>> +{
>>>> +	struct w2sg_data *data = container_of(work, struct w2sg_data,
>>>> +					      work.work);
>>>> +
>>>> +	switch (data->state) {
>>>> +	case W2SG_IDLE:
>>>> +		if (data->requested == data->is_on)
>>>> +			return;
>>>> +
>>>> +		w2sg_set_lna_power(data);	/* update LNA power state */
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>>> +		data->state = W2SG_PULSE;
>>>> +
>>>> +		pr_debug("w2sg: power gpio ON\n");
>>>> +
>>>> +		schedule_delayed_work(&data->work,
>>>> +				      msecs_to_jiffies(10));
>>>> +		break;
>>>> +
>>>> +	case W2SG_PULSE:
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>>> +		data->last_toggle = jiffies;
>>>> +		data->state = W2SG_NOPULSE;
>>>> +		data->is_on = !data->is_on;
>>>> +
>>>> +		pr_debug("w2sg: power gpio OFF\n");
>>>> +
>>>> +		schedule_delayed_work(&data->work,
>>>> +				      msecs_to_jiffies(10));
>>>> +		break;
>>>> +
>>>> +	case W2SG_NOPULSE:
>>>> +		data->state = W2SG_IDLE;
>>>> +
>>>> +		pr_debug("w2sg: idle\n");
>>>> +
>>>> +		break;
>>>> +
>>>> +	}
>>>> +}
>>>> +
>>>> +static int w2sg_rfkill_set_block(void *pdata, bool blocked)
>>>> +{
>>>> +	struct w2sg_data *data = pdata;
>>>> +
>>>> +	pr_debug("%s: blocked: %d\n", __func__, blocked);
>>>> +
>>>> +	data->lna_blocked = blocked;
>>>> +
>>>> +	return w2sg_set_lna_power(data);
>>>> +}
>>>> +
>>>> +static struct rfkill_ops w2sg0004_rfkill_ops = {
>>>> +	.set_block = w2sg_rfkill_set_block,
>>>> +};
>>>> +
>>>> +static struct serdev_device_ops serdev_ops = {
>>>> +	.receive_buf = w2sg_uart_receive_buf,
>>>> +};
>>>> +
>>>> +/*
>>>> + * we are a man-in the middle between the user-space visible tty port
>>>> + * and the serdev tty where the chip is connected.
>>>> + * This allows us to recognise when the device should be powered on
>>>> + * or off and handle the "false" state that data arrives while no
>>>> + * users-space tty client exists.
>>>> + */
>>>> +
>>>> +static struct w2sg_data *w2sg_get_by_minor(unsigned int minor)
>>>> +{
>>>> +	return w2sg_by_minor[minor];
>>>> +}
>>>> +
>>>> +static int w2sg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
>>>> +{
>>>> +	struct w2sg_data *data;
>>>> +	int retval;
>>>> +
>>>> +	pr_debug("%s() tty = %p\n", __func__, tty);
>>>> +
>>>> +	data = w2sg_get_by_minor(tty->index);
>>>> +
>>>> +	if (!data)
>>>> +		return -ENODEV;
>>>> +
>>>> +	retval = tty_standard_install(driver, tty);
>>>> +	if (retval)
>>>> +		goto error_init_termios;
>>>> +
>>>> +	tty->driver_data = data;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +error_init_termios:
>>>> +	tty_port_put(&data->port);
>>>> +	return retval;
>>>> +}
>>>> +
>>>> +static int w2sg_tty_open(struct tty_struct *tty, struct file *file)
>>>> +{
>>>> +	struct w2sg_data *data = tty->driver_data;
>>>> +
>>>> +	pr_debug("%s() data = %p open_count = ++%d\n", __func__, data, data->open_count);
>>>> +
>>>> +	w2sg_set_power(data, ++data->open_count > 0);
>>>> +
>>>> +	return tty_port_open(&data->port, tty, file);
>>>> +}
>>>> +
>>>> +static void w2sg_tty_close(struct tty_struct *tty, struct file *file)
>>>> +{
>>>> +	struct w2sg_data *data = tty->driver_data;
>>>> +
>>>> +	pr_debug("%s()\n", __func__);
>>>> +
>>>> +	w2sg_set_power(data, --data->open_count > 0);
>>>> +
>>>> +	tty_port_close(&data->port, tty, file);
>>>> +}
>>>> +
>>>> +static int w2sg_tty_write(struct tty_struct *tty,
>>>> +		const unsigned char *buffer, int count)
>>>> +{
>>>> +	struct w2sg_data *data = tty->driver_data;
>>>> +
>>>> +	/* simply pass down to UART */
>>>> +	return serdev_device_write_buf(data->uart, buffer, count);
>>>> +}
>>>> +
>>>> +static const struct tty_operations w2sg_serial_ops = {
>>>> +	.install = w2sg_tty_install,
>>>> +	.open = w2sg_tty_open,
>>>> +	.close = w2sg_tty_close,
>>>> +	.write = w2sg_tty_write,
>>>> +};
>>>> +
>>>> +static const struct tty_port_operations w2sg_port_ops = {
>>>> +};
>>> 
>>> drop the brackets? or use NULL below, not sure if this needs memory.
>> 
>> This allocates a struct tty_port_operations initialized with NULL for
>> all function pointers.
>> 
> 
> Then just drop the brackets, or allocate it with kzalloc.
> 
>> I am not sure if this is the same as providing no w2sg_port_ops at all.
>> 
>> Rather I think the serial core is only port == NULL safe but not
>> port->ops == NULL e.g.:
>> 
>> http://elixir.free-electrons.com/linux/v4.15-rc1/source/drivers/tty/tty_port.c#L422
>> 
>> A test confirms (see comment below):
>> 
>>> 
>>>> +
>>>> +static int w2sg_probe(struct serdev_device *serdev)
>>>> +{
>>>> +	struct w2sg_data *data;
>>>> +	struct rfkill *rf_kill;
>>>> +	int err;
>>>> +	int minor;
>>>> +	enum of_gpio_flags flags;
>>>> +
>>>> +	pr_debug("%s()\n", __func__);
>>>> +
>>>> +	minor = 0;
>>>> +	if (w2sg_by_minor[minor]) {
>>>> +		pr_err("w2sg minor is already in use!\n");
>>>> +		return -ENODEV;
>>>> +	}
>>>> +
>>>> +	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
>>>> +	if (data == NULL)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	w2sg_by_minor[minor] = data;
>> 
>> While looking through this, I found a potential issue if allocating the
>> gpio fails with -EPROBE_DEFER.
>> 
>> Then, we have set w2sg_by_minor[minor] != NULL and the above test will already
>> find it allocated on next deferred probe attempt.
>> 
>> So I'll move that to a position further down.
>> 
>>>> +
>>>> +	serdev_device_set_drvdata(serdev, data);
>>>> +
>>>> +	data->on_off_gpio = of_get_named_gpio_flags(serdev->dev.of_node,
>>>> +						     "enable-gpios", 0,
>>>> +						     &flags);
>>>> +	/* defer until we have all gpios */
>>>> +	if (data->on_off_gpio == -EPROBE_DEFER)
>>>> +		return -EPROBE_DEFER;
>>>> +
>>>> +	data->lna_regulator = devm_regulator_get_optional(&serdev->dev,
>>>> +							"lna");
>>>> +	if (IS_ERR(data->lna_regulator)) {
>>>> +		/* defer until we can get the regulator */
>>>> +		if (PTR_ERR(data->lna_regulator) == -EPROBE_DEFER)
>>>> +			return -EPROBE_DEFER;
>>>> +
>>>> +		data->lna_regulator = NULL;
>>>> +	}
>>>> +	pr_debug("%s() lna_regulator = %p\n", __func__, data->lna_regulator);
>>>> +
>>>> +	data->lna_blocked = true;
>>>> +	data->lna_is_off = true;
>>>> +
>>>> +	data->is_on = false;
>>>> +	data->requested = false;
>>>> +	data->state = W2SG_IDLE;
>>>> +	data->last_toggle = jiffies;
>>>> +	data->backoff = HZ;
>>>> +
>>>> +	data->uart = serdev;
>>>> +
>>>> +	INIT_DELAYED_WORK(&data->work, toggle_work);
>>>> +
>>>> +	err = devm_gpio_request(&serdev->dev, data->on_off_gpio,
>>>> +				"w2sg0004-on-off");
>>>> +	if (err < 0)
>>>> +		goto out;
>>>> +
>>>> +	gpio_direction_output(data->on_off_gpio, false);
>>>> +
>>>> +	serdev_device_set_client_ops(data->uart, &serdev_ops);
>>>> +	serdev_device_open(data->uart);
>>>> +
>>>> +	serdev_device_set_baudrate(data->uart, 9600);
>>>> +	serdev_device_set_flow_control(data->uart, false);
>>>> +
>>>> +	rf_kill = rfkill_alloc("GPS", &serdev->dev, RFKILL_TYPE_GPS,
>>>> +				&w2sg0004_rfkill_ops, data);
>>>> +	if (rf_kill == NULL) {
>>>> +		err = -ENOMEM;
>>>> +		goto err_rfkill;
>>>> +	}
>>>> +
>>>> +	err = rfkill_register(rf_kill);
>>>> +	if (err) {
>>>> +		dev_err(&serdev->dev, "Cannot register rfkill device\n");
>>>> +		goto err_rfkill;
>>>> +	}
>>>> +
>>>> +	data->rf_kill = rf_kill;
>>>> +
>>>> +	/* allocate the tty driver */
>>>> +	data->tty_drv = alloc_tty_driver(1);
>>>> +	if (!data->tty_drv)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	/* initialize the tty driver */
>>>> +	data->tty_drv->owner = THIS_MODULE;
>>>> +	data->tty_drv->driver_name = "w2sg0004";
>>>> +	data->tty_drv->name = "ttyGPS";
>>>> +	data->tty_drv->major = 0;
>>>> +	data->tty_drv->minor_start = 0;
>>>> +	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>>>> +	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
>>>> +	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>>>> +	data->tty_drv->init_termios = tty_std_termios;
>>>> +	data->tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD |
>>>> +					      HUPCL | CLOCAL;
>>>> +	/*
>>>> +	 * optional:
>>>> +	 * tty_termios_encode_baud_rate(&data->tty_drv->init_termios,
>>>> +					115200, 115200);
>>>> +	 * w2sg_tty_termios(&data->tty_drv->init_termios);
>>>> +	 */
>>>> +	tty_set_operations(data->tty_drv, &w2sg_serial_ops);
>>>> +
>>>> +	/* register the tty driver */
>>>> +	err = tty_register_driver(data->tty_drv);
>>>> +	if (err) {
>>>> +		pr_err("%s - tty_register_driver failed(%d)\n",
>>>> +			__func__, err);
>>>> +		put_tty_driver(data->tty_drv);
>>>> +		goto err_rfkill;
>>>> +	}
>>>> +
>>>> +	tty_port_init(&data->port);
>>>> +	data->port.ops = &w2sg_port_ops;
>> 
>> A test setting this to NULL leads to a kernel oops in:
>> 
>> [ 3048.821258] [<c046c598>] (tty_port_open) from [<c0466328>] (tty_open+0x1e8/0x2d8)
>> 
>> So we must define this completely empty struct w2sg_port_ops
>> and pass a reference.
>> 
>>>> +
>>>> +	pr_debug("w2sg call tty_port_register_device\n");
>>>> +
>>>> +	data->dev = tty_port_register_device(&data->port,
>>>> +			data->tty_drv, minor, &serdev->dev);
>>>> +
>>>> +	pr_debug("w2sg probed\n");
>>>> +
>>>> +	/* keep off until user space requests the device */
>>>> +	w2sg_set_power(data, false);
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_rfkill:
>>>> +	rfkill_destroy(rf_kill);
>>>> +	serdev_device_close(data->uart);
>>>> +out:
>>>> +	return err;
>>>> +}
>>>> +
>>>> +static void w2sg_remove(struct serdev_device *serdev)
>>>> +{
>>>> +	struct w2sg_data *data = serdev_device_get_drvdata(serdev);
>>>> +	int minor;
>>>> +
>>>> +	cancel_delayed_work_sync(&data->work);
>>>> +
>>>> +	/* what is the right sequence to avoid problems? */
>>>> +	serdev_device_close(data->uart);
>>>> +
>>>> +	minor = 0;
>>>> +	tty_unregister_device(data->tty_drv, minor);
>>>> +
>>>> +	tty_unregister_driver(data->tty_drv);
>>>> +}
>>>> +
>>>> +static int w2sg_suspend(struct device *dev)
>>> 
>>> __maybe_unused, or if PM is disabled you will get a warning.
>> 
>> Ok.
>> 
>>> 
>>>> +{
>>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>>> +
>>>> +	data->suspended = true;
>>>> +
>>>> +	cancel_delayed_work_sync(&data->work);
>>>> +
>>>> +	w2sg_set_lna_power(data);	/* shuts down if needed */
>>>> +
>>>> +	if (data->state == W2SG_PULSE) {
>>>> +		msleep(10);
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>>> +		data->last_toggle = jiffies;
>>>> +		data->is_on = !data->is_on;
>>>> +		data->state = W2SG_NOPULSE;
>>>> +	}
>>>> +
>>>> +	if (data->state == W2SG_NOPULSE) {
>>>> +		msleep(10);
>>>> +		data->state = W2SG_IDLE;
>>>> +	}
>>>> +
>>>> +	if (data->is_on) {
>>>> +		pr_info("GPS off for suspend %d %d %d\n", data->requested,
>>>> +			data->is_on, data->lna_is_off);
>>>> +
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 0);
>>>> +		msleep(10);
>>>> +		gpio_set_value_cansleep(data->on_off_gpio, 1);
>>>> +		data->is_on = 0;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int w2sg_resume(struct device *dev)
>>>> +{
>>>> +	struct w2sg_data *data = dev_get_drvdata(dev);
>>>> +
>>>> +	data->suspended = false;
>>>> +
>>>> +	pr_info("GPS resuming %d %d %d\n", data->requested,
>>>> +		data->is_on, data->lna_is_off);
>>>> +
>>>> +	schedule_delayed_work(&data->work, 0);	/* enables LNA if needed */
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id w2sg0004_of_match[] = {
>>>> +	{ .compatible = "wi2wi,w2sg0004" },
>>>> +	{ .compatible = "wi2wi,w2sg0084" },
>>>> +	{},
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
>>>> +
>>>> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_suspend, w2sg_resume);
>>>> +
>>>> +static struct serdev_device_driver w2sg_driver = {
>>>> +	.probe		= w2sg_probe,
>>>> +	.remove		= w2sg_remove,
>>>> +	.driver = {
>>>> +		.name	= "w2sg0004",
>>>> +		.owner	= THIS_MODULE,
>>>> +		.pm	= &w2sg_pm_ops,
>>>> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
>>>> +	},
>>>> +};
>>>> +
>>>> +module_serdev_device_driver(w2sg_driver);
>>>> +
>>>> +MODULE_ALIAS("w2sg0004");
>>> 
>>> Is this needed?
>> 
>> Seems to be redundant.
>> After removal the driver is still loaded automatically after boot:
>> 
>> root at letux:~# modprobe -c | fgrep w2sg
>> alias of:N*T*Cwi2wi,w2sg0004 w2sg0004
>> alias of:N*T*Cwi2wi,w2sg0004C* w2sg0004
>> alias of:N*T*Cwi2wi,w2sg0084 w2sg0004
>> alias of:N*T*Cwi2wi,w2sg0084C* w2sg0004
>> root at letux:~# lsmod | fgrep w2sg
>> w2sg0004               16384  2 
>> root at letux:~# 
>> 
>>> 
>>>> +
>>>> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
>>> 
>>> Who really wrote this?
>> 
>> Good question.
>> 
>> The problem is that with cleaning up the code and rewriting history over such
>> a long time, it is no more visible.
>> 
>> But I have searched through our older branches:
>> 
>> Neil Brown had developed the first version for v3.7 in 2013: 
>> http://git.goldelico.com/?p=gta04-kernel.git;a=commit;h=5b6fad7c7f15db8bb8e2c98ae9a50da52bda8b69
>> 
>> This is where the MODULE_AUTHOR line comes from.
>> 
>> Later, Marek Belisko worked on the UART driver, pinmux and interrupts.
>> 
>> And I have
>> * added lna-regulator and rfkill ca. v3.12
>> * added device tree support ca. v3.15
>> * moved to drivers/misc
>> * ported the code to serdev API ca. v4.11
>> * submitted to LKML and worked in comments by reviewers ca. 4.15
>> 
>> So this is the first version that is close to be acceptable.
>> 
>> Neil had also submitted different versions to LKML but I am not
>> sure if he still is active anywhere.
>> 
>> I have also checked a diff between the v3.7 version and the
>> current one and there are ca. 30-40% of lines original code by
>> Neil.
>> 
>> So I'd say:
>> 
>> * Neil is the original author and a significant amount of untouched code lines and comments are still there
>> * he designed the overall driver architecture
>> * Hence he is the copyright holder, did license under GPL v2 and we have just made a derivative work out of it
>> * Neil did submit his version of serdev ca. 2015 (quite different from this) but resigned after review feedback
>> * I did add some important features but not the core code and driver architecture
>> * I feel my role as maintainer and massaging everything for DT, serdev and get it upstream, but not "the author"
>> 
>> How should we best reflect this in the AUTHOR macro?
>> 
> 
> Easy:
> 
> MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
> MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
> 
> you can have more than one :)

Ah, that is much easier to solve than I had thought...

> 
>>> 
>>>> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
>>>> +MODULE_LICENSE("GPL v2");
>>>> 

[PATCH v5] now coming.

BR and thanks,
Nikolaus

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

end of thread, other threads:[~2017-12-01  7:49 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-15 21:37 [PATCH v4 0/5] misc serdev: new serdev based driver for Wi2Wi w2sg00x4 GPS module H. Nikolaus Schaller
2017-11-15 21:37 ` H. Nikolaus Schaller
2017-11-15 21:37 ` H. Nikolaus Schaller
2017-11-15 21:37 ` [PATCH v4 1/5] dt-bindings: vendor prefix for Wi2Wi, Inc H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller
2017-11-15 21:37 ` [PATCH v4 2/5] dt-bindings: gps: add w2sg00x4 bindings documentation (GPS module with UART)) H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller
2017-11-15 21:37 ` [PATCH v4 3/5] misc serdev: Add w2sg0004 (gps receiver) power control driver H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller
2017-11-23 16:06   ` Andrew F. Davis
2017-11-23 16:06     ` Andrew F. Davis
2017-11-23 16:06     ` Andrew F. Davis
2017-11-28 21:42     ` H. Nikolaus Schaller
2017-11-28 21:42       ` H. Nikolaus Schaller
2017-11-28 21:42       ` H. Nikolaus Schaller
2017-11-29 14:47       ` Andrew F. Davis
2017-11-29 14:47         ` Andrew F. Davis
2017-11-29 14:47         ` Andrew F. Davis
2017-12-01  7:49         ` H. Nikolaus Schaller
2017-12-01  7:49           ` H. Nikolaus Schaller
2017-11-15 21:37 ` [PATCH v4 4/5] DTS: gta04: add serdev node for w2sg00x4 H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller
2017-11-17 17:08   ` Tony Lindgren
2017-11-17 17:08     ` Tony Lindgren
2017-11-17 17:08     ` Tony Lindgren
2017-11-20  5:29     ` H. Nikolaus Schaller
2017-11-20  5:29       ` H. Nikolaus Schaller
2017-11-20  5:29       ` H. Nikolaus Schaller
2017-11-15 21:37 ` [PATCH v4 5/5] misc serdev: w2sg0004: add debugging code and Kconfig H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller
2017-11-15 21:37   ` H. Nikolaus Schaller

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.