• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/components/opensource/linux/linux-2.6.36/drivers/platform/x86/
1/*
2 *  ideapad_acpi.c - Lenovo IdeaPad ACPI Extras
3 *
4 *  Copyright �� 2010 Intel Corporation
5 *  Copyright �� 2010 David Woodhouse <dwmw2@infradead.org>
6 *
7 *  This program is free software; you can redistribute it and/or modify
8 *  it under the terms of the GNU General Public License as published by
9 *  the Free Software Foundation; either version 2 of the License, or
10 *  (at your option) any later version.
11 *
12 *  This program is distributed in the hope that it will be useful,
13 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 *  GNU General Public License for more details.
16 *
17 *  You should have received a copy of the GNU General Public License
18 *  along with this program; if not, write to the Free Software
19 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 *  02110-1301, USA.
21 */
22
23#include <linux/kernel.h>
24#include <linux/module.h>
25#include <linux/init.h>
26#include <linux/types.h>
27#include <acpi/acpi_bus.h>
28#include <acpi/acpi_drivers.h>
29#include <linux/rfkill.h>
30
31#define IDEAPAD_DEV_CAMERA	0
32#define IDEAPAD_DEV_WLAN	1
33#define IDEAPAD_DEV_BLUETOOTH	2
34#define IDEAPAD_DEV_3G		3
35#define IDEAPAD_DEV_KILLSW	4
36
37struct ideapad_private {
38	struct rfkill *rfk[5];
39};
40
41static struct {
42	char *name;
43	int type;
44} ideapad_rfk_data[] = {
45	/* camera has no rfkill */
46	{ "ideapad_wlan",	RFKILL_TYPE_WLAN },
47	{ "ideapad_bluetooth",	RFKILL_TYPE_BLUETOOTH },
48	{ "ideapad_3g",		RFKILL_TYPE_WWAN },
49	{ "ideapad_killsw",	RFKILL_TYPE_WLAN }
50};
51
52static int ideapad_dev_exists(int device)
53{
54	acpi_status status;
55	union acpi_object in_param;
56	struct acpi_object_list input = { 1, &in_param };
57	struct acpi_buffer output;
58	union acpi_object out_obj;
59
60	output.length = sizeof(out_obj);
61	output.pointer = &out_obj;
62
63	in_param.type = ACPI_TYPE_INTEGER;
64	in_param.integer.value = device + 1;
65
66	status = acpi_evaluate_object(NULL, "\\_SB_.DECN", &input, &output);
67	if (ACPI_FAILURE(status)) {
68		printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method failed %d. Is this an IdeaPAD?\n", status);
69		return -ENODEV;
70	}
71	if (out_obj.type != ACPI_TYPE_INTEGER) {
72		printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method returned unexpected type\n");
73		return -ENODEV;
74	}
75	return out_obj.integer.value;
76}
77
78static int ideapad_dev_get_state(int device)
79{
80	acpi_status status;
81	union acpi_object in_param;
82	struct acpi_object_list input = { 1, &in_param };
83	struct acpi_buffer output;
84	union acpi_object out_obj;
85
86	output.length = sizeof(out_obj);
87	output.pointer = &out_obj;
88
89	in_param.type = ACPI_TYPE_INTEGER;
90	in_param.integer.value = device + 1;
91
92	status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output);
93	if (ACPI_FAILURE(status)) {
94		printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status);
95		return -ENODEV;
96	}
97	if (out_obj.type != ACPI_TYPE_INTEGER) {
98		printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n");
99		return -ENODEV;
100	}
101	return out_obj.integer.value;
102}
103
104static int ideapad_dev_set_state(int device, int state)
105{
106	acpi_status status;
107	union acpi_object in_params[2];
108	struct acpi_object_list input = { 2, in_params };
109
110	in_params[0].type = ACPI_TYPE_INTEGER;
111	in_params[0].integer.value = device + 1;
112	in_params[1].type = ACPI_TYPE_INTEGER;
113	in_params[1].integer.value = state;
114
115	status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL);
116	if (ACPI_FAILURE(status)) {
117		printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status);
118		return -ENODEV;
119	}
120	return 0;
121}
122static ssize_t show_ideapad_cam(struct device *dev,
123				struct device_attribute *attr,
124				char *buf)
125{
126	int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA);
127	if (state < 0)
128		return state;
129
130	return sprintf(buf, "%d\n", state);
131}
132
133static ssize_t store_ideapad_cam(struct device *dev,
134				 struct device_attribute *attr,
135				 const char *buf, size_t count)
136{
137	int ret, state;
138
139	if (!count)
140		return 0;
141	if (sscanf(buf, "%i", &state) != 1)
142		return -EINVAL;
143	ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, !!state);
144	if (ret < 0)
145		return ret;
146	return count;
147}
148
149static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
150
151static int ideapad_rfk_set(void *data, bool blocked)
152{
153	int device = (unsigned long)data;
154
155	if (device == IDEAPAD_DEV_KILLSW)
156		return -EINVAL;
157	return ideapad_dev_set_state(device, !blocked);
158}
159
160static struct rfkill_ops ideapad_rfk_ops = {
161	.set_block = ideapad_rfk_set,
162};
163
164static void ideapad_sync_rfk_state(struct acpi_device *adevice)
165{
166	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
167	int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
168	int i;
169
170	rfkill_set_hw_state(priv->rfk[IDEAPAD_DEV_KILLSW], hw_blocked);
171	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
172		if (priv->rfk[i])
173			rfkill_set_hw_state(priv->rfk[i], hw_blocked);
174	if (hw_blocked)
175		return;
176
177	for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
178		if (priv->rfk[i])
179			rfkill_set_sw_state(priv->rfk[i], !ideapad_dev_get_state(i));
180}
181
182static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
183{
184	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
185	int ret;
186
187	priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev-1].name, &adevice->dev,
188				      ideapad_rfk_data[dev-1].type, &ideapad_rfk_ops,
189				      (void *)(long)dev);
190	if (!priv->rfk[dev])
191		return -ENOMEM;
192
193	ret = rfkill_register(priv->rfk[dev]);
194	if (ret) {
195		rfkill_destroy(priv->rfk[dev]);
196		return ret;
197	}
198	return 0;
199}
200
201static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
202{
203	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
204
205	if (!priv->rfk[dev])
206		return;
207
208	rfkill_unregister(priv->rfk[dev]);
209	rfkill_destroy(priv->rfk[dev]);
210}
211
212static const struct acpi_device_id ideapad_device_ids[] = {
213	{ "VPC2004", 0},
214	{ "", 0},
215};
216MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
217
218static int ideapad_acpi_add(struct acpi_device *adevice)
219{
220	int i;
221	int devs_present[5];
222	struct ideapad_private *priv;
223
224	for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
225		devs_present[i] = ideapad_dev_exists(i);
226		if (devs_present[i] < 0)
227			return devs_present[i];
228	}
229
230	/* The hardware switch is always present */
231	devs_present[IDEAPAD_DEV_KILLSW] = 1;
232
233	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
234	if (!priv)
235		return -ENOMEM;
236
237	if (devs_present[IDEAPAD_DEV_CAMERA]) {
238		int ret = device_create_file(&adevice->dev, &dev_attr_camera_power);
239		if (ret) {
240			kfree(priv);
241			return ret;
242		}
243	}
244
245	dev_set_drvdata(&adevice->dev, priv);
246	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
247		if (!devs_present[i])
248			continue;
249
250		ideapad_register_rfkill(adevice, i);
251	}
252	ideapad_sync_rfk_state(adevice);
253	return 0;
254}
255
256static int ideapad_acpi_remove(struct acpi_device *adevice, int type)
257{
258	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
259	int i;
260
261	device_remove_file(&adevice->dev, &dev_attr_camera_power);
262
263	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++)
264		ideapad_unregister_rfkill(adevice, i);
265
266	dev_set_drvdata(&adevice->dev, NULL);
267	kfree(priv);
268	return 0;
269}
270
271static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
272{
273	ideapad_sync_rfk_state(adevice);
274}
275
276static struct acpi_driver ideapad_acpi_driver = {
277	.name = "ideapad_acpi",
278	.class = "IdeaPad",
279	.ids = ideapad_device_ids,
280	.ops.add = ideapad_acpi_add,
281	.ops.remove = ideapad_acpi_remove,
282	.ops.notify = ideapad_acpi_notify,
283	.owner = THIS_MODULE,
284};
285
286
287static int __init ideapad_acpi_module_init(void)
288{
289	acpi_bus_register_driver(&ideapad_acpi_driver);
290
291	return 0;
292}
293
294
295static void __exit ideapad_acpi_module_exit(void)
296{
297	acpi_bus_unregister_driver(&ideapad_acpi_driver);
298
299}
300
301MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
302MODULE_DESCRIPTION("IdeaPad ACPI Extras");
303MODULE_LICENSE("GPL");
304
305module_init(ideapad_acpi_module_init);
306module_exit(ideapad_acpi_module_exit);
307