button.c 13.6 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
/*
2
 *  button.c - ACPI Button Driver
Linus Torvalds's avatar
Linus Torvalds committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
 *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or (at
 *  your option) any later version.
 *
 *  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.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
25 26 27
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
28
#include <linux/input.h>
29
#include <linux/slab.h>
30
#include <linux/acpi.h>
31
#include <acpi/button.h>
Linus Torvalds's avatar
Linus Torvalds committed
32

33 34
#define PREFIX "ACPI: "

Linus Torvalds's avatar
Linus Torvalds committed
35
#define ACPI_BUTTON_CLASS		"button"
36 37 38
#define ACPI_BUTTON_FILE_INFO		"info"
#define ACPI_BUTTON_FILE_STATE		"state"
#define ACPI_BUTTON_TYPE_UNKNOWN	0x00
Linus Torvalds's avatar
Linus Torvalds committed
39 40 41
#define ACPI_BUTTON_NOTIFY_STATUS	0x80

#define ACPI_BUTTON_SUBCLASS_POWER	"power"
Len Brown's avatar
Len Brown committed
42
#define ACPI_BUTTON_HID_POWER		"PNP0C0C"
43
#define ACPI_BUTTON_DEVICE_NAME_POWER	"Power Button"
Linus Torvalds's avatar
Linus Torvalds committed
44 45 46 47
#define ACPI_BUTTON_TYPE_POWER		0x01

#define ACPI_BUTTON_SUBCLASS_SLEEP	"sleep"
#define ACPI_BUTTON_HID_SLEEP		"PNP0C0E"
48
#define ACPI_BUTTON_DEVICE_NAME_SLEEP	"Sleep Button"
Linus Torvalds's avatar
Linus Torvalds committed
49 50 51 52 53 54 55
#define ACPI_BUTTON_TYPE_SLEEP		0x03

#define ACPI_BUTTON_SUBCLASS_LID	"lid"
#define ACPI_BUTTON_HID_LID		"PNP0C0D"
#define ACPI_BUTTON_DEVICE_NAME_LID	"Lid Switch"
#define ACPI_BUTTON_TYPE_LID		0x05

56 57 58 59
#define ACPI_BUTTON_LID_INIT_IGNORE	0x00
#define ACPI_BUTTON_LID_INIT_OPEN	0x01
#define ACPI_BUTTON_LID_INIT_METHOD	0x02

Linus Torvalds's avatar
Linus Torvalds committed
60
#define _COMPONENT		ACPI_BUTTON_COMPONENT
61
ACPI_MODULE_NAME("button");
Linus Torvalds's avatar
Linus Torvalds committed
62

63
MODULE_AUTHOR("Paul Diefenbaugh");
64
MODULE_DESCRIPTION("ACPI Button Driver");
Linus Torvalds's avatar
Linus Torvalds committed
65 66
MODULE_LICENSE("GPL");

67 68 69 70 71 72 73 74 75 76
static const struct acpi_device_id button_device_ids[] = {
	{ACPI_BUTTON_HID_LID,    0},
	{ACPI_BUTTON_HID_SLEEP,  0},
	{ACPI_BUTTON_HID_SLEEPF, 0},
	{ACPI_BUTTON_HID_POWER,  0},
	{ACPI_BUTTON_HID_POWERF, 0},
	{"", 0},
};
MODULE_DEVICE_TABLE(acpi, button_device_ids);

Len Brown's avatar
Len Brown committed
77
static int acpi_button_add(struct acpi_device *device);
78
static int acpi_button_remove(struct acpi_device *device);
79
static void acpi_button_notify(struct acpi_device *device, u32 event);
Linus Torvalds's avatar
Linus Torvalds committed
80

81
#ifdef CONFIG_PM_SLEEP
82
static int acpi_button_suspend(struct device *dev);
83
static int acpi_button_resume(struct device *dev);
84
#else
85
#define acpi_button_suspend NULL
86
#define acpi_button_resume NULL
87
#endif
88
static SIMPLE_DEV_PM_OPS(acpi_button_pm, acpi_button_suspend, acpi_button_resume);
89

Linus Torvalds's avatar
Linus Torvalds committed
90
static struct acpi_driver acpi_button_driver = {
Len Brown's avatar
Len Brown committed
91
	.name = "button",
Len Brown's avatar
Len Brown committed
92
	.class = ACPI_BUTTON_CLASS,
93
	.ids = button_device_ids,
Len Brown's avatar
Len Brown committed
94 95 96
	.ops = {
		.add = acpi_button_add,
		.remove = acpi_button_remove,
97
		.notify = acpi_button_notify,
98
	},
99
	.drv.pm = &acpi_button_pm,
Linus Torvalds's avatar
Linus Torvalds committed
100 101 102
};

struct acpi_button {
103 104 105
	unsigned int type;
	struct input_dev *input;
	char phys[32];			/* for input device */
Len Brown's avatar
Len Brown committed
106
	unsigned long pushed;
107
	bool suspended;
Linus Torvalds's avatar
Linus Torvalds committed
108 109
};

110 111
static BLOCKING_NOTIFIER_HEAD(acpi_lid_notifier);
static struct acpi_device *lid_device;
112
static u8 lid_init_state = ACPI_BUTTON_LID_INIT_METHOD;
113

114 115 116 117
/* --------------------------------------------------------------------------
                              FS Interface (/proc)
   -------------------------------------------------------------------------- */

Len Brown's avatar
Len Brown committed
118
static struct proc_dir_entry *acpi_button_dir;
119
static struct proc_dir_entry *acpi_lid_dir;
Len Brown's avatar
Len Brown committed
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
static int acpi_lid_evaluate_state(struct acpi_device *device)
{
	unsigned long long lid_state;
	acpi_status status;

	status = acpi_evaluate_integer(device->handle, "_LID", NULL, &lid_state);
	if (ACPI_FAILURE(status))
		return -ENODEV;

	return lid_state ? 1 : 0;
}

static int acpi_lid_notify_state(struct acpi_device *device, int state)
{
	struct acpi_button *button = acpi_driver_data(device);
	int ret;

	/* input layer checks if event is redundant */
	input_report_switch(button->input, SW_LID, !state);
	input_sync(button->input);

	if (state)
		pm_wakeup_event(&device->dev, 0);

	ret = blocking_notifier_call_chain(&acpi_lid_notifier, state, device);
	if (ret == NOTIFY_DONE)
		ret = blocking_notifier_call_chain(&acpi_lid_notifier, state,
						   device);
	if (ret == NOTIFY_DONE || ret == NOTIFY_OK) {
		/*
		 * It is also regarded as success if the notifier_chain
		 * returns NOTIFY_OK or NOTIFY_DONE.
		 */
		ret = 0;
	}
	return ret;
}

159 160
static int acpi_button_state_seq_show(struct seq_file *seq, void *offset)
{
161
	struct acpi_device *device = seq->private;
162
	int state;
163

164
	state = acpi_lid_evaluate_state(device);
165
	seq_printf(seq, "state:      %s\n",
166
		   state < 0 ? "unsupported" : (state ? "open" : "closed"));
167
	return 0;
168 169 170 171
}

static int acpi_button_state_open_fs(struct inode *inode, struct file *file)
{
Al Viro's avatar
Al Viro committed
172
	return single_open(file, acpi_button_state_seq_show, PDE_DATA(inode));
173 174
}

175 176 177 178 179 180 181
static const struct file_operations acpi_button_state_fops = {
	.owner = THIS_MODULE,
	.open = acpi_button_state_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};
182

Len Brown's avatar
Len Brown committed
183
static int acpi_button_add_fs(struct acpi_device *device)
184
{
185
	struct acpi_button *button = acpi_driver_data(device);
Len Brown's avatar
Len Brown committed
186
	struct proc_dir_entry *entry = NULL;
187
	int ret = 0;
188

189 190 191 192 193 194 195
	/* procfs I/F for ACPI lid device only */
	if (button->type != ACPI_BUTTON_TYPE_LID)
		return 0;

	if (acpi_button_dir || acpi_lid_dir) {
		printk(KERN_ERR PREFIX "More than one Lid device found!\n");
		return -EEXIST;
196 197
	}

198 199 200
	/* create /proc/acpi/button */
	acpi_button_dir = proc_mkdir(ACPI_BUTTON_CLASS, acpi_root_dir);
	if (!acpi_button_dir)
201
		return -ENODEV;
202

203 204 205 206 207 208
	/* create /proc/acpi/button/lid */
	acpi_lid_dir = proc_mkdir(ACPI_BUTTON_SUBCLASS_LID, acpi_button_dir);
	if (!acpi_lid_dir) {
		ret = -ENODEV;
		goto remove_button_dir;
	}
209

210 211 212 213 214 215
	/* create /proc/acpi/button/lid/LID/ */
	acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device), acpi_lid_dir);
	if (!acpi_device_dir(device)) {
		ret = -ENODEV;
		goto remove_lid_dir;
	}
216

217 218 219 220 221 222 223
	/* create /proc/acpi/button/lid/LID/state */
	entry = proc_create_data(ACPI_BUTTON_FILE_STATE,
				 S_IRUGO, acpi_device_dir(device),
				 &acpi_button_state_fops, device);
	if (!entry) {
		ret = -ENODEV;
		goto remove_dev_dir;
224 225
	}

226 227 228 229 230 231 232 233 234
done:
	return ret;

remove_dev_dir:
	remove_proc_entry(acpi_device_bid(device),
			  acpi_lid_dir);
	acpi_device_dir(device) = NULL;
remove_lid_dir:
	remove_proc_entry(ACPI_BUTTON_SUBCLASS_LID, acpi_button_dir);
235
	acpi_lid_dir = NULL;
236 237
remove_button_dir:
	remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
238
	acpi_button_dir = NULL;
239
	goto done;
240 241
}

Len Brown's avatar
Len Brown committed
242
static int acpi_button_remove_fs(struct acpi_device *device)
243
{
244
	struct acpi_button *button = acpi_driver_data(device);
245

246 247
	if (button->type != ACPI_BUTTON_TYPE_LID)
		return 0;
248

249 250 251 252 253 254
	remove_proc_entry(ACPI_BUTTON_FILE_STATE,
			  acpi_device_dir(device));
	remove_proc_entry(acpi_device_bid(device),
			  acpi_lid_dir);
	acpi_device_dir(device) = NULL;
	remove_proc_entry(ACPI_BUTTON_SUBCLASS_LID, acpi_button_dir);
255
	acpi_lid_dir = NULL;
256
	remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
257
	acpi_button_dir = NULL;
258

259
	return 0;
260 261
}

Linus Torvalds's avatar
Linus Torvalds committed
262 263 264
/* --------------------------------------------------------------------------
                                Driver Interface
   -------------------------------------------------------------------------- */
265 266 267 268 269 270 271 272 273 274 275 276 277 278
int acpi_lid_notifier_register(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&acpi_lid_notifier, nb);
}
EXPORT_SYMBOL(acpi_lid_notifier_register);

int acpi_lid_notifier_unregister(struct notifier_block *nb)
{
	return blocking_notifier_chain_unregister(&acpi_lid_notifier, nb);
}
EXPORT_SYMBOL(acpi_lid_notifier_unregister);

int acpi_lid_open(void)
{
279 280 281
	if (!lid_device)
		return -ENODEV;

282
	return acpi_lid_evaluate_state(lid_device);
283 284 285
}
EXPORT_SYMBOL(acpi_lid_open);

286
static int acpi_lid_update_state(struct acpi_device *device)
287
{
288
	int state;
289

290 291 292
	state = acpi_lid_evaluate_state(device);
	if (state < 0)
		return state;
293

294
	return acpi_lid_notify_state(device, state);
295
}
Linus Torvalds's avatar
Linus Torvalds committed
296

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
static void acpi_lid_initialize_state(struct acpi_device *device)
{
	switch (lid_init_state) {
	case ACPI_BUTTON_LID_INIT_OPEN:
		(void)acpi_lid_notify_state(device, 1);
		break;
	case ACPI_BUTTON_LID_INIT_METHOD:
		(void)acpi_lid_update_state(device);
		break;
	case ACPI_BUTTON_LID_INIT_IGNORE:
	default:
		break;
	}
}

312
static void acpi_button_notify(struct acpi_device *device, u32 event)
Linus Torvalds's avatar
Linus Torvalds committed
313
{
314
	struct acpi_button *button = acpi_driver_data(device);
315
	struct input_dev *input;
Linus Torvalds's avatar
Linus Torvalds committed
316 317

	switch (event) {
318 319 320
	case ACPI_FIXED_HARDWARE_EVENT:
		event = ACPI_BUTTON_NOTIFY_STATUS;
		/* fall through */
Linus Torvalds's avatar
Linus Torvalds committed
321
	case ACPI_BUTTON_NOTIFY_STATUS:
322 323
		input = button->input;
		if (button->type == ACPI_BUTTON_TYPE_LID) {
324
			acpi_lid_update_state(device);
325
		} else {
326 327 328 329 330
			int keycode;

			pm_wakeup_event(&device->dev, 0);
			if (button->suspended)
				break;
331

332 333
			keycode = test_bit(KEY_SLEEP, input->keybit) ?
						KEY_SLEEP : KEY_POWER;
334 335 336
			input_report_key(input, keycode, 1);
			input_sync(input);
			input_report_key(input, keycode, 0);
337
			input_sync(input);
338

339 340 341 342
			acpi_bus_generate_netlink_event(
					device->pnp.device_class,
					dev_name(&device->dev),
					event, ++button->pushed);
343
		}
Linus Torvalds's avatar
Linus Torvalds committed
344 345 346
		break;
	default:
		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
Len Brown's avatar
Len Brown committed
347
				  "Unsupported event [0x%x]\n", event));
Linus Torvalds's avatar
Linus Torvalds committed
348 349 350 351
		break;
	}
}

352
#ifdef CONFIG_PM_SLEEP
353 354 355 356 357 358 359 360 361
static int acpi_button_suspend(struct device *dev)
{
	struct acpi_device *device = to_acpi_device(dev);
	struct acpi_button *button = acpi_driver_data(device);

	button->suspended = true;
	return 0;
}

362
static int acpi_button_resume(struct device *dev)
363
{
364
	struct acpi_device *device = to_acpi_device(dev);
365
	struct acpi_button *button = acpi_driver_data(device);
366

367
	button->suspended = false;
368 369
	if (button->type == ACPI_BUTTON_TYPE_LID)
		acpi_lid_initialize_state(device);
370 371
	return 0;
}
372
#endif
373

374 375 376 377
static int acpi_button_add(struct acpi_device *device)
{
	struct acpi_button *button;
	struct input_dev *input;
378 379
	const char *hid = acpi_device_hid(device);
	char *name, *class;
380
	int error;
Linus Torvalds's avatar
Linus Torvalds committed
381

382
	button = kzalloc(sizeof(struct acpi_button), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
383
	if (!button)
384
		return -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
385

386
	device->driver_data = button;
Linus Torvalds's avatar
Linus Torvalds committed
387

388 389 390 391 392 393
	button->input = input = input_allocate_device();
	if (!input) {
		error = -ENOMEM;
		goto err_free_button;
	}

394 395 396
	name = acpi_device_name(device);
	class = acpi_device_class(device);

397 398
	if (!strcmp(hid, ACPI_BUTTON_HID_POWER) ||
	    !strcmp(hid, ACPI_BUTTON_HID_POWERF)) {
Linus Torvalds's avatar
Linus Torvalds committed
399
		button->type = ACPI_BUTTON_TYPE_POWER;
400 401
		strcpy(name, ACPI_BUTTON_DEVICE_NAME_POWER);
		sprintf(class, "%s/%s",
Linus Torvalds's avatar
Linus Torvalds committed
402
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_POWER);
403 404
	} else if (!strcmp(hid, ACPI_BUTTON_HID_SLEEP) ||
		   !strcmp(hid, ACPI_BUTTON_HID_SLEEPF)) {
Linus Torvalds's avatar
Linus Torvalds committed
405
		button->type = ACPI_BUTTON_TYPE_SLEEP;
406 407
		strcpy(name, ACPI_BUTTON_DEVICE_NAME_SLEEP);
		sprintf(class, "%s/%s",
Linus Torvalds's avatar
Linus Torvalds committed
408
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_SLEEP);
409
	} else if (!strcmp(hid, ACPI_BUTTON_HID_LID)) {
Linus Torvalds's avatar
Linus Torvalds committed
410
		button->type = ACPI_BUTTON_TYPE_LID;
411 412
		strcpy(name, ACPI_BUTTON_DEVICE_NAME_LID);
		sprintf(class, "%s/%s",
Linus Torvalds's avatar
Linus Torvalds committed
413
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID);
Len Brown's avatar
Len Brown committed
414
	} else {
415
		printk(KERN_ERR PREFIX "Unsupported hid [%s]\n", hid);
416 417
		error = -ENODEV;
		goto err_free_input;
Linus Torvalds's avatar
Linus Torvalds committed
418 419
	}

420 421 422 423
	error = acpi_button_add_fs(device);
	if (error)
		goto err_free_input;

424
	snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid);
425

426
	input->name = name;
427 428 429
	input->phys = button->phys;
	input->id.bustype = BUS_HOST;
	input->id.product = button->type;
430
	input->dev.parent = &device->dev;
431

Linus Torvalds's avatar
Linus Torvalds committed
432
	switch (button->type) {
433
	case ACPI_BUTTON_TYPE_POWER:
434
		input_set_capability(input, EV_KEY, KEY_POWER);
Linus Torvalds's avatar
Linus Torvalds committed
435
		break;
436 437

	case ACPI_BUTTON_TYPE_SLEEP:
438
		input_set_capability(input, EV_KEY, KEY_SLEEP);
Linus Torvalds's avatar
Linus Torvalds committed
439
		break;
440 441

	case ACPI_BUTTON_TYPE_LID:
442
		input_set_capability(input, EV_SW, SW_LID);
Linus Torvalds's avatar
Linus Torvalds committed
443 444 445
		break;
	}

446 447
	error = input_register_device(input);
	if (error)
448
		goto err_remove_fs;
449
	if (button->type == ACPI_BUTTON_TYPE_LID) {
450
		acpi_lid_initialize_state(device);
451 452 453 454 455 456
		/*
		 * This assumes there's only one lid device, or if there are
		 * more we only care about the last one...
		 */
		lid_device = device;
	}
Linus Torvalds's avatar
Linus Torvalds committed
457

458
	printk(KERN_INFO PREFIX "%s [%s]\n", name, acpi_device_bid(device));
459
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
460

461 462 463 464 465 466 467
 err_remove_fs:
	acpi_button_remove_fs(device);
 err_free_input:
	input_free_device(input);
 err_free_button:
	kfree(button);
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
468 469
}

470
static int acpi_button_remove(struct acpi_device *device)
Linus Torvalds's avatar
Linus Torvalds committed
471
{
472
	struct acpi_button *button = acpi_driver_data(device);
Linus Torvalds's avatar
Linus Torvalds committed
473

Len Brown's avatar
Len Brown committed
474
	acpi_button_remove_fs(device);
475
	input_unregister_device(button->input);
Linus Torvalds's avatar
Linus Torvalds committed
476
	kfree(button);
477
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
478 479
}

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
static int param_set_lid_init_state(const char *val, struct kernel_param *kp)
{
	int result = 0;

	if (!strncmp(val, "open", sizeof("open") - 1)) {
		lid_init_state = ACPI_BUTTON_LID_INIT_OPEN;
		pr_info("Notify initial lid state as open\n");
	} else if (!strncmp(val, "method", sizeof("method") - 1)) {
		lid_init_state = ACPI_BUTTON_LID_INIT_METHOD;
		pr_info("Notify initial lid state with _LID return value\n");
	} else if (!strncmp(val, "ignore", sizeof("ignore") - 1)) {
		lid_init_state = ACPI_BUTTON_LID_INIT_IGNORE;
		pr_info("Do not notify initial lid state\n");
	} else
		result = -EINVAL;
	return result;
}

static int param_get_lid_init_state(char *buffer, struct kernel_param *kp)
{
	switch (lid_init_state) {
	case ACPI_BUTTON_LID_INIT_OPEN:
		return sprintf(buffer, "open");
	case ACPI_BUTTON_LID_INIT_METHOD:
		return sprintf(buffer, "method");
	case ACPI_BUTTON_LID_INIT_IGNORE:
		return sprintf(buffer, "ignore");
	default:
		return sprintf(buffer, "invalid");
	}
	return 0;
}

module_param_call(lid_init_state,
		  param_set_lid_init_state, param_get_lid_init_state,
		  NULL, 0644);
MODULE_PARM_DESC(lid_init_state, "Behavior for reporting LID initial state");

518
module_acpi_driver(acpi_button_driver);