Logo Search packages:      
Sourcecode: eeepc-acpi version File versions  Download package

eeepc_acpi.c

/*
 *  eepc_acpi.c - Asus Eee PC hotkey driver
 *
 *  Copyright (C) 2008 Eric Cooper <ecc@cmu.edu>
 *
 *  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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/version.h>
#include <acpi/acpi_drivers.h>
#include <acpi/acpi_bus.h>
#include <asm/uaccess.h>

#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
 */
00117 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, &params, 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);

Generated by  Doxygen 1.6.0   Back to index