aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-s3c2442/gta02-pm-wlan.c
blob: 6e0226600b1687cfe7273628443f9fedc7d52202 (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
/*
 * GTA02 WLAN power management
 *
 * (C) 2008, 2009 by Openmoko Inc.
 * Author: Andy Green <andy@openmoko.com>
 * 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/mutex.h>
#include <linux/platform_device.h>

#include <mach/hardware.h>
#include <asm/mach-types.h>

#include <mach/gta02.h>
#include <mach/gta02-pm-wlan.h>
#include <mach/regs-gpio.h>
#include <mach/regs-gpioj.h>
#include <mach/gpio-fns.h>

#include <linux/delay.h>
#include <linux/rfkill.h>


/* ----- Module hardware reset ("power") ----------------------------------- */


void gta02_wlan_reset(int assert_reset)
{
	if (assert_reset) {
		s3c2410_gpio_setpin(GTA02_GPIO_nWLAN_RESET, 0);
		msleep(200); /* probably excessive but we don't have specs */
	} else {
		s3c2410_gpio_setpin(GTA02_GPIO_nWLAN_RESET, 1);
	}
}

/* ----- rfkill ------------------------------------------------------------ */

/*
 * S3C MCI handles suspend/resume through device removal/insertion. In order to
 * preserve rfkill state, as required in clause 7 of section 3.1 in rfkill.txt,
 * we therefore need to maintain rfkill state outside the driver.
 *
 * This platform driver is as good a place as any other.
 */

static int (*gta02_wlan_rfkill_cb)(void *user, int on);
static void *gta02_wlan_rfkill_user;
static DEFINE_MUTEX(gta02_wlan_rfkill_lock);
static int gta02_wlan_rfkill_on;

/*
 * gta02_wlan_query_rfkill_lock is used to obtain the rfkill state before the
 * driver is ready to process rfkill callbacks. To prevent the state from
 * changing until the driver has completed its initialization, we grab and hold
 * the rfkill lock.
 *
 * A call to gta02_wlan_query_rfkill_lock must be followed by either
 * - a call to gta02_wlan_set_rfkill_cb, to complete the setup, or
 * - a call to gta02_wlan_query_rfkill_unlock to abort the setup process.
 */

int gta02_wlan_query_rfkill_lock(void)
{
	mutex_lock(&gta02_wlan_rfkill_lock);
	return gta02_wlan_rfkill_on;
}
EXPORT_SYMBOL_GPL(gta02_wlan_query_rfkill_lock);

void gta02_wlan_query_rfkill_unlock(void)
{
	mutex_unlock(&gta02_wlan_rfkill_lock);
}
EXPORT_SYMBOL_GPL(gta02_wlan_query_rfkill_unlock);

void gta02_wlan_set_rfkill_cb(int (*cb)(void *user, int on), void *user)
{
	BUG_ON(!mutex_is_locked(&gta02_wlan_rfkill_lock));
	BUG_ON(gta02_wlan_rfkill_cb);
	gta02_wlan_rfkill_cb = cb;
	gta02_wlan_rfkill_user = user;
	mutex_unlock(&gta02_wlan_rfkill_lock);
}
EXPORT_SYMBOL_GPL(gta02_wlan_set_rfkill_cb);

void gta02_wlan_clear_rfkill_cb(void)
{
	mutex_lock(&gta02_wlan_rfkill_lock);
	BUG_ON(!gta02_wlan_rfkill_cb);
	gta02_wlan_rfkill_cb = NULL;
	mutex_unlock(&gta02_wlan_rfkill_lock);
}
EXPORT_SYMBOL_GPL(gta02_wlan_clear_rfkill_cb);

static int gta02_wlan_set_radio_block(void *data, bool blocked)
{
	struct device *dev = data;
	int res = 0;

	dev_dbg(dev, "gta02_wlan_toggle_radio: blocked %d (%p)\n",
	    blocked, gta02_wlan_rfkill_cb);
	mutex_lock(&gta02_wlan_rfkill_lock);
	if (gta02_wlan_rfkill_cb)
		res = gta02_wlan_rfkill_cb(gta02_wlan_rfkill_user, !blocked);
	if (!res)
		gta02_wlan_rfkill_on = !blocked;
	mutex_unlock(&gta02_wlan_rfkill_lock);
	return res;
}

static const struct rfkill_ops gta02_wlan_rfkill_ops = {
       .set_block = gta02_wlan_set_radio_block,
};

/* ----- Initialization/removal -------------------------------------------- */


static int __init gta02_wlan_probe(struct platform_device *pdev)
{
	/* default-on for now */
	const int default_state = 1;
	struct rfkill *rfkill;
	int ret;

	dev_info(&pdev->dev, "starting\n");

	s3c2410_gpio_cfgpin(GTA02_GPIO_nWLAN_RESET, S3C2410_GPIO_OUTPUT);
	gta02_wlan_reset(1);
	gta02_wlan_reset(0);

	rfkill = rfkill_alloc("ar6000", &pdev->dev, RFKILL_TYPE_WLAN,
							&gta02_wlan_rfkill_ops, &pdev->dev);


	if (!rfkill) {
		dev_err(&pdev->dev, "Failed to allocate rfkill\n");
		return -ENOMEM;
	}
	
	rfkill_init_sw_state(rfkill, default_state);
	/*
	 * If the WLAN driver somehow managed to get activated before we're
	 * ready, the driver is now in an unknown state, which isn't something
	 * we're prepared to handle. This can't happen, so just fail hard.
	 */
	BUG_ON(gta02_wlan_rfkill_cb);
	gta02_wlan_rfkill_on = default_state;

	ret = rfkill_register(rfkill);
	if (ret) {
		rfkill_destroy(rfkill);
		dev_err(&pdev->dev, "Failed to register rfkill\n");
		return ret;
	}

	dev_set_drvdata(&pdev->dev, rfkill);

	return 0;
}

static int gta02_wlan_remove(struct platform_device *pdev)
{
	struct rfkill *rfkill = dev_get_drvdata(&pdev->dev);

	rfkill_destroy(rfkill);

	return 0;
}

static struct platform_driver gta02_wlan_driver = {
	.probe		= gta02_wlan_probe,
	.remove		= gta02_wlan_remove,
	.driver		= {
		.name		= "gta02-pm-wlan",
	},
};

static int __devinit gta02_wlan_init(void)
{
	return platform_driver_register(&gta02_wlan_driver);
}
module_init(gta02_wlan_init);

static void gta02_wlan_exit(void)
{
	platform_driver_unregister(&gta02_wlan_driver);
}
module_exit(gta02_wlan_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andy Green <andy@openmoko.com>");
MODULE_DESCRIPTION("Openmoko GTA02 WLAN power management");