/* * eepc_acpi.c - Asus Eee PC hotkey driver * * Copyright (C) 2008 Eric Cooper * * Based on asus_acpi.c as patched for the Eee PC by Asus: * ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar * * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor * * 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 #include #include #include #include #include #include #include #include #define EEEPC_HOTK_NAME "Asus EeePC Hotkey Driver" #define EEEPC_HOTK_CLASS "hotkey" #define EEEPC_HOTK_DEVICE_NAME "Hotkey" #define EEEPC_HOTK_HID "ASUS010" /* * Definitions for Asus EeePC */ #define PROC_ASUS "asus" //the directory #define PROC_LCD "lcd" #define PROC_BRN "brn" #define PROC_DISP "disp" #define PROC_INIT "init" #define PROC_CAMERA "camera" #define PROC_CARDR "cardr" #define PROC_CPUFV "cpufv" #define PROC_HDPS "hdps" #define PROC_MODEM "modem" #define PROC_WLAN "wlan" #define PROC_USB "usb" #define NOTIFY_WLAN_ON 0x10 enum { DISABLE_ASL_WLAN = 0x0001, DISABLE_ASL_BLUETOOTH = 0x0002, DISABLE_ASL_IRDA = 0x0004, DISABLE_ASL_CAMERA = 0x0008, DISABLE_ASL_TV = 0x0010, DISABLE_ASL_GPS = 0x0020, DISABLE_ASL_DISPLAYSWITCH = 0x0040, DISABLE_ASL_MODEM = 0x0080, DISABLE_ASL_CARDREADER = 0x0100 }; typedef enum { CM_ASL_WLAN = 0, CM_ASL_BLUETOOTH, CM_ASL_IRDA, CM_ASL_1394, CM_ASL_CAMERA, CM_ASL_TV, CM_ASL_GPS, CM_ASL_DVDROM, CM_ASL_DISPLAYSWITCH, CM_ASL_PANELBRIGHT, CM_ASL_BIOSFLASH, CM_ASL_ACPIFLASH, CM_ASL_CPUFV, CM_ASL_CPUTEMPERATURE, CM_ASL_FANCPU, CM_ASL_FANCHASSIS, CM_ASL_USBPORT1, CM_ASL_USBPORT2, CM_ASL_USBPORT3, CM_ASL_MODEM, CM_ASL_CARDREADER, CM_ASL_LID } cm_asl_t; const char *cm_getv[] = { "WLDG", NULL, NULL, NULL, "CAMG", NULL, NULL, NULL, NULL, "PBLG", NULL, NULL, "CFVG", NULL, NULL, NULL, "USBG", NULL, NULL, "MODG", "CRDG", "LIDG" }; const char *cm_setv[] = { "WLDS", NULL, NULL, NULL, "CAMS", NULL, NULL, NULL, "SDSP", "PBLS", "HDPS", NULL, "CFVS", NULL, NULL, NULL, "USBG", NULL, NULL, "MODS", "CRDS", NULL }; static unsigned int init_flag; static struct proc_dir_entry *eeepc_proc_dir; /* * This is the main structure, we can use it to store useful information * about the hotk device */ struct eeepc_hotk { struct acpi_device *device; //the device we are in acpi_handle handle; //the handle of the hotk device unsigned int cm_supported; //the control method supported status of this BIOS. unsigned short event_count[128]; //count for each event }; /* The actual device the driver binds to */ static struct eeepc_hotk *ehotk; /* * The hotkey driver declaration */ static int eeepc_hotk_add(struct acpi_device *device); static int eeepc_hotk_remove(struct acpi_device *device, int type); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) static const struct acpi_device_id eee_device_ids[] = { {EEEPC_HOTK_HID, 0}, {"", 0}, }; #else #define eee_device_ids EEEPC_HOTK_HID #endif static struct acpi_driver eeepc_hotk_driver = { .name = "eeepc_acpi", .class = EEEPC_HOTK_CLASS, .ids = eee_device_ids, .ops = { .add = eeepc_hotk_add, .remove = eeepc_hotk_remove, }, }; MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Eric Cooper"); MODULE_DESCRIPTION(EEEPC_HOTK_NAME); MODULE_LICENSE("GPL"); static int parse_arg(const char __user * buf, unsigned long count, int *val) { char s[32]; if (!count) return 0; if (count > 31) return -EINVAL; if (copy_from_user(s, buf, count)) return -EFAULT; s[count] = 0; if (sscanf(s, "%i", val) != 1) return -EINVAL; return count; } /* * returns 1 if write is successful, otherwise 0. */ static int write_eeepc_acpi_int(acpi_handle handle, const char *method, int val, struct acpi_buffer *output) { struct acpi_object_list params; union acpi_object in_obj; acpi_status status; params.count = 1; params.pointer = &in_obj; in_obj.type = ACPI_TYPE_INTEGER; in_obj.integer.value = val; status = acpi_evaluate_object(handle, (char *) method, ¶ms, output); return status == AE_OK; } static int read_eeepc_acpi_int(acpi_handle handle, const char *method, int *val) { struct acpi_buffer output; union acpi_object out_obj; acpi_status status; output.length = sizeof(out_obj); output.pointer = &out_obj; status = acpi_evaluate_object(handle, (char *) method, NULL, &output); *val = out_obj.integer.value; return status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; } static int eeepc_hotk_write_proc(struct file *file, const char __user * buffer, unsigned long count, void *data) { int value, rv; cm_asl_t cm; rv = parse_arg(buffer, count, &value); cm = (unsigned int) data; if (rv > 0 && (ehotk->cm_supported & (0x1 << cm))) { if (!write_eeepc_acpi_int(ehotk->handle, cm_setv[cm], value, NULL)) printk(KERN_WARNING "[eeepc hotk] Error writing %s.\n", cm_setv[cm]); } return rv; } static int eeepc_hotk_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { int value; cm_asl_t cm; cm = (unsigned int) data; if ((ehotk->cm_supported & (0x1 << cm))) { if (!cm_getv[cm]) return 0; if (!read_eeepc_acpi_int(ehotk->handle, cm_getv[cm], &value)) printk(KERN_WARNING "[eeepc hotk] Error reading %s.\n", cm_getv[cm]); } else { value = -1; } return sprintf(page, "%d\n", value); } static int eeepc_hotk_reset_init(struct file *file, const char __user * buffer, unsigned long count, void *data) { int value, rv; rv = parse_arg(buffer, count, &value); if (!write_eeepc_acpi_int(ehotk->handle, "INIT", value, NULL)) printk(KERN_ERR "[eeepc hotk] Hotkey initialization failed\n"); else printk(KERN_INFO "[eeepc hotk] Reset init flag 0x%x\n", value); return rv; } static int eeepc_hotk_init_proc(char *name, mode_t mode, struct acpi_device *device) { struct proc_dir_entry *proc = create_proc_entry(name, mode, acpi_device_dir(device)); if (!proc) { printk(KERN_WARNING "[eeepc hotk] Unable to create init fs entry\n"); return -1; } proc->write_proc = eeepc_hotk_reset_init; proc->owner = THIS_MODULE; return 0; } static int eeepc_hotk_new_proc(char *name, cm_asl_t cm, mode_t mode, struct acpi_device *device) { struct proc_dir_entry *proc = create_proc_entry(name, mode, acpi_device_dir(device)); if (!proc) { printk(KERN_WARNING "[eeepc hotk] Unable to create %s fs entry\n", name); return -1; } proc->write_proc = eeepc_hotk_write_proc; proc->read_proc = eeepc_hotk_read_proc; proc->data = (void *) cm; proc->owner = THIS_MODULE; return 0; } static int eeepc_hotk_add_fs(struct acpi_device *device) { mode_t mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; acpi_device_dir(device) = eeepc_proc_dir; if (!acpi_device_dir(device)) return -ENODEV; if (ehotk->cm_supported & (0x1 << CM_ASL_WLAN)) eeepc_hotk_new_proc(PROC_WLAN, CM_ASL_WLAN, mode, device); if (ehotk->cm_supported & (0x1 << CM_ASL_CAMERA)) eeepc_hotk_new_proc(PROC_CAMERA, CM_ASL_CAMERA, mode, device); if (ehotk->cm_supported & (0x1 << CM_ASL_DISPLAYSWITCH)) eeepc_hotk_new_proc(PROC_DISP, CM_ASL_DISPLAYSWITCH, mode, device); if (ehotk->cm_supported & (0x1 << CM_ASL_PANELBRIGHT)) eeepc_hotk_new_proc(PROC_BRN, CM_ASL_PANELBRIGHT, mode, device); if (ehotk->cm_supported & (0x1 << CM_ASL_BIOSFLASH)) eeepc_hotk_new_proc(PROC_HDPS, CM_ASL_BIOSFLASH, mode, device); if (ehotk->cm_supported & (0x1 << CM_ASL_CPUFV)) eeepc_hotk_new_proc(PROC_CPUFV, CM_ASL_CPUFV, mode, device); if (ehotk->cm_supported & (0x1 << CM_ASL_MODEM)) eeepc_hotk_new_proc(PROC_MODEM, CM_ASL_MODEM, mode, device); if (ehotk->cm_supported & (0x1 << CM_ASL_CARDREADER)) eeepc_hotk_new_proc(PROC_CARDR, CM_ASL_CARDREADER, mode, device); eeepc_hotk_init_proc(PROC_INIT, mode, device); return 0; } static int eeepc_hotk_remove_fs(struct acpi_device *device) { if (acpi_device_dir(device)) { if (ehotk->cm_supported & (0x1 << CM_ASL_WLAN)) remove_proc_entry(PROC_WLAN, acpi_device_dir(device)); if (ehotk->cm_supported & (0x1 << CM_ASL_CAMERA)) remove_proc_entry(PROC_CAMERA, acpi_device_dir(device)); if (ehotk->cm_supported & (0x1 << CM_ASL_DISPLAYSWITCH)) remove_proc_entry(PROC_DISP, acpi_device_dir(device)); if (ehotk->cm_supported & (0x1 << CM_ASL_PANELBRIGHT)) remove_proc_entry(PROC_BRN, acpi_device_dir(device)); if (ehotk->cm_supported & (0x1 << CM_ASL_BIOSFLASH)) remove_proc_entry(PROC_HDPS, acpi_device_dir(device)); if (ehotk->cm_supported & (0x1 << CM_ASL_CPUFV)) remove_proc_entry(PROC_CPUFV, acpi_device_dir(device)); if (ehotk->cm_supported & (0x1 << CM_ASL_MODEM)) remove_proc_entry(PROC_MODEM, acpi_device_dir(device)); if (ehotk->cm_supported & (0x1 << CM_ASL_CARDREADER)) remove_proc_entry(PROC_CARDR, acpi_device_dir(device)); remove_proc_entry(PROC_INIT, acpi_device_dir(device)); } return 0; } static int eeepc_hotk_check(void) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; int result = AE_OK; result = acpi_bus_get_status(ehotk->device); if (result) return result; if (ehotk->device->status.present) { if (!write_eeepc_acpi_int(ehotk->handle, "INIT", init_flag, &buffer)) { printk(KERN_ERR "[eeepc hotk] Hotkey initialization failed\n"); return -ENODEV; } else { printk(KERN_NOTICE "[eeepc hotk] Hotkey init flags 0x%x.\n", init_flag); } // get control methods supported if (!read_eeepc_acpi_int(ehotk->handle, "CMSG", &ehotk->cm_supported)) { printk(KERN_ERR "[eeepc hotk] Get control methods supported failed\n"); return -ENODEV; } else { printk(KERN_INFO "[eeepc hotk] Get control methods supported: 0x%x\n", ehotk->cm_supported); } ehotk->cm_supported |= (0x1 << CM_ASL_LID); } else { printk(KERN_ERR "[eeepc hotk] Hotkey device not present, aborting\n"); return -EINVAL; } return result; } static void eeepc_hotk_notify(acpi_handle handle, u32 event, void *data) { if (!ehotk) return; // if DISABLE_ASL_WLAN is set, the notify code for fn+f2 will always be 0x10 if (event == NOTIFY_WLAN_ON && (DISABLE_ASL_WLAN & init_flag)) { int value; if (ehotk->cm_supported & (0x1 << CM_ASL_WLAN)) { if (!read_eeepc_acpi_int(ehotk->handle, cm_getv[CM_ASL_WLAN], &value)) printk(KERN_WARNING "[eeepc hotk] Error reading %s\n", cm_getv[CM_ASL_WLAN]); else if (value == 1) event = 0x11; } } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23) #define acpi_bus_generate_proc_event acpi_bus_generate_event #endif acpi_bus_generate_proc_event(ehotk->device, event, ehotk->event_count[event % 128]++); } static int ehotk_found; static int eeepc_hotk_add(struct acpi_device *device) { acpi_status status = AE_OK; int result; if (!device) return -EINVAL; printk(KERN_NOTICE EEEPC_HOTK_NAME "\n"); ehotk = kzalloc(sizeof(struct eeepc_hotk), GFP_KERNEL); if (!ehotk) return -ENOMEM; ehotk->handle = device->handle; strcpy(acpi_device_name(device), EEEPC_HOTK_DEVICE_NAME); strcpy(acpi_device_class(device), EEEPC_HOTK_CLASS); acpi_driver_data(device) = ehotk; ehotk->device = device; result = eeepc_hotk_check(); if (result) goto end; result = eeepc_hotk_add_fs(device); if (result) goto end; status = acpi_install_notify_handler(ehotk->handle, ACPI_SYSTEM_NOTIFY, eeepc_hotk_notify, ehotk); if (ACPI_FAILURE(status)) printk(KERN_ERR "[eeepc hotk] Error installing notify handler\n"); ehotk_found = 1; end: if (result) kfree(ehotk); return result; } static int eeepc_hotk_remove(struct acpi_device *device, int type) { acpi_status status = 0; if (!device || !acpi_driver_data(device)) return -EINVAL; status = acpi_remove_notify_handler(ehotk->handle, ACPI_SYSTEM_NOTIFY, eeepc_hotk_notify); if (ACPI_FAILURE(status)) printk(KERN_ERR "[eeepc hotk] Error removing notify handler\n"); eeepc_hotk_remove_fs(device); kfree(ehotk); return 0; } static void __exit eeepc_hotk_exit(void) { acpi_bus_unregister_driver(&eeepc_hotk_driver); remove_proc_entry(PROC_ASUS, acpi_root_dir); } static int __init eeepc_hotk_init(void) { int result; if (acpi_disabled) return -ENODEV; eeepc_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir); if (!eeepc_proc_dir) { printk(KERN_ERR "[eeepc hotk] Unable to create /proc entry\n"); return -ENODEV; } eeepc_proc_dir->owner = THIS_MODULE; init_flag = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH; result = acpi_bus_register_driver(&eeepc_hotk_driver); if (result < 0) { remove_proc_entry(PROC_ASUS, acpi_root_dir); return result; } if (!ehotk_found) { acpi_bus_unregister_driver(&eeepc_hotk_driver); remove_proc_entry(PROC_ASUS, acpi_root_dir); return result; } return 0; } module_init(eeepc_hotk_init); module_exit(eeepc_hotk_exit);