button.c 14.1 KB
Newer Older
L
Linus Torvalds 已提交
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
/*
 *  acpi_button.c - ACPI Button Driver ($Revision: 30 $)
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
29 30 31
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
32
#include <linux/input.h>
L
Linus Torvalds 已提交
33 34 35 36 37
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>

#define ACPI_BUTTON_COMPONENT		0x00080000
#define ACPI_BUTTON_CLASS		"button"
38 39 40
#define ACPI_BUTTON_FILE_INFO		"info"
#define ACPI_BUTTON_FILE_STATE		"state"
#define ACPI_BUTTON_TYPE_UNKNOWN	0x00
L
Linus Torvalds 已提交
41 42 43
#define ACPI_BUTTON_NOTIFY_STATUS	0x80

#define ACPI_BUTTON_SUBCLASS_POWER	"power"
L
Len Brown 已提交
44
#define ACPI_BUTTON_HID_POWER		"PNP0C0C"
L
Linus Torvalds 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
#define ACPI_BUTTON_DEVICE_NAME_POWER	"Power Button (CM)"
#define ACPI_BUTTON_DEVICE_NAME_POWERF	"Power Button (FF)"
#define ACPI_BUTTON_TYPE_POWER		0x01
#define ACPI_BUTTON_TYPE_POWERF		0x02

#define ACPI_BUTTON_SUBCLASS_SLEEP	"sleep"
#define ACPI_BUTTON_HID_SLEEP		"PNP0C0E"
#define ACPI_BUTTON_DEVICE_NAME_SLEEP	"Sleep Button (CM)"
#define ACPI_BUTTON_DEVICE_NAME_SLEEPF	"Sleep Button (FF)"
#define ACPI_BUTTON_TYPE_SLEEP		0x03
#define ACPI_BUTTON_TYPE_SLEEPF		0x04

#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

#define _COMPONENT		ACPI_BUTTON_COMPONENT
63
ACPI_MODULE_NAME("button");
L
Linus Torvalds 已提交
64

65
MODULE_AUTHOR("Paul Diefenbaugh");
66
MODULE_DESCRIPTION("ACPI Button Driver");
L
Linus Torvalds 已提交
67 68
MODULE_LICENSE("GPL");

L
Len Brown 已提交
69 70
static int acpi_button_add(struct acpi_device *device);
static int acpi_button_remove(struct acpi_device *device, int type);
71 72
static int acpi_button_info_open_fs(struct inode *inode, struct file *file);
static int acpi_button_state_open_fs(struct inode *inode, struct file *file);
L
Linus Torvalds 已提交
73 74

static struct acpi_driver acpi_button_driver = {
L
Len Brown 已提交
75
	.name = "button",
L
Len Brown 已提交
76
	.class = ACPI_BUTTON_CLASS,
77
	.ids = "button_power,button_sleep,PNP0C0D,PNP0C0C,PNP0C0E",
L
Len Brown 已提交
78 79 80
	.ops = {
		.add = acpi_button_add,
		.remove = acpi_button_remove,
81
	},
L
Linus Torvalds 已提交
82 83 84
};

struct acpi_button {
L
Len Brown 已提交
85
	struct acpi_device *device;	/* Fixed button kludge */
86 87 88
	unsigned int type;
	struct input_dev *input;
	char phys[32];			/* for input device */
L
Len Brown 已提交
89
	unsigned long pushed;
L
Linus Torvalds 已提交
90 91
};

92
static const struct file_operations acpi_button_info_fops = {
L
Len Brown 已提交
93 94 95 96
	.open = acpi_button_info_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
97 98
};

99
static const struct file_operations acpi_button_state_fops = {
L
Len Brown 已提交
100 101 102 103
	.open = acpi_button_state_open_fs,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
104
};
L
Len Brown 已提交
105

106 107 108 109
/* --------------------------------------------------------------------------
                              FS Interface (/proc)
   -------------------------------------------------------------------------- */

L
Len Brown 已提交
110
static struct proc_dir_entry *acpi_button_dir;
111 112 113

static int acpi_button_info_seq_show(struct seq_file *seq, void *offset)
{
114
	struct acpi_button *button = seq->private;
115 116

	if (!button || !button->device)
117
		return 0;
118

L
Len Brown 已提交
119 120
	seq_printf(seq, "type:                    %s\n",
		   acpi_device_name(button->device));
121

122
	return 0;
123 124 125 126 127 128
}

static int acpi_button_info_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_button_info_seq_show, PDE(inode)->data);
}
L
Len Brown 已提交
129

130 131
static int acpi_button_state_seq_show(struct seq_file *seq, void *offset)
{
132
	struct acpi_button *button = seq->private;
L
Len Brown 已提交
133 134
	acpi_status status;
	unsigned long state;
135 136

	if (!button || !button->device)
137
		return 0;
138

139
	status = acpi_evaluate_integer(button->device->handle, "_LID", NULL, &state);
140 141 142
	seq_printf(seq, "state:      %s\n",
		   ACPI_FAILURE(status) ? "unsupported" :
			(state ? "open" : "closed"));
143
	return 0;
144 145 146 147 148 149 150 151 152 153 154
}

static int acpi_button_state_open_fs(struct inode *inode, struct file *file)
{
	return single_open(file, acpi_button_state_seq_show, PDE(inode)->data);
}

static struct proc_dir_entry *acpi_power_dir;
static struct proc_dir_entry *acpi_sleep_dir;
static struct proc_dir_entry *acpi_lid_dir;

L
Len Brown 已提交
155
static int acpi_button_add_fs(struct acpi_device *device)
156
{
L
Len Brown 已提交
157
	struct proc_dir_entry *entry = NULL;
158
	struct acpi_button *button;
159 160

	if (!device || !acpi_driver_data(device))
161
		return -EINVAL;
162 163 164 165 166 167 168

	button = acpi_driver_data(device);

	switch (button->type) {
	case ACPI_BUTTON_TYPE_POWER:
	case ACPI_BUTTON_TYPE_POWERF:
		if (!acpi_power_dir)
L
Len Brown 已提交
169 170
			acpi_power_dir = proc_mkdir(ACPI_BUTTON_SUBCLASS_POWER,
						    acpi_button_dir);
171 172 173 174 175
		entry = acpi_power_dir;
		break;
	case ACPI_BUTTON_TYPE_SLEEP:
	case ACPI_BUTTON_TYPE_SLEEPF:
		if (!acpi_sleep_dir)
L
Len Brown 已提交
176 177
			acpi_sleep_dir = proc_mkdir(ACPI_BUTTON_SUBCLASS_SLEEP,
						    acpi_button_dir);
178 179 180 181
		entry = acpi_sleep_dir;
		break;
	case ACPI_BUTTON_TYPE_LID:
		if (!acpi_lid_dir)
L
Len Brown 已提交
182 183
			acpi_lid_dir = proc_mkdir(ACPI_BUTTON_SUBCLASS_LID,
						  acpi_button_dir);
184 185 186 187 188
		entry = acpi_lid_dir;
		break;
	}

	if (!entry)
189
		return -ENODEV;
190 191 192 193
	entry->owner = THIS_MODULE;

	acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device), entry);
	if (!acpi_device_dir(device))
194
		return -ENODEV;
195 196 197 198
	acpi_device_dir(device)->owner = THIS_MODULE;

	/* 'info' [R] */
	entry = create_proc_entry(ACPI_BUTTON_FILE_INFO,
L
Len Brown 已提交
199
				  S_IRUGO, acpi_device_dir(device));
200
	if (!entry)
201
		return -ENODEV;
202 203 204 205 206 207 208 209 210
	else {
		entry->proc_fops = &acpi_button_info_fops;
		entry->data = acpi_driver_data(device);
		entry->owner = THIS_MODULE;
	}

	/* show lid state [R] */
	if (button->type == ACPI_BUTTON_TYPE_LID) {
		entry = create_proc_entry(ACPI_BUTTON_FILE_STATE,
L
Len Brown 已提交
211
					  S_IRUGO, acpi_device_dir(device));
212
		if (!entry)
213
			return -ENODEV;
214 215 216 217 218 219 220
		else {
			entry->proc_fops = &acpi_button_state_fops;
			entry->data = acpi_driver_data(device);
			entry->owner = THIS_MODULE;
		}
	}

221
	return 0;
222 223
}

L
Len Brown 已提交
224
static int acpi_button_remove_fs(struct acpi_device *device)
225
{
226
	struct acpi_button *button = acpi_driver_data(device);
227 228 229 230

	if (acpi_device_dir(device)) {
		if (button->type == ACPI_BUTTON_TYPE_LID)
			remove_proc_entry(ACPI_BUTTON_FILE_STATE,
L
Len Brown 已提交
231
					  acpi_device_dir(device));
232
		remove_proc_entry(ACPI_BUTTON_FILE_INFO,
L
Len Brown 已提交
233
				  acpi_device_dir(device));
234 235

		remove_proc_entry(acpi_device_bid(device),
L
Len Brown 已提交
236
				  acpi_device_dir(device)->parent);
237 238 239
		acpi_device_dir(device) = NULL;
	}

240
	return 0;
241 242
}

L
Linus Torvalds 已提交
243 244 245 246
/* --------------------------------------------------------------------------
                                Driver Interface
   -------------------------------------------------------------------------- */

L
Len Brown 已提交
247
static void acpi_button_notify(acpi_handle handle, u32 event, void *data)
L
Linus Torvalds 已提交
248
{
249 250
	struct acpi_button *button = data;
	struct input_dev *input;
L
Linus Torvalds 已提交
251 252

	if (!button || !button->device)
253
		return;
L
Linus Torvalds 已提交
254 255 256

	switch (event) {
	case ACPI_BUTTON_NOTIFY_STATUS:
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
		input = button->input;

		if (button->type == ACPI_BUTTON_TYPE_LID) {
			struct acpi_handle *handle = button->device->handle;
			unsigned long state;

			if (!ACPI_FAILURE(acpi_evaluate_integer(handle, "_LID",
								NULL, &state)))
				input_report_switch(input, SW_LID, !state);

		} else {
			int keycode = test_bit(KEY_SLEEP, input->keybit) ?
						KEY_SLEEP : KEY_POWER;

			input_report_key(input, keycode, 1);
			input_sync(input);
			input_report_key(input, keycode, 0);
		}
		input_sync(input);

L
Len Brown 已提交
277 278
		acpi_bus_generate_event(button->device, event,
					++button->pushed);
L
Linus Torvalds 已提交
279 280 281
		break;
	default:
		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
L
Len Brown 已提交
282
				  "Unsupported event [0x%x]\n", event));
L
Linus Torvalds 已提交
283 284 285
		break;
	}

286
	return;
L
Linus Torvalds 已提交
287 288
}

L
Len Brown 已提交
289
static acpi_status acpi_button_notify_fixed(void *data)
L
Linus Torvalds 已提交
290
{
291
	struct acpi_button *button = data;
L
Linus Torvalds 已提交
292

293
	if (!button)
294
		return AE_BAD_PARAMETER;
L
Linus Torvalds 已提交
295

296
	acpi_button_notify(button->device->handle, ACPI_BUTTON_NOTIFY_STATUS, button);
L
Linus Torvalds 已提交
297

298
	return AE_OK;
L
Linus Torvalds 已提交
299 300
}

301
static int acpi_button_install_notify_handlers(struct acpi_button *button)
L
Linus Torvalds 已提交
302
{
303
	acpi_status status;
L
Linus Torvalds 已提交
304

305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
	switch (button->type) {
	case ACPI_BUTTON_TYPE_POWERF:
		status =
		    acpi_install_fixed_event_handler(ACPI_EVENT_POWER_BUTTON,
						     acpi_button_notify_fixed,
						     button);
		break;
	case ACPI_BUTTON_TYPE_SLEEPF:
		status =
		    acpi_install_fixed_event_handler(ACPI_EVENT_SLEEP_BUTTON,
						     acpi_button_notify_fixed,
						     button);
		break;
	default:
		status = acpi_install_notify_handler(button->device->handle,
						     ACPI_DEVICE_NOTIFY,
						     acpi_button_notify,
						     button);
		break;
	}

	return ACPI_FAILURE(status) ? -ENODEV : 0;
}

static void acpi_button_remove_notify_handlers(struct acpi_button *button)
{
	switch (button->type) {
	case ACPI_BUTTON_TYPE_POWERF:
		acpi_remove_fixed_event_handler(ACPI_EVENT_POWER_BUTTON,
						acpi_button_notify_fixed);
		break;
	case ACPI_BUTTON_TYPE_SLEEPF:
		acpi_remove_fixed_event_handler(ACPI_EVENT_SLEEP_BUTTON,
						acpi_button_notify_fixed);
		break;
	default:
		acpi_remove_notify_handler(button->device->handle,
					   ACPI_DEVICE_NOTIFY,
					   acpi_button_notify);
		break;
	}
}

static int acpi_button_add(struct acpi_device *device)
{
	int error;
	struct acpi_button *button;
	struct input_dev *input;
L
Linus Torvalds 已提交
353 354

	if (!device)
355
		return -EINVAL;
L
Linus Torvalds 已提交
356

357
	button = kzalloc(sizeof(struct acpi_button), GFP_KERNEL);
L
Linus Torvalds 已提交
358
	if (!button)
359
		return -ENOMEM;
L
Linus Torvalds 已提交
360 361 362 363

	button->device = device;
	acpi_driver_data(device) = button;

364 365 366 367 368 369
	button->input = input = input_allocate_device();
	if (!input) {
		error = -ENOMEM;
		goto err_free_button;
	}

L
Linus Torvalds 已提交
370 371 372 373 374 375
	/*
	 * Determine the button type (via hid), as fixed-feature buttons
	 * need to be handled a bit differently than generic-space.
	 */
	if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_POWER)) {
		button->type = ACPI_BUTTON_TYPE_POWER;
L
Len Brown 已提交
376 377
		strcpy(acpi_device_name(device), ACPI_BUTTON_DEVICE_NAME_POWER);
		sprintf(acpi_device_class(device), "%s/%s",
L
Linus Torvalds 已提交
378
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_POWER);
L
Len Brown 已提交
379
	} else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_POWERF)) {
L
Linus Torvalds 已提交
380 381
		button->type = ACPI_BUTTON_TYPE_POWERF;
		strcpy(acpi_device_name(device),
L
Len Brown 已提交
382 383
		       ACPI_BUTTON_DEVICE_NAME_POWERF);
		sprintf(acpi_device_class(device), "%s/%s",
L
Linus Torvalds 已提交
384
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_POWER);
L
Len Brown 已提交
385
	} else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_SLEEP)) {
L
Linus Torvalds 已提交
386
		button->type = ACPI_BUTTON_TYPE_SLEEP;
L
Len Brown 已提交
387 388
		strcpy(acpi_device_name(device), ACPI_BUTTON_DEVICE_NAME_SLEEP);
		sprintf(acpi_device_class(device), "%s/%s",
L
Linus Torvalds 已提交
389
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_SLEEP);
L
Len Brown 已提交
390
	} else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_SLEEPF)) {
L
Linus Torvalds 已提交
391 392
		button->type = ACPI_BUTTON_TYPE_SLEEPF;
		strcpy(acpi_device_name(device),
L
Len Brown 已提交
393 394
		       ACPI_BUTTON_DEVICE_NAME_SLEEPF);
		sprintf(acpi_device_class(device), "%s/%s",
L
Linus Torvalds 已提交
395
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_SLEEP);
L
Len Brown 已提交
396
	} else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_LID)) {
L
Linus Torvalds 已提交
397
		button->type = ACPI_BUTTON_TYPE_LID;
L
Len Brown 已提交
398 399
		strcpy(acpi_device_name(device), ACPI_BUTTON_DEVICE_NAME_LID);
		sprintf(acpi_device_class(device), "%s/%s",
L
Linus Torvalds 已提交
400
			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID);
L
Len Brown 已提交
401
	} else {
402 403
		printk(KERN_ERR PREFIX "Unsupported hid [%s]\n",
			    acpi_device_hid(device));
404 405
		error = -ENODEV;
		goto err_free_input;
L
Linus Torvalds 已提交
406 407
	}

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
	error = acpi_button_add_fs(device);
	if (error)
		goto err_free_input;

	error = acpi_button_install_notify_handlers(button);
	if (error)
		goto err_remove_fs;

	snprintf(button->phys, sizeof(button->phys),
		 "%s/button/input0", acpi_device_hid(device));

	input->name = acpi_device_name(device);
	input->phys = button->phys;
	input->id.bustype = BUS_HOST;
	input->id.product = button->type;
423

L
Linus Torvalds 已提交
424
	switch (button->type) {
425
	case ACPI_BUTTON_TYPE_POWER:
L
Linus Torvalds 已提交
426
	case ACPI_BUTTON_TYPE_POWERF:
427 428
		input->evbit[0] = BIT(EV_KEY);
		set_bit(KEY_POWER, input->keybit);
L
Linus Torvalds 已提交
429
		break;
430 431

	case ACPI_BUTTON_TYPE_SLEEP:
L
Linus Torvalds 已提交
432
	case ACPI_BUTTON_TYPE_SLEEPF:
433 434
		input->evbit[0] = BIT(EV_KEY);
		set_bit(KEY_SLEEP, input->keybit);
L
Linus Torvalds 已提交
435
		break;
436 437 438 439

	case ACPI_BUTTON_TYPE_LID:
		input->evbit[0] = BIT(EV_SW);
		set_bit(SW_LID, input->swbit);
L
Linus Torvalds 已提交
440 441 442
		break;
	}

443 444 445
	error = input_register_device(input);
	if (error)
		goto err_remove_handlers;
L
Linus Torvalds 已提交
446 447 448

	if (device->wakeup.flags.valid) {
		/* Button's GPE is run-wake GPE */
L
Len Brown 已提交
449 450 451 452 453
		acpi_set_gpe_type(device->wakeup.gpe_device,
				  device->wakeup.gpe_number,
				  ACPI_GPE_TYPE_WAKE_RUN);
		acpi_enable_gpe(device->wakeup.gpe_device,
				device->wakeup.gpe_number, ACPI_NOT_ISR);
L
Linus Torvalds 已提交
454 455 456
		device->wakeup.state.enabled = 1;
	}

L
Len Brown 已提交
457 458
	printk(KERN_INFO PREFIX "%s [%s]\n",
	       acpi_device_name(device), acpi_device_bid(device));
L
Linus Torvalds 已提交
459

460
	return 0;
L
Linus Torvalds 已提交
461

462 463 464 465 466 467 468 469 470
 err_remove_handlers:
	acpi_button_remove_notify_handlers(button);
 err_remove_fs:
	acpi_button_remove_fs(device);
 err_free_input:
	input_free_device(input);
 err_free_button:
	kfree(button);
	return error;
L
Linus Torvalds 已提交
471 472
}

L
Len Brown 已提交
473
static int acpi_button_remove(struct acpi_device *device, int type)
L
Linus Torvalds 已提交
474
{
475
	struct acpi_button *button;
L
Linus Torvalds 已提交
476 477

	if (!device || !acpi_driver_data(device))
478
		return -EINVAL;
L
Linus Torvalds 已提交
479 480 481

	button = acpi_driver_data(device);

482
	acpi_button_remove_notify_handlers(button);
L
Len Brown 已提交
483
	acpi_button_remove_fs(device);
484
	input_unregister_device(button->input);
L
Linus Torvalds 已提交
485 486
	kfree(button);

487
	return 0;
L
Linus Torvalds 已提交
488 489
}

L
Len Brown 已提交
490
static int __init acpi_button_init(void)
L
Linus Torvalds 已提交
491
{
492
	int result;
L
Linus Torvalds 已提交
493

494 495
	acpi_button_dir = proc_mkdir(ACPI_BUTTON_CLASS, acpi_root_dir);
	if (!acpi_button_dir)
496
		return -ENODEV;
497
	acpi_button_dir->owner = THIS_MODULE;
L
Linus Torvalds 已提交
498 499
	result = acpi_bus_register_driver(&acpi_button_driver);
	if (result < 0) {
500
		remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
501
		return -ENODEV;
L
Linus Torvalds 已提交
502 503
	}

504
	return 0;
L
Linus Torvalds 已提交
505 506
}

L
Len Brown 已提交
507
static void __exit acpi_button_exit(void)
L
Linus Torvalds 已提交
508 509 510
{
	acpi_bus_unregister_driver(&acpi_button_driver);

L
Len Brown 已提交
511
	if (acpi_power_dir)
512 513 514 515 516 517
		remove_proc_entry(ACPI_BUTTON_SUBCLASS_POWER, acpi_button_dir);
	if (acpi_sleep_dir)
		remove_proc_entry(ACPI_BUTTON_SUBCLASS_SLEEP, acpi_button_dir);
	if (acpi_lid_dir)
		remove_proc_entry(ACPI_BUTTON_SUBCLASS_LID, acpi_button_dir);
	remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
L
Linus Torvalds 已提交
518 519 520 521
}

module_init(acpi_button_init);
module_exit(acpi_button_exit);