aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-s3c2442/gta02-pm-gps.c
blob: 2b71b7e481108190a83ed7be3d910ecdbeb8080d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/*
 * 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/delay.h>
#include <linux/platform_device.h>

#include <mach/hardware.h>
#include <mach/gpio-fns.h>

#include <asm/mach-types.h>

#include <mach/gta02.h>
#include <linux/mfd/pcf50633/core.h>
#include <linux/mfd/pcf50633/pmic.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 */
		s3c2410_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPH4_TXD1);
		/* remove pulldown now it won't be floating any more */
		s3c2410_gpio_pullup(S3C2410_GPH(5), 0);

		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
		 */
		s3c2410_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPIO_OUTPUT);
		s3c2410_gpio_setpin(S3C2410_GPH(4), 0);
		/* don't let RX from unpowered GPS float */
		s3c2410_gpio_pullup(S3C2410_GPH(5), 1);
		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") ||
	    !strcmp(attr->attr.name, "pwron")) {
		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)
{
	unsigned long on = simple_strtoul(buf, NULL, 10);

	if (!strcmp(attr->attr.name, "power_on") ||
	    !strcmp(attr->attr.name, "pwron")) {
		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 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 __init 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");

	/*
	 * Here we should call the code that handles the set GPS power
	 * off action.  But, the regulator API does not allow us to
	 * reassert regulator state, and when we read the regulator API
	 * logical state, it can differ from the actual state,  So
	 * a workaround for this is to just set the regulator off in the
	 * PMU directly.  Because that's different from normal flow, we
	 * have to reproduce other things from the OFF action here too.
	 */

	/*
	 * u-boot enables LDO5 (GPS), which doesn't make sense and
	 * causes confusion. We therefore disable the regulator here.
	 */
	pcf50633_reg_write(gta02_pcf, PCF50633_REG_LDO5ENA, 0);

	/*
	 * take care not to power unpowered GPS from UART TX
	 * return them to GPIO and force low
	 */
	s3c2410_gpio_cfgpin(S3C2410_GPH(4), S3C2410_GPIO_OUTPUT);
	s3c2410_gpio_setpin(S3C2410_GPH(4), 0);
	/* don't let RX from unpowered GPS float */
	s3c2410_gpio_pullup(S3C2410_GPH(5), 1);

	return sysfs_create_group(&pdev->dev.kobj,
				  &gta02_gps_attr_group);
}

static int 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		= gta02_pm_gps_remove,
	.driver		= {
		.name		= "gta02-pm-gps",
		.pm			= GTA02_GPS_PM_OPS,
	},
};

static int __devinit gta02_pm_gps_init(void)
{
	return platform_driver_register(&gta02_pm_gps_driver);
}
module_init(gta02_pm_gps_init);

static void 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>");