1 /*
2  *  acpi_button.c - ACPI Button Driver ($Revision: 29 $)
3  *
4  *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
5  *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
6  *
7  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or (at
12  *  your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful, but
15  *  WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  *  General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License along
20  *  with this program; if not, write to the Free Software Foundation, Inc.,
21  *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
22  *
23  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24  */
25 
26 #include <linux/kernel.h>
27 #include <linux/module.h>
28 #include <linux/init.h>
29 #include <linux/types.h>
30 #include <linux/compatmac.h>
31 #include <linux/proc_fs.h>
32 #include <acpi/acpi_bus.h>
33 #include <acpi/acpi_drivers.h>
34 
35 
36 #define _COMPONENT		ACPI_BUTTON_COMPONENT
37 ACPI_MODULE_NAME		("acpi_button")
38 
39 MODULE_AUTHOR("Paul Diefenbaugh");
40 MODULE_DESCRIPTION(ACPI_BUTTON_DRIVER_NAME);
41 MODULE_LICENSE("GPL");
42 
43 #define PREFIX			"ACPI: "
44 
45 
46 static int acpi_button_add (struct acpi_device *device);
47 static int acpi_button_remove (struct acpi_device *device, int type);
48 
49 static struct acpi_driver acpi_button_driver = {
50 	.name =		ACPI_BUTTON_DRIVER_NAME,
51 	.class =	ACPI_BUTTON_CLASS,
52 	.ids =		"ACPI_FPB,ACPI_FSB,PNP0C0D,PNP0C0C,PNP0C0E",
53 	.ops =		{
54 				.add =		acpi_button_add,
55 				.remove =	acpi_button_remove,
56 			},
57 };
58 
59 struct acpi_button {
60 	acpi_handle		handle;
61 	struct acpi_device	*device;	/* Fixed button kludge */
62 	u8			type;
63 	unsigned long		pushed;
64 };
65 
66 
67 /* --------------------------------------------------------------------------
68                               FS Interface (/proc)
69    -------------------------------------------------------------------------- */
70 
71 static struct proc_dir_entry	*acpi_button_dir;
72 
73 static int
acpi_button_read_info(char * page,char ** start,off_t off,int count,int * eof,void * data)74 acpi_button_read_info (
75 	char			*page,
76 	char			**start,
77 	off_t			off,
78 	int 			count,
79 	int 			*eof,
80 	void			*data)
81 {
82 	struct acpi_button	*button = (struct acpi_button *) data;
83 	char			*p = page;
84 	int			len = 0;
85 
86 	ACPI_FUNCTION_TRACE("acpi_button_read_info");
87 
88 	if (!button || !button->device || (off != 0))
89 		goto end;
90 
91 	p += sprintf(p, "type:                    %s\n",
92 		acpi_device_name(button->device));
93 
94 end:
95 	len = (p - page);
96 	if (len <= off+count) *eof = 1;
97 	*start = page + off;
98 	len -= off;
99 	if (len>count) len = count;
100 	if (len<0) len = 0;
101 
102 	return_VALUE(len);
103 }
104 
105 static int
acpi_button_lid_read_state(char * page,char ** start,off_t off,int count,int * eof,void * data)106 acpi_button_lid_read_state(
107 	char			*page,
108 	char			**start,
109 	off_t			off,
110 	int 			count,
111 	int 			*eof,
112 	void			*data)
113 {
114 	struct acpi_button	*button = (struct acpi_button *) data;
115 	char			*p = page;
116 	int			len = 0;
117 	acpi_status		status=AE_OK;
118 	unsigned long		state;
119 
120 	ACPI_FUNCTION_TRACE("acpi_button_lid_read_state");
121 
122 	if (!button || !button->device || (off != 0))
123 		goto end;
124 
125 	status=acpi_evaluate_integer(button->handle,"_LID",NULL,&state);
126 	if (ACPI_FAILURE(status)){
127 	    p += sprintf(p, "state:      unsupported\n");
128 	}
129 	else{
130 	    p += sprintf(p, "state:      %s\n", (state ? "open" : "closed"));
131 	}
132 
133 end:
134 	len = (p - page);
135 	if (len <= off+count) *eof = 1;
136 	*start = page + off;
137 	len -= off;
138 	if (len>count) len = count;
139 	if (len<0) len = 0;
140 
141 	return_VALUE(len);
142 }
143 
144 static int
acpi_button_add_fs(struct acpi_device * device)145 acpi_button_add_fs (
146 	struct acpi_device	*device)
147 {
148 	struct proc_dir_entry	*entry = NULL;
149 	struct acpi_button	*button = NULL;
150 
151 	ACPI_FUNCTION_TRACE("acpi_button_add_fs");
152 
153 	if (!device || !acpi_driver_data(device))
154 		return_VALUE(-EINVAL);
155 
156 	button = acpi_driver_data(device);
157 
158 	switch (button->type) {
159 	case ACPI_BUTTON_TYPE_POWER:
160 	case ACPI_BUTTON_TYPE_POWERF:
161 			entry = proc_mkdir(ACPI_BUTTON_SUBCLASS_POWER,
162 				acpi_button_dir);
163 		break;
164 	case ACPI_BUTTON_TYPE_SLEEP:
165 	case ACPI_BUTTON_TYPE_SLEEPF:
166 			entry = proc_mkdir(ACPI_BUTTON_SUBCLASS_SLEEP,
167 				acpi_button_dir);
168 		break;
169 	case ACPI_BUTTON_TYPE_LID:
170 			entry = proc_mkdir(ACPI_BUTTON_SUBCLASS_LID,
171 				acpi_button_dir);
172 		break;
173 	}
174 
175 	if (!entry)
176 		return_VALUE(-ENODEV);
177 	entry->owner = THIS_MODULE;
178 
179 	acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device), entry);
180 	if (!acpi_device_dir(device))
181 		return_VALUE(-ENODEV);
182 	acpi_device_dir(device)->owner = THIS_MODULE;
183 
184 	/* 'info' [R] */
185 	entry = create_proc_entry(ACPI_BUTTON_FILE_INFO,
186 		S_IRUGO, acpi_device_dir(device));
187 	if (!entry)
188 		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
189 			"Unable to create '%s' fs entry\n",
190 			ACPI_BUTTON_FILE_INFO));
191 	else {
192 		entry->read_proc = acpi_button_read_info;
193 		entry->data = acpi_driver_data(device);
194 		entry->owner = THIS_MODULE;
195 	}
196 
197 	if (button->type==ACPI_BUTTON_TYPE_LID){
198 	    /* 'state' [R] */
199 	    entry = create_proc_entry(ACPI_BUTTON_FILE_STATE,
200 			S_IRUGO, acpi_device_dir(device));
201 	    if (!entry)
202 		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
203 			  "Unable to create '%s' fs entry\n",
204 			   ACPI_BUTTON_FILE_STATE));
205 	    else {
206 		entry->read_proc = acpi_button_lid_read_state;
207 		entry->data = acpi_driver_data(device);
208 		entry->owner = THIS_MODULE;
209 	    }
210 	}
211 
212 	return_VALUE(0);
213 }
214 
215 
216 static int
acpi_button_remove_fs(struct acpi_device * device)217 acpi_button_remove_fs (
218 	struct acpi_device	*device)
219 {
220 	struct acpi_button	*button = NULL;
221 
222 	ACPI_FUNCTION_TRACE("acpi_button_remove_fs");
223 
224 	button = acpi_driver_data(device);
225 	if (acpi_device_dir(device)) {
226  		if (button->type == ACPI_BUTTON_TYPE_LID)
227  			remove_proc_entry(ACPI_BUTTON_FILE_STATE,
228  					     acpi_device_dir(device));
229  		remove_proc_entry(ACPI_BUTTON_FILE_INFO,
230  				     acpi_device_dir(device));
231 
232  		remove_proc_entry(acpi_device_bid(device),
233  				     acpi_device_dir(device)->parent);
234 
235 		switch (button->type) {
236 			case ACPI_BUTTON_TYPE_POWER:
237 			case ACPI_BUTTON_TYPE_POWERF:
238 				remove_proc_entry(ACPI_BUTTON_SUBCLASS_POWER,
239 					acpi_button_dir);
240 				break;
241 			case ACPI_BUTTON_TYPE_SLEEP:
242 			case ACPI_BUTTON_TYPE_SLEEPF:
243 				remove_proc_entry(ACPI_BUTTON_SUBCLASS_SLEEP,
244 					acpi_button_dir);
245 				break;
246 			case ACPI_BUTTON_TYPE_LID:
247 				remove_proc_entry(ACPI_BUTTON_SUBCLASS_LID,
248 					acpi_button_dir);
249 				break;
250 		}
251 		acpi_device_dir(device) = NULL;
252 	}
253 
254 	return_VALUE(0);
255 }
256 
257 
258 /* --------------------------------------------------------------------------
259                                 Driver Interface
260    -------------------------------------------------------------------------- */
261 
262 void
acpi_button_notify(acpi_handle handle,u32 event,void * data)263 acpi_button_notify (
264 	acpi_handle		handle,
265 	u32			event,
266 	void			*data)
267 {
268 	struct acpi_button	*button = (struct acpi_button *) data;
269 
270 	ACPI_FUNCTION_TRACE("acpi_button_notify");
271 
272 	if (!button || !button->device)
273 		return_VOID;
274 
275 	switch (event) {
276 	case ACPI_BUTTON_NOTIFY_STATUS:
277 		acpi_bus_generate_event(button->device, event, ++button->pushed);
278 		break;
279 	default:
280 		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
281 			"Unsupported event [0x%x]\n", event));
282 		break;
283 	}
284 
285 	return_VOID;
286 }
287 
288 
289 acpi_status
acpi_button_notify_fixed(void * data)290 acpi_button_notify_fixed (
291 	void			*data)
292 {
293 	struct acpi_button	*button = (struct acpi_button *) data;
294 
295 	ACPI_FUNCTION_TRACE("acpi_button_notify_fixed");
296 
297 	if (!button)
298 		return_ACPI_STATUS(AE_BAD_PARAMETER);
299 
300 	acpi_button_notify(button->handle, ACPI_BUTTON_NOTIFY_STATUS, button);
301 
302 	return_ACPI_STATUS(AE_OK);
303 }
304 
305 
306 static int
acpi_button_add(struct acpi_device * device)307 acpi_button_add (
308 	struct acpi_device	*device)
309 {
310 	int			result = 0;
311 	acpi_status		status = AE_OK;
312 	struct acpi_button	*button = NULL;
313 
314 	static struct acpi_device *power_button;
315 	static struct acpi_device *sleep_button;
316 	static struct acpi_device *lid_button;
317 
318 	ACPI_FUNCTION_TRACE("acpi_button_add");
319 
320 	if (!device)
321 		return_VALUE(-EINVAL);
322 
323 	button = kmalloc(sizeof(struct acpi_button), GFP_KERNEL);
324 	if (!button)
325 		return_VALUE(-ENOMEM);
326 	memset(button, 0, sizeof(struct acpi_button));
327 
328 	button->device = device;
329 	button->handle = device->handle;
330 	acpi_driver_data(device) = button;
331 
332 	/*
333 	 * Determine the button type (via hid), as fixed-feature buttons
334 	 * need to be handled a bit differently than generic-space.
335 	 */
336 	if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_POWER)) {
337 		button->type = ACPI_BUTTON_TYPE_POWER;
338 		sprintf(acpi_device_name(device), "%s",
339 			ACPI_BUTTON_DEVICE_NAME_POWER);
340 		sprintf(acpi_device_class(device), "%s/%s",
341 			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_POWER);
342 	}
343 	else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_POWERF)) {
344 		button->type = ACPI_BUTTON_TYPE_POWERF;
345 		sprintf(acpi_device_name(device), "%s",
346 			ACPI_BUTTON_DEVICE_NAME_POWERF);
347 		sprintf(acpi_device_class(device), "%s/%s",
348 			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_POWER);
349 	}
350 	else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_SLEEP)) {
351 		button->type = ACPI_BUTTON_TYPE_SLEEP;
352 		sprintf(acpi_device_name(device), "%s",
353 			ACPI_BUTTON_DEVICE_NAME_SLEEP);
354 		sprintf(acpi_device_class(device), "%s/%s",
355 			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_SLEEP);
356 	}
357 	else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_SLEEPF)) {
358 		button->type = ACPI_BUTTON_TYPE_SLEEPF;
359 		sprintf(acpi_device_name(device), "%s",
360 			ACPI_BUTTON_DEVICE_NAME_SLEEPF);
361 		sprintf(acpi_device_class(device), "%s/%s",
362 			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_SLEEP);
363 	}
364 	else if (!strcmp(acpi_device_hid(device), ACPI_BUTTON_HID_LID)) {
365 		button->type = ACPI_BUTTON_TYPE_LID;
366 		sprintf(acpi_device_name(device), "%s",
367 			ACPI_BUTTON_DEVICE_NAME_LID);
368 		sprintf(acpi_device_class(device), "%s/%s",
369 			ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID);
370 	}
371 	else {
372 		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unsupported hid [%s]\n",
373 			acpi_device_hid(device)));
374 		result = -ENODEV;
375 		goto end;
376 	}
377 
378 	/*
379 	 * Ensure only one button of each type is used.
380 	 */
381 	switch (button->type) {
382 	case ACPI_BUTTON_TYPE_POWER:
383 	case ACPI_BUTTON_TYPE_POWERF:
384 		if (!power_button)
385 			power_button = device;
386 		else {
387 			kfree(button);
388 			return_VALUE(-ENODEV);
389 		}
390 		break;
391 	case ACPI_BUTTON_TYPE_SLEEP:
392 	case ACPI_BUTTON_TYPE_SLEEPF:
393 		if (!sleep_button)
394 			sleep_button = device;
395 		else {
396 			kfree(button);
397 			return_VALUE(-ENODEV);
398 		}
399 		break;
400 	case ACPI_BUTTON_TYPE_LID:
401 		if (!lid_button)
402 			lid_button = device;
403 		else {
404 			kfree(button);
405 			return_VALUE(-ENODEV);
406 		}
407 		break;
408 	}
409 
410 	result = acpi_button_add_fs(device);
411 	if (result)
412 		goto end;
413 
414 	switch (button->type) {
415 	case ACPI_BUTTON_TYPE_POWERF:
416 		status = acpi_install_fixed_event_handler (
417 			ACPI_EVENT_POWER_BUTTON,
418 			acpi_button_notify_fixed,
419 			button);
420 		break;
421 	case ACPI_BUTTON_TYPE_SLEEPF:
422 		status = acpi_install_fixed_event_handler (
423 			ACPI_EVENT_SLEEP_BUTTON,
424 			acpi_button_notify_fixed,
425 			button);
426 		break;
427 	default:
428 		status = acpi_install_notify_handler (
429 			button->handle,
430 			ACPI_DEVICE_NOTIFY,
431 			acpi_button_notify,
432 			button);
433 		break;
434 	}
435 
436 	if (ACPI_FAILURE(status)) {
437 		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
438 			"Error installing notify handler\n"));
439 		result = -ENODEV;
440 		goto end;
441 	}
442 
443 	printk(KERN_INFO PREFIX "%s [%s]\n",
444 		acpi_device_name(device), acpi_device_bid(device));
445 
446 end:
447 	if (result) {
448 		acpi_button_remove_fs(device);
449 		kfree(button);
450 	}
451 
452 	return_VALUE(result);
453 }
454 
455 
456 static int
acpi_button_remove(struct acpi_device * device,int type)457 acpi_button_remove (struct acpi_device *device, int type)
458 {
459 	acpi_status		status = 0;
460 	struct acpi_button	*button = NULL;
461 
462 	ACPI_FUNCTION_TRACE("acpi_button_remove");
463 
464 	if (!device || !acpi_driver_data(device))
465 		return_VALUE(-EINVAL);
466 
467 	button = acpi_driver_data(device);
468 
469 	/* Unregister for device notifications. */
470 	switch (button->type) {
471 	case ACPI_BUTTON_TYPE_POWERF:
472 		status = acpi_remove_fixed_event_handler(
473 			ACPI_EVENT_POWER_BUTTON, acpi_button_notify_fixed);
474 		break;
475 	case ACPI_BUTTON_TYPE_SLEEPF:
476 		status = acpi_remove_fixed_event_handler(
477 			ACPI_EVENT_SLEEP_BUTTON, acpi_button_notify_fixed);
478 		break;
479 	default:
480 		status = acpi_remove_notify_handler(button->handle,
481 			ACPI_DEVICE_NOTIFY, acpi_button_notify);
482 		break;
483 	}
484 
485 	if (ACPI_FAILURE(status))
486 		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
487 			"Error removing notify handler\n"));
488 
489 	acpi_button_remove_fs(device);
490 
491 	kfree(button);
492 
493 	return_VALUE(0);
494 }
495 
496 
497 static int __init
acpi_button_init(void)498 acpi_button_init (void)
499 {
500 	int			result = 0;
501 
502 	ACPI_FUNCTION_TRACE("acpi_button_init");
503 
504 	acpi_button_dir = proc_mkdir(ACPI_BUTTON_CLASS, acpi_root_dir);
505 	if (!acpi_button_dir)
506 		return_VALUE(-ENODEV);
507 	acpi_button_dir->owner = THIS_MODULE;
508 
509 	result = acpi_bus_register_driver(&acpi_button_driver);
510 	if (result < 0) {
511 		remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
512 		return_VALUE(-ENODEV);
513 	}
514 
515 	return_VALUE(0);
516 }
517 
518 
519 static void __exit
acpi_button_exit(void)520 acpi_button_exit (void)
521 {
522 	ACPI_FUNCTION_TRACE("acpi_button_exit");
523 
524 	acpi_bus_unregister_driver(&acpi_button_driver);
525 
526 	remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
527 
528 	return_VOID;
529 }
530 
531 
532 module_init(acpi_button_init);
533 module_exit(acpi_button_exit);
534