diff mbox

[v3,3/3] misc: Add w2sg0004 gps receiver driver

Message ID c53ab7a3ca68f8b9f802c1ea799d72e1cb04a1eb.1445018913.git.hns@goldelico.com
State Under Review, archived
Headers show

Commit Message

H. Nikolaus Schaller Oct. 16, 2015, 6:08 p.m. UTC
Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
Use uart slave + notification hooks to glue with tty and turn on/off the
module. Detect if the module is turned on (sends data) but should be off,
e.g. if already turned on during boot.

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 serial slave API.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 .../devicetree/bindings/misc/wi2wi,w2sg0004.txt    |  18 +
 .../devicetree/bindings/vendor-prefixes.txt        |   1 +
 drivers/misc/Kconfig                               |  18 +
 drivers/misc/Makefile                              |   1 +
 drivers/misc/w2sg0004.c                            | 443 +++++++++++++++++++++
 drivers/tty/serial/serial_core.c                   |  25 +-
 include/linux/w2sg0004.h                           |  27 ++
 7 files changed, 522 insertions(+), 11 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt
 create mode 100644 drivers/misc/w2sg0004.c
 create mode 100644 include/linux/w2sg0004.h

Comments

H. Nikolaus Schaller Oct. 16, 2015, 6:15 p.m. UTC | #1
Am 16.10.2015 um 20:08 schrieb H. Nikolaus Schaller <hns@goldelico.com>:

> Add driver for Wi2Wi W2SG0004/84 GPS module connected through uart.
> Use uart slave + notification hooks to glue with tty and turn on/off the
> module. Detect if the module is turned on (sends data) but should be off,
> e.g. if already turned on during boot.
> 
> 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 serial slave API.
> 
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
> .../devicetree/bindings/misc/wi2wi,w2sg0004.txt    |  18 +
> .../devicetree/bindings/vendor-prefixes.txt        |   1 +
> drivers/misc/Kconfig                               |  18 +
> drivers/misc/Makefile                              |   1 +
> drivers/misc/w2sg0004.c                            | 443 +++++++++++++++++++++
> drivers/tty/serial/serial_core.c                   |  25 +-
^^^sorry this change is garbage from patch editing^^^
> include/linux/w2sg0004.h                           |  27 ++
> 7 files changed, 522 insertions(+), 11 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt
> create mode 100644 drivers/misc/w2sg0004.c
> create mode 100644 include/linux/w2sg0004.h
> 
> diff --git a/Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt b/Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt
> new file mode 100644
> index 0000000..ef0d6d5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt
> @@ -0,0 +1,18 @@
> +Wi2Wi GPS module connected through UART
> +
> +Required properties:
> +- compatible: wi2wi,w2sg0004 or wi2wi,w2sg0084
> +- on-off-gpio: the GPIO that controls the module's on-off toggle input
> +- uart: the uart we are connected to (provides DTR for power control)
> +
> +Optional properties:
> +- lna-suppy: an (optional) LNA regulator that is enabled together with the GPS receiver
> +
> +example:
> +
> +        gps_receiver: w2sg0004 {
> +                compatible = "wi2wi,w2sg0004";
> +                lna-supply = <&vsim>;	/* LNA regulator */
> +                on-off-gpio = <&gpio5 17 0>;	/* GPIO_145: trigger for turning on/off w2sg0004 */
> +                uart = <&uart1>;	/* we are a slave of uart1 */
> +        }
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
> index 82d2ac9..a778eb5 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.txt
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
> @@ -230,6 +230,7 @@ via	VIA Technologies, Inc.
> virtio	Virtual I/O Device Specification, developed by the OASIS consortium
> voipac	Voipac Technologies s.r.o.
> wexler	Wexler
> +wi2wi	Wi2Wi, Inc.
> winbond Winbond Electronics corp.
> wlf	Wolfson Microelectronics
> wm	Wondermedia Technologies, Inc.
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index ccccc29..1279faf 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -537,4 +537,22 @@ source "drivers/misc/mic/Kconfig"
> source "drivers/misc/genwqe/Kconfig"
> source "drivers/misc/echo/Kconfig"
> source "drivers/misc/cxl/Kconfig"
> +
> +menu "GTA04 misc hardware support"
> +
> +config W2SG0004
> +	tristate "W2SG0004 on/off control"
> +	depends on GPIOLIB
> +	help
> +	  Enable on/off control of W2SG0004 GPS to allow powering up/down if
> +	  the /dev/tty$n is opened/closed.
> +	  It also provides a rfkill gps node to control the LNA power.
> +
> +config W2SG0004_DEBUG
> +	bool "W2SG0004 on/off debugging"
> +	depends on W2SG0004
> +	help
> +	  Enable driver debugging mode of W2SG0004 GPS.
> +
> +endmenu
> endmenu
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index 537d7f3..a153a89 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -54,5 +54,6 @@ obj-$(CONFIG_SRAM)		+= sram.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/
> diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
> new file mode 100644
> index 0000000..6aadf44
> --- /dev/null
> +++ b/drivers/misc/w2sg0004.c
> @@ -0,0 +1,443 @@
> +/*
> + * w2sg0004.c
> + * 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/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/sched.h>
> +#include <linux/irq.h>
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +#include <linux/delay.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/w2sg0004.h>
> +#include <linux/workqueue.h>
> +#include <linux/rfkill.h>
> +#include <linux/serial_core.h>
> +
> +#ifdef CONFIG_W2SG0004_DEBUG
> +#undef pr_debug
> +#define pr_debug printk
> +#endif
> +
> +/*
> + * 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 uart_port *uart;		/* the drvdata of the uart or tty */
> +	enum w2sg_state	state;
> +	int		requested;	/* requested state (0/1) */
> +	int		suspended;
> +	spinlock_t	lock;
> +	struct delayed_work work;
> +};
> +
> +static int w2sg_data_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_data_set_power(void *pdata, int val)
> +{
> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
> +	unsigned long flags;
> +
> +	pr_debug("%s to %d (%d)\n", __func__, val, data->requested);
> +
> +	spin_lock_irqsave(&data->lock, flags);
> +
> +	if (val && !data->requested) {
> +		data->requested = true;
> +	} else if (!val && data->requested) {
> +		data->backoff = HZ;
> +		data->requested = false;
> +	} else
> +		goto unlock;
> +
> +	pr_debug("w2sg scheduled for %d\n", data->requested);
> +
> +	if (!data->suspended)
> +		schedule_delayed_work(&data->work, 0);
> +unlock:
> +	spin_unlock_irqrestore(&data->lock, flags);
> +}
> +
> +/* called each time data is received by the host (i.e. sent by the w2sg0004) */
> +
> +static int rx_notification(void *pdata, unsigned int *c)
> +{
> +	struct w2sg_data *data = (struct w2sg_data *) pdata;
> +	unsigned long flags;
> +
> +	if (!data->requested && !data->is_on) {
> +		/* we have received a RX signal while GPS should be off */
> +		pr_debug("%02x!", *c);
> +
> +		if ((data->state == W2SG_IDLE) &&
> +		    time_after(jiffies,
> +		    data->last_toggle + data->backoff)) {
> +			/* Should be off by now, time to toggle again */
> +			pr_debug("w2sg has sent data although it should be off!\n");
> +			data->is_on = true;
> +			data->backoff *= 2;
> +			spin_lock_irqsave(&data->lock, flags);
> +			if (!data->suspended)
> +				schedule_delayed_work(&data->work, 0);
> +			spin_unlock_irqrestore(&data->lock, flags);
> +		}
> +	}
> +	return 0;	/* forward to tty */
> +}
> +
> +/* called by uart modem control line changes (DTR) */
> +
> +static int w2sg_mctrl(void *pdata, int val)
> +{
> +	pr_debug("%s(...,%x)\n", __func__, val);
> +	val = (val & TIOCM_DTR) != 0;	/* DTR controls power on/off */
> +	w2sg_data_set_power((struct w2sg_data *) pdata, val);
> +	return 0;
> +}
> +
> +/* 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:
> +		spin_lock_irq(&data->lock);
> +		if (data->requested == data->is_on) {
> +			spin_unlock_irq(&data->lock);
> +			return;
> +		}
> +		spin_unlock_irq(&data->lock);
> +		w2sg_data_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_data_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_data_set_lna_power(data);
> +}
> +
> +static struct rfkill_ops w2sg_data0004_rfkill_ops = {
> +	.set_block = w2sg_data_rfkill_set_block,
> +};
> +
> +static int w2sg_data_probe(struct platform_device *pdev)
> +{
> +	struct w2sg_pdata *pdata = dev_get_platdata(&pdev->dev);
> +	struct w2sg_data *data;
> +	struct rfkill *rf_kill;
> +	int err;
> +
> +	pr_debug("%s()\n", __func__);
> +
> +	if (pdev->dev.of_node) {
> +		struct device *dev = &pdev->dev;
> +		enum of_gpio_flags flags;
> +
> +		pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
> +		if (!pdata)
> +			return -ENOMEM;
> +
> +		pdata->on_off_gpio = of_get_named_gpio_flags(dev->of_node,
> +							     "on-off-gpio", 0,
> +							     &flags);
> +
> +		if (pdata->on_off_gpio == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;	/* defer until we have all gpios */
> +
> +		pdata->lna_regulator = devm_regulator_get_optional(dev, "lna");
> +
> +		pr_debug("%s() lna_regulator = %p\n", __func__, pdata->lna_regulator);
> +
> +		pdev->dev.platform_data = pdata;
> +	}
> +
> +	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (data == NULL)
> +		return -ENOMEM;
> +
> +	data->lna_regulator = pdata->lna_regulator;
> +	data->lna_blocked = true;
> +	data->lna_is_off = true;
> +
> +	data->on_off_gpio = pdata->on_off_gpio;
> +
> +	data->is_on = false;
> +	data->requested = false;
> +	data->state = W2SG_IDLE;
> +	data->last_toggle = jiffies;
> +	data->backoff = HZ;
> +
> +#ifdef CONFIG_OF
> +	data->uart = devm_serial_get_uart_by_phandle(&pdev->dev, "uart", 0);
> +	if (IS_ERR(data->uart)) {
> +		if (PTR_ERR(data->uart) == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;	/* we can't probe yet */
> +		data->uart = NULL;	/* no UART */
> +		}
> +#endif
> +
> +	INIT_DELAYED_WORK(&data->work, toggle_work);
> +	spin_lock_init(&data->lock);
> +
> +	err = devm_gpio_request(&pdev->dev, data->on_off_gpio, "w2sg0004-on-off");
> +	if (err < 0)
> +		goto out;
> +
> +	gpio_direction_output(data->on_off_gpio, false);
> +
> +	if (data->uart) {
> +		struct ktermios termios = {
> +			.c_iflag = IGNBRK,
> +			.c_oflag = 0,
> +			.c_cflag = B9600 | CS8,
> +			.c_lflag = 0,
> +			.c_line = 0,
> +			.c_ispeed = 9600,
> +			.c_ospeed = 9600
> +		};
> +		uart_register_slave(data->uart, data);
> +		uart_register_mctrl_notification(data->uart, w2sg_mctrl);
> +		uart_register_rx_notification(data->uart, rx_notification,
> +					      &termios);
> +	}
> +
> +	rf_kill = rfkill_alloc("GPS", &pdev->dev, RFKILL_TYPE_GPS,
> +				&w2sg_data0004_rfkill_ops, data);
> +	if (rf_kill == NULL) {
> +		err = -ENOMEM;
> +		goto err_rfkill;
> +	}
> +
> +	err = rfkill_register(rf_kill);
> +	if (err) {
> +		dev_err(&pdev->dev, "Cannot register rfkill device\n");
> +		goto err_rfkill;
> +	}
> +
> +	data->rf_kill = rf_kill;
> +
> +	platform_set_drvdata(pdev, data);
> +
> +	pr_debug("w2sg0004 probed\n");
> +
> +#ifdef CONFIG_W2SG0004_DEBUG
> +	/* 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
> +
> +	/* if we won't receive mctrl notifications, turn on.
> +	 * otherwise keep off until DTR is asserted through mctrl.
> +	 */
> +
> +	w2sg_data_set_power(data, !data->uart);
> +
> +	return 0;
> +
> +err_rfkill:
> +	rfkill_destroy(rf_kill);
> +out:
> +	return err;
> +}
> +
> +static int w2sg_data_remove(struct platform_device *pdev)
> +{
> +	struct w2sg_data *data = platform_get_drvdata(pdev);
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	if (data->uart) {
> +		uart_register_rx_notification(data->uart, NULL, NULL);
> +		uart_register_slave(data->uart, NULL);
> +	}
> +	return 0;
> +}
> +
> +static int w2sg_data_suspend(struct device *dev)
> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	spin_lock_irq(&data->lock);
> +	data->suspended = true;
> +	spin_unlock_irq(&data->lock);
> +
> +	cancel_delayed_work_sync(&data->work);
> +
> +	w2sg_data_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_data_resume(struct device *dev)
> +{
> +	struct w2sg_data *data = dev_get_drvdata(dev);
> +
> +	spin_lock_irq(&data->lock);
> +	data->suspended = false;
> +	spin_unlock_irq(&data->lock);
> +
> +	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;
> +}
> +
> +#if defined(CONFIG_OF)
> +static const struct of_device_id w2sg0004_of_match[] = {
> +	{ .compatible = "wi2wi,w2sg0004" },
> +	{ .compatible = "wi2wi,w2sg0084" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
> +#endif
> +
> +SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_data_suspend, w2sg_data_resume);
> +
> +static struct platform_driver w2sg_data_driver = {
> +	.probe		= w2sg_data_probe,
> +	.remove		= w2sg_data_remove,
> +	.driver = {
> +		.name	= "w2sg0004",
> +		.owner	= THIS_MODULE,
> +		.pm	= &w2sg_pm_ops,
> +		.of_match_table = of_match_ptr(w2sg0004_of_match)
> +	},
> +};
> +
> +module_platform_driver(w2sg_data_driver);
> +
> +MODULE_ALIAS("w2sg0004");
> +
> +MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
> +MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
> +MODULE_LICENSE("GPL v2");
VVVsorry this change is garbage from patch editingVVV
> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> index b731100..c8ae0dd 100644
> --- a/drivers/tty/serial/serial_core.c
> +++ b/drivers/tty/serial/serial_core.c
> @@ -177,7 +177,7 @@ void uart_register_slave(struct uart_port *uport, void *slave)
> EXPORT_SYMBOL_GPL(uart_register_slave);
> 
> void uart_register_mctrl_notification(struct uart_port *uport,
> -		int (*function)(void *slave, int state))
> +			int (*function)(void *slave, int state))
> {
> 	uport->mctrl_notification = function;
> }
> @@ -208,14 +208,14 @@ void uart_register_rx_notification(struct uart_port *uport,
> 			retval = uport->ops->startup(uport);
> 			if (retval == 0 && termios) {
> 				int hw_stopped;
> -				/*
> -				 * Initialise the hardware port settings.
> -				 */
> +/*
> + * Initialise the hardware port settings.
> + */
> 				uport->ops->set_termios(uport, termios, NULL);
> 
> -				/*
> -				 * Set modem status enables based on termios cflag
> -				 */
> +/*
> + * Set modem status enables based on termios cflag
> + */
> 				spin_lock_irq(&uport->lock);
> 				if (termios->c_cflag & CRTSCTS)
> 					uport->status |= UPSTAT_CTS_ENABLE;
> @@ -227,11 +227,13 @@ void uart_register_rx_notification(struct uart_port *uport,
> 				else
> 					uport->status |= UPSTAT_DCD_ENABLE;
> 
> -				/* reset sw-assisted CTS flow control based on (possibly) new mode */
> +/*
> + * reset sw-assisted CTS flow control based on (possibly) new mode
> + */
> 				hw_stopped = uport->hw_stopped;
> 				uport->hw_stopped = uart_softcts_mode(uport) &&
> -					!(uport->ops->get_mctrl(uport)
> -						& TIOCM_CTS);
> +					!(uport->ops->get_mctrl(uport) &
> +						TIOCM_CTS);
> 				if (uport->hw_stopped) {
> 					if (!hw_stopped)
> 						uport->ops->stop_tx(uport);
> @@ -432,7 +434,8 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
> 			uart_clear_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
> 		/*
> 		 * if we have a slave that has registered for rx_notifications
> -		 * we do not shut down the uart port to be able to monitor the device
> +		 * we do not shut down the uart port to be able to monitor the
> +		 * device
> 		 */
> 		if (!uport->rx_notification)
> 			uart_port_shutdown(port);
^^^sorry this change is garbage from patch editing^^^
> diff --git a/include/linux/w2sg0004.h b/include/linux/w2sg0004.h
> new file mode 100644
> index 0000000..ad0c4a1
> --- /dev/null
> +++ b/include/linux/w2sg0004.h
> @@ -0,0 +1,27 @@
> +/*
> + * UART slave to allow ON/OFF control of w2sg0004 GPS receiver.
> + *
> + * Copyright (C) 2011 Neil Brown <neil@brown.name>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +
> +
> +#ifndef __LINUX_W2SG0004_H
> +#define __LINUX_W2SG0004_H
> +
> +#include <linux/regulator/consumer.h>
> +
> +struct w2sg_pdata {
> +	struct regulator *lna_regulator;	/* enable LNA power */
> +	int	on_off_gpio;	/*  on-off input of the GPS module */
> +};
> +#endif /* __LINUX_W2SG0004_H */
> -- 
> 2.5.1
> 

--
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
Arnd Bergmann Oct. 16, 2015, 7:06 p.m. UTC | #2
On Friday 16 October 2015 20:08:35 H. Nikolaus Schaller wrote:
> +
> +static int w2sg_data_probe(struct platform_device *pdev)
> +{
> +       struct w2sg_pdata *pdata = dev_get_platdata(&pdev->dev);
> +       struct w2sg_data *data;
> +       struct rfkill *rf_kill;
> +       int err;
> +
> +       pr_debug("%s()\n", __func__);
> +
> +       if (pdev->dev.of_node) {
> +               struct device *dev = &pdev->dev;
> +               enum of_gpio_flags flags;
> +
> +               pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
> +               if (!pdata)
> 

Why is this a platform_device and not a serio_device?

	Arnd
--
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
H. Nikolaus Schaller Oct. 16, 2015, 7:27 p.m. UTC | #3
Am 16.10.2015 um 21:06 schrieb Arnd Bergmann <arnd@arndb.de>:

> On Friday 16 October 2015 20:08:35 H. Nikolaus Schaller wrote:
>> +
>> +static int w2sg_data_probe(struct platform_device *pdev)
>> +{
>> +       struct w2sg_pdata *pdata = dev_get_platdata(&pdev->dev);
>> +       struct w2sg_data *data;
>> +       struct rfkill *rf_kill;
>> +       int err;
>> +
>> +       pr_debug("%s()\n", __func__);
>> +
>> +       if (pdev->dev.of_node) {
>> +               struct device *dev = &pdev->dev;
>> +               enum of_gpio_flags flags;
>> +
>> +               pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
>> +               if (!pdata)
>> 
> 
> Why is this a platform_device and not a serio_device?

I can't find a struct serio_device. What is that?

BR and thanks,
Nikolaus
--
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
Arnd Bergmann Oct. 16, 2015, 7:38 p.m. UTC | #4
On Friday 16 October 2015 21:27:11 H. Nikolaus Schaller wrote:
> Am 16.10.2015 um 21:06 schrieb Arnd Bergmann <arnd@arndb.de>:
> 
> > On Friday 16 October 2015 20:08:35 H. Nikolaus Schaller wrote:
> >> +
> >> +static int w2sg_data_probe(struct platform_device *pdev)
> >> +{
> >> +       struct w2sg_pdata *pdata = dev_get_platdata(&pdev->dev);
> >> +       struct w2sg_data *data;
> >> +       struct rfkill *rf_kill;
> >> +       int err;
> >> +
> >> +       pr_debug("%s()\n", __func__);
> >> +
> >> +       if (pdev->dev.of_node) {
> >> +               struct device *dev = &pdev->dev;
> >> +               enum of_gpio_flags flags;
> >> +
> >> +               pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
> >> +               if (!pdata)
> >> 
> > 
> > Why is this a platform_device and not a serio_device?
> 
> I can't find a struct serio_device. What is that?
> 

Sorry, I meant 'struct serio', see drivers/input/serio/

This is an existing infrastructure that is used for devices attached
to a dumb serial device (rs232 or 8042/psaux usually). They have
a user interface for connecting a driver to a port, but you should
be able to do it all in the kernel as well if DT has the information
what device is connected.

	Arnd
--
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
H. Nikolaus Schaller Oct. 16, 2015, 8:07 p.m. UTC | #5
Am 16.10.2015 um 21:38 schrieb Arnd Bergmann <arnd@arndb.de>:

> On Friday 16 October 2015 21:27:11 H. Nikolaus Schaller wrote:
>> Am 16.10.2015 um 21:06 schrieb Arnd Bergmann <arnd@arndb.de>:
>> 
>>> On Friday 16 October 2015 20:08:35 H. Nikolaus Schaller wrote:
>>>> +
>>>> +static int w2sg_data_probe(struct platform_device *pdev)
>>>> +{
>>>> +       struct w2sg_pdata *pdata = dev_get_platdata(&pdev->dev);
>>>> +       struct w2sg_data *data;
>>>> +       struct rfkill *rf_kill;
>>>> +       int err;
>>>> +
>>>> +       pr_debug("%s()\n", __func__);
>>>> +
>>>> +       if (pdev->dev.of_node) {
>>>> +               struct device *dev = &pdev->dev;
>>>> +               enum of_gpio_flags flags;
>>>> +
>>>> +               pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
>>>> +               if (!pdata)
>>>> 
>>> 
>>> Why is this a platform_device and not a serio_device?
>> 
>> I can't find a struct serio_device. What is that?
>> 
> 
> Sorry, I meant 'struct serio', see drivers/input/serio/
> 
> This is an existing infrastructure that is used for devices attached
> to a dumb serial device (rs232 or 8042/psaux usually). They have
> a user interface for connecting a driver to a port, but you should
> be able to do it all in the kernel as well if DT has the information
> what device is connected.

Ah, I understand. But it is for a different purpose. E.g. making a
serial device (mouse/touch) an input device. So it is a driver sitting
"on top" of tty/uart drivers.

The problem to be solved here is a different one. The only task for
the driver is to do power control of the device. I.e. turn it on by
open("/dev/ttyX") or asserting DTR.

So we are on a much lower level.

Please see also the patch 0/3 I have resent (the BLURB defined
by git --edit-description was apparently eaten by my git send-email).

BR and thanks,
Nikolaus

--
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
kernel test robot Oct. 16, 2015, 11:45 p.m. UTC | #6
Hi Nikolaus,

[auto build test WARNING on tty/tty-next -- if it's inappropriate base, please suggest rules for selecting the more suitable base]

url:    https://github.com/0day-ci/linux/commits/H-Nikolaus-Schaller/UART-slave-device-support-goldelico-version/20151017-021238
config: xtensa-allmodconfig (attached as .config)
reproduce:
        wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=xtensa 

All warnings (new ones prefixed by >>):

>> drivers/misc/w2sg0004.c:361:12: warning: 'w2sg_data_suspend' defined but not used [-Wunused-function]
    static int w2sg_data_suspend(struct device *dev)
               ^
>> drivers/misc/w2sg0004.c:399:12: warning: 'w2sg_data_resume' defined but not used [-Wunused-function]
    static int w2sg_data_resume(struct device *dev)
               ^

vim +/w2sg_data_suspend +361 drivers/misc/w2sg0004.c

   355			uart_register_rx_notification(data->uart, NULL, NULL);
   356			uart_register_slave(data->uart, NULL);
   357		}
   358		return 0;
   359	}
   360	
 > 361	static int w2sg_data_suspend(struct device *dev)
   362	{
   363		struct w2sg_data *data = dev_get_drvdata(dev);
   364	
   365		spin_lock_irq(&data->lock);
   366		data->suspended = true;
   367		spin_unlock_irq(&data->lock);
   368	
   369		cancel_delayed_work_sync(&data->work);
   370	
   371		w2sg_data_set_lna_power(data);	/* shuts down if needed */
   372	
   373		if (data->state == W2SG_PULSE) {
   374			msleep(10);
   375			gpio_set_value_cansleep(data->on_off_gpio, 1);
   376			data->last_toggle = jiffies;
   377			data->is_on = !data->is_on;
   378			data->state = W2SG_NOPULSE;
   379		}
   380	
   381		if (data->state == W2SG_NOPULSE) {
   382			msleep(10);
   383			data->state = W2SG_IDLE;
   384		}
   385	
   386		if (data->is_on) {
   387			pr_info("GPS off for suspend %d %d %d\n", data->requested,
   388				data->is_on, data->lna_is_off);
   389	
   390			gpio_set_value_cansleep(data->on_off_gpio, 0);
   391			msleep(10);
   392			gpio_set_value_cansleep(data->on_off_gpio, 1);
   393			data->is_on = 0;
   394		}
   395	
   396		return 0;
   397	}
   398	
 > 399	static int w2sg_data_resume(struct device *dev)
   400	{
   401		struct w2sg_data *data = dev_get_drvdata(dev);
   402	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt b/Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt
new file mode 100644
index 0000000..ef0d6d5
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/wi2wi,w2sg0004.txt
@@ -0,0 +1,18 @@ 
+Wi2Wi GPS module connected through UART
+
+Required properties:
+- compatible: wi2wi,w2sg0004 or wi2wi,w2sg0084
+- on-off-gpio: the GPIO that controls the module's on-off toggle input
+- uart: the uart we are connected to (provides DTR for power control)
+
+Optional properties:
+- lna-suppy: an (optional) LNA regulator that is enabled together with the GPS receiver
+
+example:
+
+        gps_receiver: w2sg0004 {
+                compatible = "wi2wi,w2sg0004";
+                lna-supply = <&vsim>;	/* LNA regulator */
+                on-off-gpio = <&gpio5 17 0>;	/* GPIO_145: trigger for turning on/off w2sg0004 */
+                uart = <&uart1>;	/* we are a slave of uart1 */
+        }
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 82d2ac9..a778eb5 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -230,6 +230,7 @@  via	VIA Technologies, Inc.
 virtio	Virtual I/O Device Specification, developed by the OASIS consortium
 voipac	Voipac Technologies s.r.o.
 wexler	Wexler
+wi2wi	Wi2Wi, Inc.
 winbond Winbond Electronics corp.
 wlf	Wolfson Microelectronics
 wm	Wondermedia Technologies, Inc.
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index ccccc29..1279faf 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -537,4 +537,22 @@  source "drivers/misc/mic/Kconfig"
 source "drivers/misc/genwqe/Kconfig"
 source "drivers/misc/echo/Kconfig"
 source "drivers/misc/cxl/Kconfig"
+
+menu "GTA04 misc hardware support"
+
+config W2SG0004
+	tristate "W2SG0004 on/off control"
+	depends on GPIOLIB
+	help
+	  Enable on/off control of W2SG0004 GPS to allow powering up/down if
+	  the /dev/tty$n is opened/closed.
+	  It also provides a rfkill gps node to control the LNA power.
+
+config W2SG0004_DEBUG
+	bool "W2SG0004 on/off debugging"
+	depends on W2SG0004
+	help
+	  Enable driver debugging mode of W2SG0004 GPS.
+
+endmenu
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 537d7f3..a153a89 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -54,5 +54,6 @@  obj-$(CONFIG_SRAM)		+= sram.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/
diff --git a/drivers/misc/w2sg0004.c b/drivers/misc/w2sg0004.c
new file mode 100644
index 0000000..6aadf44
--- /dev/null
+++ b/drivers/misc/w2sg0004.c
@@ -0,0 +1,443 @@ 
+/*
+ * w2sg0004.c
+ * 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/module.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/w2sg0004.h>
+#include <linux/workqueue.h>
+#include <linux/rfkill.h>
+#include <linux/serial_core.h>
+
+#ifdef CONFIG_W2SG0004_DEBUG
+#undef pr_debug
+#define pr_debug printk
+#endif
+
+/*
+ * 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 uart_port *uart;		/* the drvdata of the uart or tty */
+	enum w2sg_state	state;
+	int		requested;	/* requested state (0/1) */
+	int		suspended;
+	spinlock_t	lock;
+	struct delayed_work work;
+};
+
+static int w2sg_data_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_data_set_power(void *pdata, int val)
+{
+	struct w2sg_data *data = (struct w2sg_data *) pdata;
+	unsigned long flags;
+
+	pr_debug("%s to %d (%d)\n", __func__, val, data->requested);
+
+	spin_lock_irqsave(&data->lock, flags);
+
+	if (val && !data->requested) {
+		data->requested = true;
+	} else if (!val && data->requested) {
+		data->backoff = HZ;
+		data->requested = false;
+	} else
+		goto unlock;
+
+	pr_debug("w2sg scheduled for %d\n", data->requested);
+
+	if (!data->suspended)
+		schedule_delayed_work(&data->work, 0);
+unlock:
+	spin_unlock_irqrestore(&data->lock, flags);
+}
+
+/* called each time data is received by the host (i.e. sent by the w2sg0004) */
+
+static int rx_notification(void *pdata, unsigned int *c)
+{
+	struct w2sg_data *data = (struct w2sg_data *) pdata;
+	unsigned long flags;
+
+	if (!data->requested && !data->is_on) {
+		/* we have received a RX signal while GPS should be off */
+		pr_debug("%02x!", *c);
+
+		if ((data->state == W2SG_IDLE) &&
+		    time_after(jiffies,
+		    data->last_toggle + data->backoff)) {
+			/* Should be off by now, time to toggle again */
+			pr_debug("w2sg has sent data although it should be off!\n");
+			data->is_on = true;
+			data->backoff *= 2;
+			spin_lock_irqsave(&data->lock, flags);
+			if (!data->suspended)
+				schedule_delayed_work(&data->work, 0);
+			spin_unlock_irqrestore(&data->lock, flags);
+		}
+	}
+	return 0;	/* forward to tty */
+}
+
+/* called by uart modem control line changes (DTR) */
+
+static int w2sg_mctrl(void *pdata, int val)
+{
+	pr_debug("%s(...,%x)\n", __func__, val);
+	val = (val & TIOCM_DTR) != 0;	/* DTR controls power on/off */
+	w2sg_data_set_power((struct w2sg_data *) pdata, val);
+	return 0;
+}
+
+/* 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:
+		spin_lock_irq(&data->lock);
+		if (data->requested == data->is_on) {
+			spin_unlock_irq(&data->lock);
+			return;
+		}
+		spin_unlock_irq(&data->lock);
+		w2sg_data_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_data_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_data_set_lna_power(data);
+}
+
+static struct rfkill_ops w2sg_data0004_rfkill_ops = {
+	.set_block = w2sg_data_rfkill_set_block,
+};
+
+static int w2sg_data_probe(struct platform_device *pdev)
+{
+	struct w2sg_pdata *pdata = dev_get_platdata(&pdev->dev);
+	struct w2sg_data *data;
+	struct rfkill *rf_kill;
+	int err;
+
+	pr_debug("%s()\n", __func__);
+
+	if (pdev->dev.of_node) {
+		struct device *dev = &pdev->dev;
+		enum of_gpio_flags flags;
+
+		pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+		if (!pdata)
+			return -ENOMEM;
+
+		pdata->on_off_gpio = of_get_named_gpio_flags(dev->of_node,
+							     "on-off-gpio", 0,
+							     &flags);
+
+		if (pdata->on_off_gpio == -EPROBE_DEFER)
+			return -EPROBE_DEFER;	/* defer until we have all gpios */
+
+		pdata->lna_regulator = devm_regulator_get_optional(dev, "lna");
+
+		pr_debug("%s() lna_regulator = %p\n", __func__, pdata->lna_regulator);
+
+		pdev->dev.platform_data = pdata;
+	}
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	data->lna_regulator = pdata->lna_regulator;
+	data->lna_blocked = true;
+	data->lna_is_off = true;
+
+	data->on_off_gpio = pdata->on_off_gpio;
+
+	data->is_on = false;
+	data->requested = false;
+	data->state = W2SG_IDLE;
+	data->last_toggle = jiffies;
+	data->backoff = HZ;
+
+#ifdef CONFIG_OF
+	data->uart = devm_serial_get_uart_by_phandle(&pdev->dev, "uart", 0);
+	if (IS_ERR(data->uart)) {
+		if (PTR_ERR(data->uart) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;	/* we can't probe yet */
+		data->uart = NULL;	/* no UART */
+		}
+#endif
+
+	INIT_DELAYED_WORK(&data->work, toggle_work);
+	spin_lock_init(&data->lock);
+
+	err = devm_gpio_request(&pdev->dev, data->on_off_gpio, "w2sg0004-on-off");
+	if (err < 0)
+		goto out;
+
+	gpio_direction_output(data->on_off_gpio, false);
+
+	if (data->uart) {
+		struct ktermios termios = {
+			.c_iflag = IGNBRK,
+			.c_oflag = 0,
+			.c_cflag = B9600 | CS8,
+			.c_lflag = 0,
+			.c_line = 0,
+			.c_ispeed = 9600,
+			.c_ospeed = 9600
+		};
+		uart_register_slave(data->uart, data);
+		uart_register_mctrl_notification(data->uart, w2sg_mctrl);
+		uart_register_rx_notification(data->uart, rx_notification,
+					      &termios);
+	}
+
+	rf_kill = rfkill_alloc("GPS", &pdev->dev, RFKILL_TYPE_GPS,
+				&w2sg_data0004_rfkill_ops, data);
+	if (rf_kill == NULL) {
+		err = -ENOMEM;
+		goto err_rfkill;
+	}
+
+	err = rfkill_register(rf_kill);
+	if (err) {
+		dev_err(&pdev->dev, "Cannot register rfkill device\n");
+		goto err_rfkill;
+	}
+
+	data->rf_kill = rf_kill;
+
+	platform_set_drvdata(pdev, data);
+
+	pr_debug("w2sg0004 probed\n");
+
+#ifdef CONFIG_W2SG0004_DEBUG
+	/* 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
+
+	/* if we won't receive mctrl notifications, turn on.
+	 * otherwise keep off until DTR is asserted through mctrl.
+	 */
+
+	w2sg_data_set_power(data, !data->uart);
+
+	return 0;
+
+err_rfkill:
+	rfkill_destroy(rf_kill);
+out:
+	return err;
+}
+
+static int w2sg_data_remove(struct platform_device *pdev)
+{
+	struct w2sg_data *data = platform_get_drvdata(pdev);
+
+	cancel_delayed_work_sync(&data->work);
+
+	if (data->uart) {
+		uart_register_rx_notification(data->uart, NULL, NULL);
+		uart_register_slave(data->uart, NULL);
+	}
+	return 0;
+}
+
+static int w2sg_data_suspend(struct device *dev)
+{
+	struct w2sg_data *data = dev_get_drvdata(dev);
+
+	spin_lock_irq(&data->lock);
+	data->suspended = true;
+	spin_unlock_irq(&data->lock);
+
+	cancel_delayed_work_sync(&data->work);
+
+	w2sg_data_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_data_resume(struct device *dev)
+{
+	struct w2sg_data *data = dev_get_drvdata(dev);
+
+	spin_lock_irq(&data->lock);
+	data->suspended = false;
+	spin_unlock_irq(&data->lock);
+
+	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;
+}
+
+#if defined(CONFIG_OF)
+static const struct of_device_id w2sg0004_of_match[] = {
+	{ .compatible = "wi2wi,w2sg0004" },
+	{ .compatible = "wi2wi,w2sg0084" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, w2sg0004_of_match);
+#endif
+
+SIMPLE_DEV_PM_OPS(w2sg_pm_ops, w2sg_data_suspend, w2sg_data_resume);
+
+static struct platform_driver w2sg_data_driver = {
+	.probe		= w2sg_data_probe,
+	.remove		= w2sg_data_remove,
+	.driver = {
+		.name	= "w2sg0004",
+		.owner	= THIS_MODULE,
+		.pm	= &w2sg_pm_ops,
+		.of_match_table = of_match_ptr(w2sg0004_of_match)
+	},
+};
+
+module_platform_driver(w2sg_data_driver);
+
+MODULE_ALIAS("w2sg0004");
+
+MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
+MODULE_DESCRIPTION("w2sg0004 GPS power management driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index b731100..c8ae0dd 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -177,7 +177,7 @@  void uart_register_slave(struct uart_port *uport, void *slave)
 EXPORT_SYMBOL_GPL(uart_register_slave);
 
 void uart_register_mctrl_notification(struct uart_port *uport,
-		int (*function)(void *slave, int state))
+			int (*function)(void *slave, int state))
 {
 	uport->mctrl_notification = function;
 }
@@ -208,14 +208,14 @@  void uart_register_rx_notification(struct uart_port *uport,
 			retval = uport->ops->startup(uport);
 			if (retval == 0 && termios) {
 				int hw_stopped;
-				/*
-				 * Initialise the hardware port settings.
-				 */
+/*
+ * Initialise the hardware port settings.
+ */
 				uport->ops->set_termios(uport, termios, NULL);
 
-				/*
-				 * Set modem status enables based on termios cflag
-				 */
+/*
+ * Set modem status enables based on termios cflag
+ */
 				spin_lock_irq(&uport->lock);
 				if (termios->c_cflag & CRTSCTS)
 					uport->status |= UPSTAT_CTS_ENABLE;
@@ -227,11 +227,13 @@  void uart_register_rx_notification(struct uart_port *uport,
 				else
 					uport->status |= UPSTAT_DCD_ENABLE;
 
-				/* reset sw-assisted CTS flow control based on (possibly) new mode */
+/*
+ * reset sw-assisted CTS flow control based on (possibly) new mode
+ */
 				hw_stopped = uport->hw_stopped;
 				uport->hw_stopped = uart_softcts_mode(uport) &&
-					!(uport->ops->get_mctrl(uport)
-						& TIOCM_CTS);
+					!(uport->ops->get_mctrl(uport) &
+						TIOCM_CTS);
 				if (uport->hw_stopped) {
 					if (!hw_stopped)
 						uport->ops->stop_tx(uport);
@@ -432,7 +434,8 @@  static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
 			uart_clear_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
 		/*
 		 * if we have a slave that has registered for rx_notifications
-		 * we do not shut down the uart port to be able to monitor the device
+		 * we do not shut down the uart port to be able to monitor the
+		 * device
 		 */
 		if (!uport->rx_notification)
 			uart_port_shutdown(port);
diff --git a/include/linux/w2sg0004.h b/include/linux/w2sg0004.h
new file mode 100644
index 0000000..ad0c4a1
--- /dev/null
+++ b/include/linux/w2sg0004.h
@@ -0,0 +1,27 @@ 
+/*
+ * UART slave to allow ON/OFF control of w2sg0004 GPS receiver.
+ *
+ * Copyright (C) 2011 Neil Brown <neil@brown.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+
+
+#ifndef __LINUX_W2SG0004_H
+#define __LINUX_W2SG0004_H
+
+#include <linux/regulator/consumer.h>
+
+struct w2sg_pdata {
+	struct regulator *lna_regulator;	/* enable LNA power */
+	int	on_off_gpio;	/*  on-off input of the GPS module */
+};
+#endif /* __LINUX_W2SG0004_H */