Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.8.
  1/*
  2 *  quickstart.c - ACPI Direct App Launch driver
  3 *
  4 *
  5 *  Copyright (C) 2007-2010 Angelo Arrifano <miknix@gmail.com>
  6 *
  7 *  Information gathered from disassembled dsdt and from here:
  8 *  <http://www.microsoft.com/whdc/system/platform/firmware/DirAppLaunch.mspx>
  9 *
 10 *  This program is free software; you can redistribute it and/or modify
 11 *  it under the terms of the GNU General Public License as published by
 12 *  the Free Software Foundation; either version 2 of the License, or
 13 *  (at your option) any later version.
 14 *
 15 *  This program is distributed in the hope that it will be useful,
 16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 18 *  GNU General Public License for more details.
 19 *
 20 *  You should have received a copy of the GNU General Public License
 21 *  along with this program; if not, write to the Free Software
 22 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 23 *
 24 */
 25
 26#define QUICKSTART_VERSION "1.04"
 27
 28#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 29
 30#include <linux/kernel.h>
 31#include <linux/module.h>
 32#include <linux/init.h>
 33#include <linux/types.h>
 34#include <linux/acpi.h>
 35#include <linux/platform_device.h>
 36#include <linux/input.h>
 37
 38MODULE_AUTHOR("Angelo Arrifano");
 39MODULE_DESCRIPTION("ACPI Direct App Launch driver");
 40MODULE_LICENSE("GPL");
 41
 42#define QUICKSTART_ACPI_DEVICE_NAME	"quickstart"
 43#define QUICKSTART_ACPI_CLASS		"quickstart"
 44#define QUICKSTART_ACPI_HID		"PNP0C32"
 45
 46#define QUICKSTART_PF_DRIVER_NAME	"quickstart"
 47#define QUICKSTART_PF_DEVICE_NAME	"quickstart"
 48
 49/*
 50 * There will be two events:
 51 * 0x02 - A hot button was pressed while device was off/sleeping.
 52 * 0x80 - A hot button was pressed while device was up.
 53 */
 54#define QUICKSTART_EVENT_WAKE		0x02
 55#define QUICKSTART_EVENT_RUNTIME	0x80
 56
 57struct quickstart_button {
 58	char *name;
 59	unsigned int id;
 60	struct list_head list;
 61};
 62
 63struct quickstart_acpi {
 64	struct acpi_device *device;
 65	struct quickstart_button *button;
 66};
 67
 68static LIST_HEAD(buttons);
 69static struct quickstart_button *pressed;
 70
 71static struct input_dev *quickstart_input;
 72
 73/* Platform driver functions */
 74static ssize_t buttons_show(struct device *dev, struct device_attribute *attr,
 75			    char *buf)
 76{
 77	int count = 0;
 78	struct quickstart_button *b;
 79
 80	if (list_empty(&buttons))
 81		return snprintf(buf, PAGE_SIZE, "none");
 82
 83	list_for_each_entry(b, &buttons, list) {
 84		count += snprintf(buf + count, PAGE_SIZE - count, "%u\t%s\n",
 85							b->id, b->name);
 86
 87		if (count >= PAGE_SIZE) {
 88			count = PAGE_SIZE;
 89			break;
 90		}
 91	}
 92
 93	return count;
 94}
 95
 96static ssize_t pressed_button_show(struct device *dev,
 97				   struct device_attribute *attr, char *buf)
 98{
 99	return scnprintf(buf, PAGE_SIZE, "%s\n",
100					(pressed ? pressed->name : "none"));
101}
102
103
104static ssize_t pressed_button_store(struct device *dev,
105				    struct device_attribute *attr,
106				    const char *buf, size_t count)
107{
108	if (count < 2)
109		return -EINVAL;
110
111	if (strncasecmp(buf, "none", 4) != 0)
112		return -EINVAL;
113
114	pressed = NULL;
115	return count;
116}
117
118/* Helper functions */
119static struct quickstart_button *quickstart_buttons_add(void)
120{
121	struct quickstart_button *b;
122
123	b = kzalloc(sizeof(*b), GFP_KERNEL);
124	if (!b)
125		return NULL;
126
127	list_add_tail(&b->list, &buttons);
128
129	return b;
130}
131
132static void quickstart_button_del(struct quickstart_button *data)
133{
134	if (!data)
135		return;
136
137	list_del(&data->list);
138	kfree(data->name);
139	kfree(data);
140}
141
142static void quickstart_buttons_free(void)
143{
144	struct quickstart_button *b, *n;
145
146	list_for_each_entry_safe(b, n, &buttons, list)
147		quickstart_button_del(b);
148}
149
150/* ACPI Driver functions */
151static void quickstart_acpi_notify(acpi_handle handle, u32 event, void *data)
152{
153	struct quickstart_acpi *quickstart = data;
154
155	if (!quickstart)
156		return;
157
158	switch (event) {
159	case QUICKSTART_EVENT_WAKE:
160		pressed = quickstart->button;
161		break;
162	case QUICKSTART_EVENT_RUNTIME:
163		input_report_key(quickstart_input, quickstart->button->id, 1);
164		input_sync(quickstart_input);
165		input_report_key(quickstart_input, quickstart->button->id, 0);
166		input_sync(quickstart_input);
167		break;
168	default:
169		pr_err("Unexpected ACPI event notify (%u)\n", event);
170		break;
171	}
172}
173
174static int quickstart_acpi_ghid(struct quickstart_acpi *quickstart)
175{
176	acpi_status status;
177	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
178	int ret = 0;
179
180	/*
181	 * This returns a buffer telling the button usage ID,
182	 * and triggers pending notify events (The ones before booting).
183	 */
184	status = acpi_evaluate_object(quickstart->device->handle, "GHID", NULL,
185								&buffer);
186	if (ACPI_FAILURE(status)) {
187		pr_err("%s GHID method failed\n", quickstart->button->name);
188		return -EINVAL;
189	}
190
191	/*
192	 * <<The GHID method can return a BYTE, WORD, or DWORD.
193	 * The value must be encoded in little-endian byte
194	 * order (least significant byte first).>>
195	 */
196	switch (buffer.length) {
197	case 1:
198		quickstart->button->id = *(uint8_t *)buffer.pointer;
199		break;
200	case 2:
201		quickstart->button->id = *(uint16_t *)buffer.pointer;
202		break;
203	case 4:
204		quickstart->button->id = *(uint32_t *)buffer.pointer;
205		break;
206	case 8:
207		quickstart->button->id = *(uint64_t *)buffer.pointer;
208		break;
209	default:
210		pr_err("%s GHID method returned buffer of unexpected length %lu\n",
211				quickstart->button->name,
212				(unsigned long)buffer.length);
213		ret = -EINVAL;
214		break;
215	}
216
217	kfree(buffer.pointer);
218
219	return ret;
220}
221
222static int quickstart_acpi_config(struct quickstart_acpi *quickstart)
223{
224	char *bid = acpi_device_bid(quickstart->device);
225	char *name;
226
227	name = kmalloc(strlen(bid) + 1, GFP_KERNEL);
228	if (!name)
229		return -ENOMEM;
230
231	/* Add new button to list */
232	quickstart->button = quickstart_buttons_add();
233	if (!quickstart->button) {
234		kfree(name);
235		return -ENOMEM;
236	}
237
238	quickstart->button->name = name;
239	strcpy(quickstart->button->name, bid);
240
241	return 0;
242}
243
244static int quickstart_acpi_add(struct acpi_device *device)
245{
246	int ret;
247	acpi_status status;
248	struct quickstart_acpi *quickstart;
249
250	if (!device)
251		return -EINVAL;
252
253	quickstart = kzalloc(sizeof(*quickstart), GFP_KERNEL);
254	if (!quickstart)
255		return -ENOMEM;
256
257	quickstart->device = device;
258
259	strcpy(acpi_device_name(device), QUICKSTART_ACPI_DEVICE_NAME);
260	strcpy(acpi_device_class(device), QUICKSTART_ACPI_CLASS);
261	device->driver_data = quickstart;
262
263	/* Add button to list and initialize some stuff */
264	ret = quickstart_acpi_config(quickstart);
265	if (ret < 0)
266		goto fail_config;
267
268	status = acpi_install_notify_handler(device->handle, ACPI_ALL_NOTIFY,
269						quickstart_acpi_notify,
270						quickstart);
271	if (ACPI_FAILURE(status)) {
272		pr_err("Notify handler install error\n");
273		ret = -ENODEV;
274		goto fail_installnotify;
275	}
276
277	ret = quickstart_acpi_ghid(quickstart);
278	if (ret < 0)
279		goto fail_ghid;
280
281	return 0;
282
283fail_ghid:
284	acpi_remove_notify_handler(device->handle, ACPI_ALL_NOTIFY,
285						quickstart_acpi_notify);
286
287fail_installnotify:
288	quickstart_button_del(quickstart->button);
289
290fail_config:
291
292	kfree(quickstart);
293
294	return ret;
295}
296
297static int quickstart_acpi_remove(struct acpi_device *device)
298{
299	acpi_status status;
300	struct quickstart_acpi *quickstart;
301
302	if (!device)
303		return -EINVAL;
304
305	quickstart = acpi_driver_data(device);
306	if (!quickstart)
307		return -EINVAL;
308
309	status = acpi_remove_notify_handler(device->handle, ACPI_ALL_NOTIFY,
310						quickstart_acpi_notify);
311	if (ACPI_FAILURE(status))
312		pr_err("Error removing notify handler\n");
313
314	kfree(quickstart);
315
316	return 0;
317}
318
319/* Platform driver structs */
320static DEVICE_ATTR_RW(pressed_button);
321static DEVICE_ATTR_RO(buttons);
322static struct platform_device *pf_device;
323static struct platform_driver pf_driver = {
324	.driver = {
325		.name = QUICKSTART_PF_DRIVER_NAME,
326		.owner = THIS_MODULE,
327	}
328};
329
330static const struct acpi_device_id quickstart_device_ids[] = {
331	{QUICKSTART_ACPI_HID, 0},
332	{"", 0},
333};
334
335static struct acpi_driver quickstart_acpi_driver = {
336	.name = "quickstart",
337	.class = QUICKSTART_ACPI_CLASS,
338	.ids = quickstart_device_ids,
339	.ops = {
340			.add = quickstart_acpi_add,
341			.remove = quickstart_acpi_remove,
342		},
343};
344
345/* Module functions */
346static void quickstart_exit(void)
347{
348	input_unregister_device(quickstart_input);
349
350	device_remove_file(&pf_device->dev, &dev_attr_pressed_button);
351	device_remove_file(&pf_device->dev, &dev_attr_buttons);
352
353	platform_device_unregister(pf_device);
354
355	platform_driver_unregister(&pf_driver);
356
357	acpi_bus_unregister_driver(&quickstart_acpi_driver);
358
359	quickstart_buttons_free();
360}
361
362static int __init quickstart_init_input(void)
363{
364	struct quickstart_button *b;
365	int ret;
366
367	quickstart_input = input_allocate_device();
368
369	if (!quickstart_input)
370		return -ENOMEM;
371
372	quickstart_input->name = "Quickstart ACPI Buttons";
373	quickstart_input->id.bustype = BUS_HOST;
374
375	list_for_each_entry(b, &buttons, list) {
376		set_bit(EV_KEY, quickstart_input->evbit);
377		set_bit(b->id, quickstart_input->keybit);
378	}
379
380	ret = input_register_device(quickstart_input);
381	if (ret) {
382		input_free_device(quickstart_input);
383		return ret;
384	}
385
386	return 0;
387}
388
389static int __init quickstart_init(void)
390{
391	int ret;
392
393	/* ACPI driver register */
394	ret = acpi_bus_register_driver(&quickstart_acpi_driver);
395	if (ret)
396		return ret;
397
398	/* If existing bus with no devices */
399	if (list_empty(&buttons)) {
400		ret = -ENODEV;
401		goto fail_pfdrv_reg;
402	}
403
404	/* Platform driver register */
405	ret = platform_driver_register(&pf_driver);
406	if (ret)
407		goto fail_pfdrv_reg;
408
409	/* Platform device register */
410	pf_device = platform_device_alloc(QUICKSTART_PF_DEVICE_NAME, -1);
411	if (!pf_device) {
412		ret = -ENOMEM;
413		goto fail_pfdev_alloc;
414	}
415	ret = platform_device_add(pf_device);
416	if (ret)
417		goto fail_pfdev_add;
418
419	/* Create device sysfs file */
420	ret = device_create_file(&pf_device->dev, &dev_attr_pressed_button);
421	if (ret)
422		goto fail_dev_file;
423
424	ret = device_create_file(&pf_device->dev, &dev_attr_buttons);
425	if (ret)
426		goto fail_dev_file2;
427
428	/* Input device */
429	ret = quickstart_init_input();
430	if (ret)
431		goto fail_input;
432
433	pr_info("ACPI Direct App Launch ver %s\n", QUICKSTART_VERSION);
434
435	return 0;
436fail_input:
437	device_remove_file(&pf_device->dev, &dev_attr_buttons);
438
439fail_dev_file2:
440	device_remove_file(&pf_device->dev, &dev_attr_pressed_button);
441
442fail_dev_file:
443	platform_device_del(pf_device);
444
445fail_pfdev_add:
446	platform_device_put(pf_device);
447
448fail_pfdev_alloc:
449	platform_driver_unregister(&pf_driver);
450
451fail_pfdrv_reg:
452	acpi_bus_unregister_driver(&quickstart_acpi_driver);
453
454	return ret;
455}
456
457module_init(quickstart_init);
458module_exit(quickstart_exit);