From 5a7eaa51c3e7298416d1faf17ba96678498b6a7e Mon Sep 17 00:00:00 2001
From: Lars-Peter Clausen <lars@metafoo.de>
Date: Mon, 17 May 2010 20:22:47 +0200
Subject: [PATCH 23/69] gta02: Add gps power management device

---
 arch/arm/mach-s3c2440/Makefile                    |    1 +
 arch/arm/mach-s3c2440/gta02-pm-gps.c              |  229 +++++++++++++++++++++
 arch/arm/mach-s3c2440/include/mach/gta02-pm-gps.h |    1 +
 arch/arm/mach-s3c2440/mach-gta02.c                |   16 ++
 4 files changed, 247 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-s3c2440/gta02-pm-gps.c
 create mode 100644 arch/arm/mach-s3c2440/include/mach/gta02-pm-gps.h

diff --git a/arch/arm/mach-s3c2440/Makefile b/arch/arm/mach-s3c2440/Makefile
index cb785b1..062c339 100644
--- a/arch/arm/mach-s3c2440/Makefile
+++ b/arch/arm/mach-s3c2440/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_MACH_AT2440EVB) += mach-at2440evb.o
 obj-$(CONFIG_MACH_MINI2440) += mach-mini2440.o
 obj-$(CONFIG_MACH_NEO1973_GTA02) += mach-gta02.o \
 	gta02-pm-bt.o \
+	gta02-pm-gps.o \
 obj-$(CONFIG_MACH_RX1950) += mach-rx1950.o
 
 # extra machine support
diff --git a/arch/arm/mach-s3c2440/gta02-pm-gps.c b/arch/arm/mach-s3c2440/gta02-pm-gps.c
new file mode 100644
index 0000000..4ca3ac6
--- /dev/null
+++ b/arch/arm/mach-s3c2440/gta02-pm-gps.c
@@ -0,0 +1,229 @@
+/*
+ * GPS Power Management code for the Openmoko Freerunner GSM Phone
+ *
+ * (C) 2007-2009 by Openmoko Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ * All rights reserved.
+ *
+ * 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
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+
+#include <mach/hardware.h>
+#include <mach/gpio-fns.h>
+#include <linux/gpio.h>
+
+#include <mach/gta02.h>
+
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+
+struct gta02_pm_gps_data {
+#ifdef CONFIG_PM
+	int keep_on_in_suspend;
+#endif
+	int power_was_on;
+	struct regulator *regulator;
+};
+
+static struct gta02_pm_gps_data gta02_gps;
+
+int gta02_pm_gps_is_on(void)
+{
+	return gta02_gps.power_was_on;
+}
+EXPORT_SYMBOL_GPL(gta02_pm_gps_is_on);
+
+/* This is the POWERON pin */
+static void gps_pwron_set(int on)
+{
+	if (on) {
+		/* return UART pins to being UART pins */
+		s3c_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPH4_TXD1);
+		/* remove pulldown now it won't be floating any more */
+		s3c_gpio_setpull(S3C2410_GPH(5), S3C_GPIO_PULL_NONE);
+
+		if (!gta02_gps.power_was_on)
+			regulator_enable(gta02_gps.regulator);
+	} else {
+		/*
+		 * take care not to power unpowered GPS from UART TX
+		 * return them to GPIO and force low
+		 */
+		s3c_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPIO_OUTPUT);
+		gpio_set_value(S3C2410_GPH(4), 0);
+		/* don't let RX from unpowered GPS float */
+		s3c_gpio_setpull(S3C2410_GPH(5), S3C_GPIO_PULL_DOWN);
+		if (gta02_gps.power_was_on)
+			regulator_disable(gta02_gps.regulator);
+	}
+}
+
+static int gps_pwron_get(void)
+{
+	return regulator_is_enabled(gta02_gps.regulator);
+}
+
+#ifdef CONFIG_PM
+/* This is the flag for keeping gps ON during suspend */
+static void gps_keep_on_in_suspend_set(int on)
+{
+	gta02_gps.keep_on_in_suspend = on;
+}
+
+static int gps_keep_on_in_suspend_get(void)
+{
+	return gta02_gps.keep_on_in_suspend;
+}
+#endif
+
+static ssize_t power_gps_read(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	int ret = 0;
+
+	if (!strcmp(attr->attr.name, "power_on"))
+		ret = gps_pwron_get();
+#ifdef CONFIG_PM
+	else if (!strcmp(attr->attr.name, "keep_on_in_suspend"))
+		ret = gps_keep_on_in_suspend_get();
+#endif
+	if (ret)
+		return strlcpy(buf, "1\n", 3);
+	else
+		return strlcpy(buf, "0\n", 3);
+}
+
+static ssize_t power_gps_write(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	int ret;
+	unsigned long on;
+
+	ret = strict_strtoul(buf, 10, &on);
+	if (ret)
+		return ret;
+
+	if (!strcmp(attr->attr.name, "power_on")) {
+		gps_pwron_set(on);
+		gta02_gps.power_was_on = !!on;
+#ifdef CONFIG_PM
+	} else if (!strcmp(attr->attr.name, "keep_on_in_suspend")) {
+		gps_keep_on_in_suspend_set(on);
+#endif
+	}
+	return count;
+}
+
+#ifdef CONFIG_PM
+static int gta02_pm_gps_suspend(struct device *dev)
+{
+	if (!gta02_gps.keep_on_in_suspend ||
+		!gta02_gps.power_was_on)
+		gps_pwron_set(0);
+	else
+		dev_warn(dev, "GTA02: keeping gps ON "
+			 "during suspend\n");
+	return 0;
+}
+
+static int gta02_pm_gps_resume(struct device *dev)
+{
+	if (!gta02_gps.keep_on_in_suspend && gta02_gps.power_was_on)
+		gps_pwron_set(1);
+
+	return 0;
+}
+
+static DEVICE_ATTR(keep_on_in_suspend, 0644, power_gps_read, power_gps_write);
+
+static const struct dev_pm_ops gta02_gps_pm_ops = {
+	.suspend	= gta02_pm_gps_suspend,
+	.resume		= gta02_pm_gps_resume,
+};
+
+#define GTA02_GPS_PM_OPS (&gta02_gps_pm_ops)
+
+#else
+#define GTA02_GPS_PM_OPS NULL
+#endif
+
+static DEVICE_ATTR(power_on, 0644, power_gps_read, power_gps_write);
+
+static struct attribute *gta02_gps_sysfs_entries[] = {
+	&dev_attr_power_on.attr,
+#ifdef CONFIG_PM
+	&dev_attr_keep_on_in_suspend.attr,
+#endif
+	NULL
+};
+
+static struct attribute_group gta02_gps_attr_group = {
+	.name	= NULL,
+	.attrs	= gta02_gps_sysfs_entries,
+};
+
+static int __devinit gta02_pm_gps_probe(struct platform_device *pdev)
+{
+	gta02_gps.regulator = regulator_get(&pdev->dev, "RF_3V");
+	if (IS_ERR(gta02_gps.regulator)) {
+		dev_err(&pdev->dev, "probe failed %ld\n",
+			PTR_ERR(gta02_gps.regulator));
+
+		return PTR_ERR(gta02_gps.regulator);
+	}
+
+	dev_info(&pdev->dev, "starting\n");
+
+
+	/*
+	 * take care not to power unpowered GPS from UART TX
+	 * return them to GPIO and force low
+	 */
+	gpio_request(S3C2410_GPH(4), "gps tx");
+	gpio_direction_output(S3C2410_GPH(4), 0);
+	/* don't let RX from unpowered GPS float */
+	s3c_gpio_setpull(S3C2410_GPH(5), S3C_GPIO_PULL_UP);
+
+	return sysfs_create_group(&pdev->dev.kobj,
+				  &gta02_gps_attr_group);
+}
+
+static int __devexit gta02_pm_gps_remove(struct platform_device *pdev)
+{
+	regulator_put(gta02_gps.regulator);
+	sysfs_remove_group(&pdev->dev.kobj, &gta02_gps_attr_group);
+	return 0;
+}
+
+static struct platform_driver gta02_pm_gps_driver = {
+	.probe		= gta02_pm_gps_probe,
+	.remove		= __devexit_p(gta02_pm_gps_remove),
+	.driver		= {
+		.name		= "gta02-pm-gps",
+		.pm			= GTA02_GPS_PM_OPS,
+	},
+};
+
+static int __init gta02_pm_gps_init(void)
+{
+	return platform_driver_register(&gta02_pm_gps_driver);
+}
+module_init(gta02_pm_gps_init);
+
+static void __exit gta02_pm_gps_exit(void)
+{
+	platform_driver_unregister(&gta02_pm_gps_driver);
+}
+module_exit(gta02_pm_gps_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
diff --git a/arch/arm/mach-s3c2440/include/mach/gta02-pm-gps.h b/arch/arm/mach-s3c2440/include/mach/gta02-pm-gps.h
new file mode 100644
index 0000000..f15180a
--- /dev/null
+++ b/arch/arm/mach-s3c2440/include/mach/gta02-pm-gps.h
@@ -0,0 +1 @@
+extern int gta02_pm_gps_is_on(void);
diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
index 5304adc..5a0c57c 100644
--- a/arch/arm/mach-s3c2440/mach-gta02.c
+++ b/arch/arm/mach-s3c2440/mach-gta02.c
@@ -95,6 +95,8 @@
 #include <plat/ts.h>
 
 
+#include <mach/gta02-pm-gps.h>
+
 static struct pcf50633 *gta02_pcf;
 
 /*
@@ -154,6 +156,10 @@ static struct platform_device gta02_pm_bt_dev = {
 	.name = "gta02-pm-bt",
 };
 
+static struct platform_device gta02_pm_gps_dev = {
+	.name = "gta02-pm-gps",
+};
+
 #ifdef CONFIG_CHARGER_PCF50633
 /*
  * On GTA02 the 1A charger features a 48K resistor to 0V on the ID pin.
@@ -264,6 +270,13 @@ static struct regulator_consumer_supply ldo4_consumers[] = {
 	},
 };
 
+static struct regulator_consumer_supply ldo5_consumers[] = {
+	{
+		.dev = &gta02_pm_gps_dev.dev,
+		.supply = "RF_3V",
+	},
+};
+
 struct pcf50633_platform_data gta02_pcf_pdata = {
 	.resumers = {
 		[0] =	PCF50633_INT1_USBINS |
@@ -365,6 +378,8 @@ struct pcf50633_platform_data gta02_pcf_pdata = {
 				.valid_ops_mask = REGULATOR_CHANGE_STATUS,
 				.apply_uV = 1,
 			},
+			.num_consumer_supplies = ARRAY_SIZE(ldo5_consumers),
+			.consumer_supplies = ldo5_consumers,
 		},
 		[PCF50633_REGULATOR_LDO6] = {
 			.constraints = {
@@ -645,6 +660,7 @@ static struct platform_device *gta02_devices[] __initdata = {
 	&s3c_device_adc,
 	&s3c_device_ts,
 	&gta02_pm_bt_dev,
+	&gta02_pm_gps_dev,
 };
 
 /* These guys DO need to be children of PMU. */
-- 
1.7.2.5

