Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  thinkpad_acpi.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  thinkpad_acpi.c - ThinkPad ACPI Extras
 *
 *  Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
 *  Copyright (C) 2006-2009 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#define TPACPI_VERSION "0.26"
#define TPACPI_SYSFS_VERSION 0x030000

/*
 *  Changelog:
 *  2007-10-20 changelog trimmed down
 *
 *  2007-03-27  0.14 renamed to thinkpad_acpi and moved to
 *   drivers/misc.
 *
 *  2006-11-22 0.13 new maintainer
 *   changelog now lives in git commit history, and will
 *   not be updated further in-file.
 *
 *  2005-03-17 0.11 support for 600e, 770x
 *     thanks to Jamie Lentin <lentinj@dial.pipex.com>
 *
 *  2005-01-16 0.9 use MODULE_VERSION
 *     thanks to Henrik Brix Andersen <brix@gentoo.org>
 * fix parameter passing on module loading
 *     thanks to Rusty Russell <rusty@rustcorp.com.au>
 *     thanks to Jim Radford <radford@blackbean.org>
 *  2004-11-08 0.8 fix init error case, don't return from a macro
 *     thanks to Chris Wright <chrisw@osdl.org>
 */


#include <linux/acpi.h>
#include <linux/backlight.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/freezer.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/leds.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/nvram.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/proc_fs.h>
#include <linux/rfkill.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/units.h>
#include <linux/workqueue.h>

#include <acpi/battery.h>
#include <acpi/video.h>

#include <drm/drm_privacy_screen_driver.h>

#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>

#include "../dual_accel_detect.h"

/* ThinkPad CMOS commands */
#define TP_CMOS_VOLUME_DOWN 0
#define TP_CMOS_VOLUME_UP 1
#define TP_CMOS_VOLUME_MUTE 2
#define TP_CMOS_BRIGHTNESS_UP 4
#define TP_CMOS_BRIGHTNESS_DOWN 5
#define TP_CMOS_THINKLIGHT_ON 12
#define TP_CMOS_THINKLIGHT_OFF 13

/* NVRAM Addresses */
enum tp_nvram_addr {
 TP_NVRAM_ADDR_HK2  = 0x57,
 TP_NVRAM_ADDR_THINKLIGHT = 0x58,
 TP_NVRAM_ADDR_VIDEO  = 0x59,
 TP_NVRAM_ADDR_BRIGHTNESS = 0x5e,
 TP_NVRAM_ADDR_MIXER  = 0x60,
};

/* NVRAM bit masks */
enum {
 TP_NVRAM_MASK_HKT_THINKPAD = 0x08,
 TP_NVRAM_MASK_HKT_ZOOM  = 0x20,
 TP_NVRAM_MASK_HKT_DISPLAY = 0x40,
 TP_NVRAM_MASK_HKT_HIBERNATE = 0x80,
 TP_NVRAM_MASK_THINKLIGHT = 0x10,
 TP_NVRAM_MASK_HKT_DISPEXPND = 0x30,
 TP_NVRAM_MASK_HKT_BRIGHTNESS = 0x20,
 TP_NVRAM_MASK_LEVEL_BRIGHTNESS = 0x0f,
 TP_NVRAM_POS_LEVEL_BRIGHTNESS = 0,
 TP_NVRAM_MASK_MUTE  = 0x40,
 TP_NVRAM_MASK_HKT_VOLUME = 0x80,
 TP_NVRAM_MASK_LEVEL_VOLUME = 0x0f,
 TP_NVRAM_POS_LEVEL_VOLUME = 0,
};

/* Misc NVRAM-related */
enum {
 TP_NVRAM_LEVEL_VOLUME_MAX = 14,
};

/* ACPI HIDs */
#define TPACPI_ACPI_IBM_HKEY_HID "IBM0068"
#define TPACPI_ACPI_LENOVO_HKEY_HID "LEN0068"
#define TPACPI_ACPI_LENOVO_HKEY_V2_HID "LEN0268"
#define TPACPI_ACPI_EC_HID  "PNP0C09"

/* Input IDs */
#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
#define TPACPI_HKEY_INPUT_VERSION 0x4101

/* ACPI \WGSV commands */
enum {
 TP_ACPI_WGSV_GET_STATE  = 0x01, /* Get state information */
 TP_ACPI_WGSV_PWR_ON_ON_RESUME = 0x02, /* Resume WWAN powered on */
 TP_ACPI_WGSV_PWR_OFF_ON_RESUME = 0x03, /* Resume WWAN powered off */
 TP_ACPI_WGSV_SAVE_STATE  = 0x04, /* Save state for S4/S5 */
};

/* TP_ACPI_WGSV_GET_STATE bits */
enum {
 TP_ACPI_WGSV_STATE_WWANEXIST = 0x0001, /* WWAN hw available */
 TP_ACPI_WGSV_STATE_WWANPWR = 0x0002, /* WWAN radio enabled */
 TP_ACPI_WGSV_STATE_WWANPWRRES = 0x0004, /* WWAN state at resume */
 TP_ACPI_WGSV_STATE_WWANBIOSOFF = 0x0008, /* WWAN disabled in BIOS */
 TP_ACPI_WGSV_STATE_BLTHEXIST = 0x0001, /* BLTH hw available */
 TP_ACPI_WGSV_STATE_BLTHPWR = 0x0002, /* BLTH radio enabled */
 TP_ACPI_WGSV_STATE_BLTHPWRRES = 0x0004, /* BLTH state at resume */
 TP_ACPI_WGSV_STATE_BLTHBIOSOFF = 0x0008, /* BLTH disabled in BIOS */
 TP_ACPI_WGSV_STATE_UWBEXIST = 0x0010, /* UWB hw available */
 TP_ACPI_WGSV_STATE_UWBPWR = 0x0020, /* UWB radio enabled */
};

/* HKEY events */
enum tpacpi_hkey_event_t {
 /* Original hotkeys */
 TP_HKEY_EV_ORIG_KEY_START = 0x1001, /* First hotkey (FN+F1) */
 TP_HKEY_EV_BRGHT_UP  = 0x1010, /* Brightness up */
 TP_HKEY_EV_BRGHT_DOWN  = 0x1011, /* Brightness down */
 TP_HKEY_EV_KBD_LIGHT  = 0x1012, /* Thinklight/kbd backlight */
 TP_HKEY_EV_VOL_UP  = 0x1015, /* Volume up or unmute */
 TP_HKEY_EV_VOL_DOWN  = 0x1016, /* Volume down or unmute */
 TP_HKEY_EV_VOL_MUTE  = 0x1017, /* Mixer output mute */
 TP_HKEY_EV_ORIG_KEY_END  = 0x1020, /* Last original hotkey code */

 /* Adaptive keyboard (2014 X1 Carbon) */
 TP_HKEY_EV_DFR_CHANGE_ROW = 0x1101, /* Change adaptive kbd Fn row mode */
 TP_HKEY_EV_DFR_S_QUICKVIEW_ROW = 0x1102, /* Set adap. kbd Fn row to function mode */
 TP_HKEY_EV_ADAPTIVE_KEY_START = 0x1103, /* First hotkey code on adaptive kbd */
 TP_HKEY_EV_ADAPTIVE_KEY_END = 0x1116, /* Last hotkey code on adaptive kbd */

 /* Extended hotkey events in 2017+ models */
 TP_HKEY_EV_EXTENDED_KEY_START = 0x1300, /* First extended hotkey code */
 TP_HKEY_EV_PRIVACYGUARD_TOGGLE = 0x130f, /* Toggle priv.guard on/off */
 TP_HKEY_EV_EXTENDED_KEY_END = 0x1319, /* Last extended hotkey code using
   * hkey -> scancode translation for
   * compat. Later codes are entered
   * directly in the sparse-keymap.
   */

 TP_HKEY_EV_AMT_TOGGLE  = 0x131a, /* Toggle AMT on/off */
 TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */
 TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */
 TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */
 TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */

 /* Reasons for waking up from S3/S4 */
 TP_HKEY_EV_WKUP_S3_UNDOCK = 0x2304, /* undock requested, S3 */
 TP_HKEY_EV_WKUP_S4_UNDOCK = 0x2404, /* undock requested, S4 */
 TP_HKEY_EV_WKUP_S3_BAYEJ = 0x2305, /* bay ejection req, S3 */
 TP_HKEY_EV_WKUP_S4_BAYEJ = 0x2405, /* bay ejection req, S4 */
 TP_HKEY_EV_WKUP_S3_BATLOW = 0x2313, /* battery empty, S3 */
 TP_HKEY_EV_WKUP_S4_BATLOW = 0x2413, /* battery empty, S4 */

 /* Auto-sleep after eject request */
 TP_HKEY_EV_BAYEJ_ACK  = 0x3003, /* bay ejection complete */
 TP_HKEY_EV_UNDOCK_ACK  = 0x4003, /* undock complete */

 /* Misc bay events */
 TP_HKEY_EV_OPTDRV_EJ  = 0x3006, /* opt. drive tray ejected */
 TP_HKEY_EV_HOTPLUG_DOCK  = 0x4010, /* docked into hotplug dock
     or port replicator */

 TP_HKEY_EV_HOTPLUG_UNDOCK = 0x4011, /* undocked from hotplug
     dock or port replicator */

 /*
 * Thinkpad X1 Tablet series devices emit 0x4012 and 0x4013
 * when keyboard cover is attached, detached or folded onto the back
 */

 TP_HKEY_EV_KBD_COVER_ATTACH = 0x4012, /* keyboard cover attached */
 TP_HKEY_EV_KBD_COVER_DETACH = 0x4013, /* keyboard cover detached or folded back */

 /* User-interface events */
 TP_HKEY_EV_LID_CLOSE  = 0x5001, /* laptop lid closed */
 TP_HKEY_EV_LID_OPEN  = 0x5002, /* laptop lid opened */
 TP_HKEY_EV_TABLET_TABLET = 0x5009, /* tablet swivel up */
 TP_HKEY_EV_TABLET_NOTEBOOK = 0x500a, /* tablet swivel down */
 TP_HKEY_EV_TABLET_CHANGED = 0x60c0, /* X1 Yoga (2016):
   * enter/leave tablet mode
   */

 TP_HKEY_EV_PEN_INSERTED  = 0x500b, /* tablet pen inserted */
 TP_HKEY_EV_PEN_REMOVED  = 0x500c, /* tablet pen removed */
 TP_HKEY_EV_BRGHT_CHANGED = 0x5010, /* backlight control event */

 /* Key-related user-interface events */
 TP_HKEY_EV_KEY_NUMLOCK  = 0x6000, /* NumLock key pressed */
 TP_HKEY_EV_KEY_FN  = 0x6005, /* Fn key pressed? E420 */
 TP_HKEY_EV_KEY_FN_ESC           = 0x6060, /* Fn+Esc key pressed X240 */

 /* Thermal events */
 TP_HKEY_EV_ALARM_BAT_HOT = 0x6011, /* battery too hot */
 TP_HKEY_EV_ALARM_BAT_XHOT = 0x6012, /* battery critically hot */
 TP_HKEY_EV_ALARM_BAT_LIM_CHANGE = 0x6013, /* battery charge limit changed*/
 TP_HKEY_EV_ALARM_SENSOR_HOT = 0x6021, /* sensor too hot */
 TP_HKEY_EV_ALARM_SENSOR_XHOT = 0x6022, /* sensor critically hot */
 TP_HKEY_EV_THM_TABLE_CHANGED = 0x6030, /* windows; thermal table changed */
 TP_HKEY_EV_THM_CSM_COMPLETED    = 0x6032, /* windows; thermal control set
   * command completed. Related to
   * AML DYTC */

 TP_HKEY_EV_THM_TRANSFM_CHANGED  = 0x60F0, /* windows; thermal transformation
   * changed. Related to AML GMTS */


 /* AC-related events */
 TP_HKEY_EV_AC_CHANGED  = 0x6040, /* AC status changed */

 /* Further user-interface events */
 TP_HKEY_EV_PALM_DETECTED = 0x60b0, /* palm hoveres keyboard */
 TP_HKEY_EV_PALM_UNDETECTED = 0x60b1, /* palm removed */

 /* Misc */
 TP_HKEY_EV_RFKILL_CHANGED = 0x7000, /* rfkill switch changed */

 /* Misc2 */
 TP_HKEY_EV_TRACK_DOUBLETAP      = 0x8036, /* trackpoint doubletap */
};

/****************************************************************************
 * Main driver
 */


#define TPACPI_NAME "thinkpad"
#define TPACPI_DESC "ThinkPad ACPI Extras"
#define TPACPI_FILE TPACPI_NAME "_acpi"
#define TPACPI_URL "http://ibm-acpi.sf.net/"
#define TPACPI_MAIL "ibm-acpi-devel@lists.sourceforge.net"

#define TPACPI_PROC_DIR "ibm"
#define TPACPI_ACPI_EVENT_PREFIX "ibm"
#define TPACPI_DRVR_NAME TPACPI_FILE
#define TPACPI_DRVR_SHORTNAME "tpacpi"
#define TPACPI_HWMON_DRVR_NAME TPACPI_NAME "_hwmon"

#define TPACPI_NVRAM_KTHREAD_NAME "ktpacpi_nvramd"
#define TPACPI_WORKQUEUE_NAME "ktpacpid"

#define TPACPI_MAX_ACPI_ARGS 3

/* Debugging printk groups */
#define TPACPI_DBG_ALL  0xffff
#define TPACPI_DBG_DISCLOSETASK 0x8000
#define TPACPI_DBG_INIT  0x0001
#define TPACPI_DBG_EXIT  0x0002
#define TPACPI_DBG_RFKILL 0x0004
#define TPACPI_DBG_HKEY  0x0008
#define TPACPI_DBG_FAN  0x0010
#define TPACPI_DBG_BRGHT 0x0020
#define TPACPI_DBG_MIXER 0x0040

#define FAN_NOT_PRESENT  65535

/****************************************************************************
 * Driver-wide structs and misc. variables
 */


struct ibm_struct;

struct tp_acpi_drv_struct {
 const struct acpi_device_id *hid;
 struct acpi_driver *driver;

 void (*notify) (struct ibm_struct *, u32);
 acpi_handle *handle;
 u32 type;
 struct acpi_device *device;
};

struct ibm_struct {
 char *name;

 int (*read) (struct seq_file *);
 int (*write) (char *);
 void (*exit) (void);
 void (*resume) (void);
 void (*suspend) (void);
 void (*shutdown) (void);

 struct list_head all_drivers;

 struct tp_acpi_drv_struct *acpi;

 struct {
  u8 acpi_driver_registered:1;
  u8 acpi_notify_installed:1;
  u8 proc_created:1;
  u8 init_called:1;
  u8 experimental:1;
 } flags;
};

struct ibm_init_struct {
 char param[32];

 int (*init) (struct ibm_init_struct *);
 umode_t base_procfs_mode;
 struct ibm_struct *data;
};

/* DMI Quirks */
struct quirk_entry {
 bool btusb_bug;
};

static struct quirk_entry quirk_btusb_bug = {
 .btusb_bug = true,
};

static struct {
 u32 bluetooth:1;
 u32 hotkey:1;
 u32 hotkey_mask:1;
 u32 hotkey_wlsw:1;
 enum {
  TP_HOTKEY_TABLET_NONE = 0,
  TP_HOTKEY_TABLET_USES_MHKG,
  TP_HOTKEY_TABLET_USES_GMMS,
 } hotkey_tablet;
 u32 kbdlight:1;
 u32 light:1;
 u32 light_status:1;
 u32 bright_acpimode:1;
 u32 bright_unkfw:1;
 u32 wan:1;
 u32 uwb:1;
 u32 fan_ctrl_status_undef:1;
 u32 second_fan:1;
 u32 second_fan_ctl:1;
 u32 beep_needs_two_args:1;
 u32 mixer_no_level_control:1;
 u32 battery_force_primary:1;
 u32 platform_drv_registered:1;
 u32 hotkey_poll_active:1;
 u32 has_adaptive_kbd:1;
 u32 kbd_lang:1;
 u32 trackpoint_doubletap:1;
 struct quirk_entry *quirks;
} tp_features;

static struct {
 u16 hotkey_mask_ff:1;
 u16 volume_ctrl_forbidden:1;
} tp_warned;

struct thinkpad_id_data {
 unsigned int vendor; /* ThinkPad vendor:
 * PCI_VENDOR_ID_IBM/PCI_VENDOR_ID_LENOVO */


 char *bios_version_str; /* Something like 1ZET51WW (1.03z) */
 char *ec_version_str; /* Something like 1ZHT51WW-1.04a */

 u32 bios_model;  /* 1Y = 0x3159, 0 = unknown */
 u32 ec_model;
 u16 bios_release; /* 1ZETK1WW = 0x4b31, 0 = unknown */
 u16 ec_release;

 char *model_str; /* ThinkPad T43 */
 char *nummodel_str; /* 9384A9C for a 9384-A9C model */
};
static struct thinkpad_id_data thinkpad_id;

static enum {
 TPACPI_LIFE_INIT = 0,
 TPACPI_LIFE_RUNNING,
 TPACPI_LIFE_EXITING,
} tpacpi_lifecycle;

static int experimental;
static u32 dbg_level;

static struct workqueue_struct *tpacpi_wq;

enum led_status_t {
 TPACPI_LED_OFF = 0,
 TPACPI_LED_ON,
 TPACPI_LED_BLINK,
};

/* tpacpi LED class */
struct tpacpi_led_classdev {
 struct led_classdev led_classdev;
 int led;
};

/* brightness level capabilities */
static unsigned int bright_maxlvl; /* 0 = unknown */

#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
static int dbg_wlswemul;
static bool tpacpi_wlsw_emulstate;
static int dbg_bluetoothemul;
static bool tpacpi_bluetooth_emulstate;
static int dbg_wwanemul;
static bool tpacpi_wwan_emulstate;
static int dbg_uwbemul;
static bool tpacpi_uwb_emulstate;
#endif


/*************************************************************************
 *  Debugging helpers
 */


#define dbg_printk(a_dbg_level, format, arg...)    \
do {         \
 if (dbg_level & (a_dbg_level))     \
  printk(KERN_DEBUG pr_fmt("%s: " format),  \
         __func__, ##arg);    \
while (0)

#ifdef CONFIG_THINKPAD_ACPI_DEBUG
#define vdbg_printk dbg_printk
static const char *str_supported(int is_supported);
#else
static inline const char *str_supported(int is_supported) { return ""; }
#define vdbg_printk(a_dbg_level, format, arg...) \
 do { if (0) no_printk(format, ##arg); } while (0)
#endif

static void tpacpi_log_usertask(const char * const what)
{
 printk(KERN_DEBUG pr_fmt("%s: access by process with PID %d\n"),
        what, task_tgid_vnr(current));
}

#define tpacpi_disclose_usertask(what, format, arg...)   \
do {         \
 if (unlikely((dbg_level & TPACPI_DBG_DISCLOSETASK) &&  \
       (tpacpi_lifecycle == TPACPI_LIFE_RUNNING))) { \
  printk(KERN_DEBUG pr_fmt("%s: PID %d: " format), \
         what, task_tgid_vnr(current), ## arg);  \
 }        \
while (0)

/*
 * Quirk handling helpers
 *
 * ThinkPad IDs and versions seen in the field so far are
 * two or three characters from the set [0-9A-Z], i.e. base 36.
 *
 * We use values well outside that range as specials.
 */


#define TPACPI_MATCH_ANY  0xffffffffU
#define TPACPI_MATCH_ANY_VERSION 0xffffU
#define TPACPI_MATCH_UNKNOWN  0U

/* TPID('1', 'Y') == 0x3159 */
#define TPID(__c1, __c2) (((__c1) << 8) | (__c2))
#define TPID3(__c1, __c2, __c3) (((__c1) << 16) | ((__c2) << 8) | (__c3))
#define TPVER TPID

#define TPACPI_Q_IBM(__id1, __id2, __quirk) \
 { .vendor = PCI_VENDOR_ID_IBM,  \
   .bios = TPID(__id1, __id2),  \
   .ec = TPACPI_MATCH_ANY,  \
   .quirks = (__quirk) }

#define TPACPI_Q_LNV(__id1, __id2, __quirk) \
 { .vendor = PCI_VENDOR_ID_LENOVO, \
   .bios = TPID(__id1, __id2),  \
   .ec = TPACPI_MATCH_ANY,  \
   .quirks = (__quirk) }

#define TPACPI_Q_LNV3(__id1, __id2, __id3, __quirk) \
 { .vendor = PCI_VENDOR_ID_LENOVO, \
   .bios = TPID3(__id1, __id2, __id3), \
   .ec = TPACPI_MATCH_ANY,  \
   .quirks = (__quirk) }

#define TPACPI_QEC_IBM(__id1, __id2, __quirk) \
 { .vendor = PCI_VENDOR_ID_IBM,  \
   .bios = TPACPI_MATCH_ANY,  \
   .ec = TPID(__id1, __id2),  \
   .quirks = (__quirk) }

#define TPACPI_QEC_LNV(__id1, __id2, __quirk) \
 { .vendor = PCI_VENDOR_ID_LENOVO, \
   .bios = TPACPI_MATCH_ANY,  \
   .ec = TPID(__id1, __id2),  \
   .quirks = (__quirk) }

struct tpacpi_quirk {
 unsigned int vendor;
 u32 bios;
 u32 ec;
 unsigned long quirks;
};

/**
 * tpacpi_check_quirks() - search BIOS/EC version on a list
 * @qlist: array of &struct tpacpi_quirk
 * @qlist_size: number of elements in @qlist
 *
 * Iterates over a quirks list until one is found that matches the
 * ThinkPad's vendor, BIOS and EC model.
 *
 * Returns: %0 if nothing matches, otherwise returns the quirks field of
 * the matching &struct tpacpi_quirk entry.
 *
 * The match criteria is: vendor, ec and bios must match.
 */

static unsigned long __init tpacpi_check_quirks(
   const struct tpacpi_quirk *qlist,
   unsigned int qlist_size)
{
 while (qlist_size) {
  if ((qlist->vendor == thinkpad_id.vendor ||
    qlist->vendor == TPACPI_MATCH_ANY) &&
      (qlist->bios == thinkpad_id.bios_model ||
    qlist->bios == TPACPI_MATCH_ANY) &&
      (qlist->ec == thinkpad_id.ec_model ||
    qlist->ec == TPACPI_MATCH_ANY))
   return qlist->quirks;

  qlist_size--;
  qlist++;
 }
 return 0;
}

static __always_inline bool __pure __init tpacpi_is_lenovo(void)
{
 return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
}

static __always_inline bool __pure __init tpacpi_is_ibm(void)
{
 return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
}

/****************************************************************************
 ****************************************************************************
 *
 * ACPI Helpers and device model
 *
 ****************************************************************************
 ****************************************************************************/


/*************************************************************************
 * ACPI basic handles
 */


static acpi_handle root_handle;
static acpi_handle ec_handle;

#define TPACPI_HANDLE(object, parent, paths...)   \
 static acpi_handle  object##_handle;   \
 static const acpi_handle * const object##_parent __initconst = \
      &parent##_handle; \
 static char *object##_paths[] __initdata = { paths }

TPACPI_HANDLE(ecrd, ec, "ECRD"); /* 570 */
TPACPI_HANDLE(ecwr, ec, "ECWR"); /* 570 */

TPACPI_HANDLE(cmos, root, "\\UCMS"/* R50, R50e, R50p, R51, */
     /* T4x, X31, X40 */
    "\\CMOS",  /* A3x, G4x, R32, T23, T30, X22-24, X30 */
    "\\CMS",  /* R40, R40e */
    );   /* all others */

TPACPI_HANDLE(hkey, ec, "\\_SB.HKEY"/* 600e/x, 770e, 770x */
    "^HKEY",  /* R30, R31 */
    "HKEY",  /* all others */
    );   /* 570 */

/*************************************************************************
 * ACPI helpers
 */


static int acpi_evalf(acpi_handle handle,
        int *res, char *method, char *fmt, ...)
{
 char *fmt0 = fmt;
 struct acpi_object_list params;
 union acpi_object in_objs[TPACPI_MAX_ACPI_ARGS];
 struct acpi_buffer result, *resultp;
 union acpi_object out_obj;
 acpi_status status;
 va_list ap;
 char res_type;
 int success;
 int quiet;

 if (!*fmt) {
  pr_err("acpi_evalf() called with empty format\n");
  return 0;
 }

 if (*fmt == 'q') {
  quiet = 1;
  fmt++;
 } else
  quiet = 0;

 res_type = *(fmt++);

 params.count = 0;
 params.pointer = &in_objs[0];

 va_start(ap, fmt);
 while (*fmt) {
  char c = *(fmt++);
  switch (c) {
  case 'd'/* int */
   in_objs[params.count].integer.value = va_arg(ap, int);
   in_objs[params.count++].type = ACPI_TYPE_INTEGER;
   break;
   /* add more types as needed */
  default:
   pr_err("acpi_evalf() called with invalid format character '%c'\n",
          c);
   va_end(ap);
   return 0;
  }
 }
 va_end(ap);

 if (res_type != 'v') {
  result.length = sizeof(out_obj);
  result.pointer = &out_obj;
  resultp = &result;
 } else
  resultp = NULL;

 status = acpi_evaluate_object(handle, method, ¶ms, resultp);

 switch (res_type) {
 case 'd':  /* int */
  success = (status == AE_OK &&
      out_obj.type == ACPI_TYPE_INTEGER);
  if (success && res)
   *res = out_obj.integer.value;
  break;
 case 'v':  /* void */
  success = status == AE_OK;
  break;
  /* add more types as needed */
 default:
  pr_err("acpi_evalf() called with invalid format character '%c'\n",
         res_type);
  return 0;
 }

 if (!success && !quiet)
  pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
         method, fmt0, acpi_format_exception(status));

 return success;
}

static int acpi_ec_read(int i, u8 *p)
{
 int v;

 if (ecrd_handle) {
  if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
   return 0;
  *p = v;
 } else {
  if (ec_read(i, p) < 0)
   return 0;
 }

 return 1;
}

static int acpi_ec_write(int i, u8 v)
{
 if (ecwr_handle) {
  if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
   return 0;
 } else {
  if (ec_write(i, v) < 0)
   return 0;
 }

 return 1;
}

static int issue_thinkpad_cmos_command(int cmos_cmd)
{
 if (!cmos_handle)
  return -ENXIO;

 if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd))
  return -EIO;

 return 0;
}

/*************************************************************************
 * ACPI device model
 */


#define TPACPI_ACPIHANDLE_INIT(object) \
 drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
  object##_paths, ARRAY_SIZE(object##_paths))

static void __init drv_acpi_handle_init(const char *name,
      acpi_handle *handle, const acpi_handle parent,
      char **paths, const int num_paths)
{
 int i;
 acpi_status status;

 vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
  name);

 for (i = 0; i < num_paths; i++) {
  status = acpi_get_handle(parent, paths[i], handle);
  if (ACPI_SUCCESS(status)) {
   dbg_printk(TPACPI_DBG_INIT,
       "Found ACPI handle %s for %s\n",
       paths[i], name);
   return;
  }
 }

 vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
      name);
 *handle = NULL;
}

static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
   u32 level, void *context, void **return_value)
{
 if (!strcmp(context, "video")) {
  struct acpi_device *dev = acpi_fetch_acpi_dev(handle);

  if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev)))
   return AE_OK;
 }

 *(acpi_handle *)return_value = handle;

 return AE_CTRL_TERMINATE;
}

static void __init tpacpi_acpi_handle_locate(const char *name,
  const char *hid,
  acpi_handle *handle)
{
 acpi_status status;
 acpi_handle device_found;

 BUG_ON(!name || !handle);
 vdbg_printk(TPACPI_DBG_INIT,
   "trying to locate ACPI handle for %s, using HID %s\n",
   name, hid ? hid : "NULL");

 memset(&device_found, 0, sizeof(device_found));
 status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback,
      (void *)name, &device_found);

 *handle = NULL;

 if (ACPI_SUCCESS(status)) {
  *handle = device_found;
  dbg_printk(TPACPI_DBG_INIT,
      "Found ACPI handle for %s\n", name);
 } else {
  vdbg_printk(TPACPI_DBG_INIT,
       "Could not locate an ACPI handle for %s: %s\n",
       name, acpi_format_exception(status));
 }
}

static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
{
 struct ibm_struct *ibm = data;

 if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
  return;

 if (!ibm || !ibm->acpi || !ibm->acpi->notify)
  return;

 ibm->acpi->notify(ibm, event);
}

static int __init setup_acpi_notify(struct ibm_struct *ibm)
{
 acpi_status status;

 BUG_ON(!ibm->acpi);

 if (!*ibm->acpi->handle)
  return 0;

 vdbg_printk(TPACPI_DBG_INIT,
  "setting up ACPI notify for %s\n", ibm->name);

 ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle);
 if (!ibm->acpi->device) {
  pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name);
  return -ENODEV;
 }

 ibm->acpi->device->driver_data = ibm;
 scnprintf(acpi_device_class(ibm->acpi->device),
    sizeof(acpi_device_class(ibm->acpi->device)),
    "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name);

 status = acpi_install_notify_handler(*ibm->acpi->handle,
   ibm->acpi->type, dispatch_acpi_notify, ibm);
 if (ACPI_FAILURE(status)) {
  if (status == AE_ALREADY_EXISTS) {
   pr_notice("another device driver is already handling %s events\n",
      ibm->name);
  } else {
   pr_err("acpi_install_notify_handler(%s) failed: %s\n",
          ibm->name, acpi_format_exception(status));
  }
  return -ENODEV;
 }
 ibm->flags.acpi_notify_installed = 1;
 return 0;
}

static int __init tpacpi_device_add(struct acpi_device *device)
{
 return 0;
}

static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
{
 int rc;

 dbg_printk(TPACPI_DBG_INIT,
  "registering %s as an ACPI driver\n", ibm->name);

 BUG_ON(!ibm->acpi);

 ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
 if (!ibm->acpi->driver) {
  pr_err("failed to allocate memory for ibm->acpi->driver\n");
  return -ENOMEM;
 }

 sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_NAME, ibm->name);
 ibm->acpi->driver->ids = ibm->acpi->hid;

 ibm->acpi->driver->ops.add = &tpacpi_device_add;

 rc = acpi_bus_register_driver(ibm->acpi->driver);
 if (rc < 0) {
  pr_err("acpi_bus_register_driver(%s) failed: %d\n",
         ibm->name, rc);
  kfree(ibm->acpi->driver);
  ibm->acpi->driver = NULL;
 } else if (!rc)
  ibm->flags.acpi_driver_registered = 1;

 return rc;
}


/****************************************************************************
 ****************************************************************************
 *
 * Procfs Helpers
 *
 ****************************************************************************
 ****************************************************************************/


static int dispatch_proc_show(struct seq_file *m, void *v)
{
 struct ibm_struct *ibm = m->private;

 if (!ibm || !ibm->read)
  return -EINVAL;
 return ibm->read(m);
}

static int dispatch_proc_open(struct inode *inode, struct file *file)
{
 return single_open(file, dispatch_proc_show, pde_data(inode));
}

static ssize_t dispatch_proc_write(struct file *file,
   const char __user *userbuf,
   size_t count, loff_t *pos)
{
 struct ibm_struct *ibm = pde_data(file_inode(file));
 char *kernbuf;
 int ret;

 if (!ibm || !ibm->write)
  return -EINVAL;
 if (count > PAGE_SIZE - 1)
  return -EINVAL;

 kernbuf = memdup_user_nul(userbuf, count);
 if (IS_ERR(kernbuf))
  return PTR_ERR(kernbuf);
 ret = ibm->write(kernbuf);
 if (ret == 0)
  ret = count;

 kfree(kernbuf);

 return ret;
}

static const struct proc_ops dispatch_proc_ops = {
 .proc_open = dispatch_proc_open,
 .proc_read = seq_read,
 .proc_lseek = seq_lseek,
 .proc_release = single_release,
 .proc_write = dispatch_proc_write,
};

/****************************************************************************
 ****************************************************************************
 *
 * Device model: input, hwmon and platform
 *
 ****************************************************************************
 ****************************************************************************/


static struct platform_device *tpacpi_pdev;
static struct platform_device *tpacpi_sensors_pdev;
static struct device *tpacpi_hwmon;
static struct device *tpacpi_pprof;
static struct input_dev *tpacpi_inputdev;
static struct mutex tpacpi_inputdev_send_mutex;
static LIST_HEAD(tpacpi_all_drivers);

#ifdef CONFIG_PM_SLEEP
static int tpacpi_suspend_handler(struct device *dev)
{
 struct ibm_struct *ibm, *itmp;

 list_for_each_entry_safe(ibm, itmp,
     &tpacpi_all_drivers,
     all_drivers) {
  if (ibm->suspend)
   (ibm->suspend)();
 }

 return 0;
}

static int tpacpi_resume_handler(struct device *dev)
{
 struct ibm_struct *ibm, *itmp;

 list_for_each_entry_safe(ibm, itmp,
     &tpacpi_all_drivers,
     all_drivers) {
  if (ibm->resume)
   (ibm->resume)();
 }

 return 0;
}
#endif

static SIMPLE_DEV_PM_OPS(tpacpi_pm,
    tpacpi_suspend_handler, tpacpi_resume_handler);

static void tpacpi_shutdown_handler(struct platform_device *pdev)
{
 struct ibm_struct *ibm, *itmp;

 list_for_each_entry_safe(ibm, itmp,
     &tpacpi_all_drivers,
     all_drivers) {
  if (ibm->shutdown)
   (ibm->shutdown)();
 }
}

/*************************************************************************
 * sysfs support helpers
 */


static int parse_strtoul(const char *buf,
  unsigned long max, unsigned long *value)
{
 char *endp;

 *value = simple_strtoul(skip_spaces(buf), &endp, 0);
 endp = skip_spaces(endp);
 if (*endp || *value > max)
  return -EINVAL;

 return 0;
}

static void tpacpi_disable_brightness_delay(void)
{
 if (acpi_evalf(hkey_handle, NULL, "PWMS""qvd", 0))
  pr_notice("ACPI backlight control delay disabled\n");
}

static void printk_deprecated_attribute(const char * const what,
     const char * const details)
{
 tpacpi_log_usertask("deprecated sysfs attribute");
 pr_warn("WARNING: sysfs attribute %s is deprecated and will be removed. %s\n",
  what, details);
}

/*************************************************************************
 * rfkill and radio control support helpers
 */


/*
 * ThinkPad-ACPI firmware handling model:
 *
 * WLSW (master wireless switch) is event-driven, and is common to all
 * firmware-controlled radios.  It cannot be controlled, just monitored,
 * as expected.  It overrides all radio state in firmware
 *
 * The kernel, a masked-off hotkey, and WLSW can change the radio state
 * (TODO: verify how WLSW interacts with the returned radio state).
 *
 * The only time there are shadow radio state changes, is when
 * masked-off hotkeys are used.
 */


/*
 * Internal driver API for radio state:
 *
 * int: < 0 = error, otherwise enum tpacpi_rfkill_state
 * bool: true means radio blocked (off)
 */

enum tpacpi_rfkill_state {
 TPACPI_RFK_RADIO_OFF = 0,
 TPACPI_RFK_RADIO_ON
};

/* rfkill switches */
enum tpacpi_rfk_id {
 TPACPI_RFK_BLUETOOTH_SW_ID = 0,
 TPACPI_RFK_WWAN_SW_ID,
 TPACPI_RFK_UWB_SW_ID,
 TPACPI_RFK_SW_MAX
};

static const char *tpacpi_rfkill_names[] = {
 [TPACPI_RFK_BLUETOOTH_SW_ID] = "bluetooth",
 [TPACPI_RFK_WWAN_SW_ID] = "wwan",
 [TPACPI_RFK_UWB_SW_ID] = "uwb",
 [TPACPI_RFK_SW_MAX] = NULL
};

/* ThinkPad-ACPI rfkill subdriver */
struct tpacpi_rfk {
 struct rfkill *rfkill;
 enum tpacpi_rfk_id id;
 const struct tpacpi_rfk_ops *ops;
};

struct tpacpi_rfk_ops {
 /* firmware interface */
 int (*get_status)(void);
 int (*set_status)(const enum tpacpi_rfkill_state);
};

static struct tpacpi_rfk *tpacpi_rfkill_switches[TPACPI_RFK_SW_MAX];

/* Query FW and update rfkill sw state for a given rfkill switch */
static int tpacpi_rfk_update_swstate(const struct tpacpi_rfk *tp_rfk)
{
 int status;

 if (!tp_rfk)
  return -ENODEV;

 status = (tp_rfk->ops->get_status)();
 if (status < 0)
  return status;

 rfkill_set_sw_state(tp_rfk->rfkill,
       (status == TPACPI_RFK_RADIO_OFF));

 return status;
}

/*
 * Sync the HW-blocking state of all rfkill switches,
 * do notice it causes the rfkill core to schedule uevents
 */

static void tpacpi_rfk_update_hwblock_state(bool blocked)
{
 unsigned int i;
 struct tpacpi_rfk *tp_rfk;

 for (i = 0; i < TPACPI_RFK_SW_MAX; i++) {
  tp_rfk = tpacpi_rfkill_switches[i];
  if (tp_rfk) {
   if (rfkill_set_hw_state(tp_rfk->rfkill,
      blocked)) {
    /* ignore -- we track sw block */
   }
  }
 }
}

/* Call to get the WLSW state from the firmware */
static int hotkey_get_wlsw(void);

/* Call to query WLSW state and update all rfkill switches */
static bool tpacpi_rfk_check_hwblock_state(void)
{
 int res = hotkey_get_wlsw();
 int hw_blocked;

 /* When unknown or unsupported, we have to assume it is unblocked */
 if (res < 0)
  return false;

 hw_blocked = (res == TPACPI_RFK_RADIO_OFF);
 tpacpi_rfk_update_hwblock_state(hw_blocked);

 return hw_blocked;
}

static int tpacpi_rfk_hook_set_block(void *data, bool blocked)
{
 struct tpacpi_rfk *tp_rfk = data;
 int res;

 dbg_printk(TPACPI_DBG_RFKILL,
     "request to change radio state to %s\n",
     blocked ? "blocked" : "unblocked");

 /* try to set radio state */
 res = (tp_rfk->ops->set_status)(blocked ?
    TPACPI_RFK_RADIO_OFF : TPACPI_RFK_RADIO_ON);

 /* and update the rfkill core with whatever the FW really did */
 tpacpi_rfk_update_swstate(tp_rfk);

 return (res < 0) ? res : 0;
}

static const struct rfkill_ops tpacpi_rfk_rfkill_ops = {
 .set_block = tpacpi_rfk_hook_set_block,
};

static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
   const struct tpacpi_rfk_ops *tp_rfkops,
   const enum rfkill_type rfktype,
   const char *name,
   const bool set_default)
{
 struct tpacpi_rfk *atp_rfk;
 int res;
 bool sw_state = false;
 bool hw_state;
 int sw_status;

 BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);

 atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
 if (atp_rfk)
  atp_rfk->rfkill = rfkill_alloc(name,
      &tpacpi_pdev->dev,
      rfktype,
      &tpacpi_rfk_rfkill_ops,
      atp_rfk);
 if (!atp_rfk || !atp_rfk->rfkill) {
  pr_err("failed to allocate memory for rfkill class\n");
  kfree(atp_rfk);
  return -ENOMEM;
 }

 atp_rfk->id = id;
 atp_rfk->ops = tp_rfkops;

 sw_status = (tp_rfkops->get_status)();
 if (sw_status < 0) {
  pr_err("failed to read initial state for %s, error %d\n",
         name, sw_status);
 } else {
  sw_state = (sw_status == TPACPI_RFK_RADIO_OFF);
  if (set_default) {
   /* try to keep the initial state, since we ask the
 * firmware to preserve it across S5 in NVRAM */

   rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
  }
 }
 hw_state = tpacpi_rfk_check_hwblock_state();
 rfkill_set_hw_state(atp_rfk->rfkill, hw_state);

 res = rfkill_register(atp_rfk->rfkill);
 if (res < 0) {
  pr_err("failed to register %s rfkill switch: %d\n", name, res);
  rfkill_destroy(atp_rfk->rfkill);
  kfree(atp_rfk);
  return res;
 }

 tpacpi_rfkill_switches[id] = atp_rfk;

 pr_info("rfkill switch %s: radio is %sblocked\n",
  name, (sw_state || hw_state) ? "" : "un");
 return 0;
}

static void tpacpi_destroy_rfkill(const enum tpacpi_rfk_id id)
{
 struct tpacpi_rfk *tp_rfk;

 BUG_ON(id >= TPACPI_RFK_SW_MAX);

 tp_rfk = tpacpi_rfkill_switches[id];
 if (tp_rfk) {
  rfkill_unregister(tp_rfk->rfkill);
  rfkill_destroy(tp_rfk->rfkill);
  tpacpi_rfkill_switches[id] = NULL;
  kfree(tp_rfk);
 }
}

static void printk_deprecated_rfkill_attribute(const char * const what)
{
 printk_deprecated_attribute(what,
   "Please switch to generic rfkill before year 2010");
}

/* sysfs <radio> enable ------------------------------------------------ */
static ssize_t tpacpi_rfk_sysfs_enable_show(const enum tpacpi_rfk_id id,
         struct device_attribute *attr,
         char *buf)
{
 int status;

 printk_deprecated_rfkill_attribute(attr->attr.name);

 /* This is in the ABI... */
 if (tpacpi_rfk_check_hwblock_state()) {
  status = TPACPI_RFK_RADIO_OFF;
 } else {
  status = tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
  if (status < 0)
   return status;
 }

 return sysfs_emit(buf, "%d\n",
   (status == TPACPI_RFK_RADIO_ON) ? 1 : 0);
}

static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
       struct device_attribute *attr,
       const char *buf, size_t count)
{
 unsigned long t;
 int res;

 printk_deprecated_rfkill_attribute(attr->attr.name);

 if (parse_strtoul(buf, 1, &t))
  return -EINVAL;

 tpacpi_disclose_usertask(attr->attr.name, "set to %ld\n", t);

 /* This is in the ABI... */
 if (tpacpi_rfk_check_hwblock_state() && !!t)
  return -EPERM;

 res = tpacpi_rfkill_switches[id]->ops->set_status((!!t) ?
    TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF);
 tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);

 return (res < 0) ? res : count;
}

/* procfs -------------------------------------------------------------- */
static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
{
 if (id >= TPACPI_RFK_SW_MAX)
  seq_printf(m, "status:\t\tnot supported\n");
 else {
  int status;

  /* This is in the ABI... */
  if (tpacpi_rfk_check_hwblock_state()) {
   status = TPACPI_RFK_RADIO_OFF;
  } else {
   status = tpacpi_rfk_update_swstate(
      tpacpi_rfkill_switches[id]);
   if (status < 0)
    return status;
  }

  seq_printf(m, "status:\t\t%s\n", str_enabled_disabled(status == TPACPI_RFK_RADIO_ON));
  seq_printf(m, "commands:\tenable, disable\n");
 }

 return 0;
}

static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf)
{
 char *cmd;
 int status = -1;
 int res = 0;

 if (id >= TPACPI_RFK_SW_MAX)
  return -ENODEV;

 while ((cmd = strsep(&buf, ","))) {
  if (strstarts(cmd, "enable"))
   status = TPACPI_RFK_RADIO_ON;
  else if (strstarts(cmd, "disable"))
   status = TPACPI_RFK_RADIO_OFF;
  else
   return -EINVAL;
 }

 if (status != -1) {
  tpacpi_disclose_usertask("procfs""attempt to %s %s\n",
    str_enable_disable(status == TPACPI_RFK_RADIO_ON),
    tpacpi_rfkill_names[id]);
  res = (tpacpi_rfkill_switches[id]->ops->set_status)(status);
  tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
 }

 return res;
}

/*************************************************************************
 * thinkpad-acpi driver attributes
 */


/* interface_version --------------------------------------------------- */
static ssize_t interface_version_show(struct device_driver *drv, char *buf)
{
 return sysfs_emit(buf, "0x%08x\n", TPACPI_SYSFS_VERSION);
}
static DRIVER_ATTR_RO(interface_version);

/* debug_level --------------------------------------------------------- */
static ssize_t debug_level_show(struct device_driver *drv, char *buf)
{
 return sysfs_emit(buf, "0x%04x\n", dbg_level);
}

static ssize_t debug_level_store(struct device_driver *drv, const char *buf,
     size_t count)
{
 unsigned long t;

 if (parse_strtoul(buf, 0xffff, &t))
  return -EINVAL;

 dbg_level = t;

 return count;
}
static DRIVER_ATTR_RW(debug_level);

/* version ------------------------------------------------------------- */
static ssize_t version_show(struct device_driver *drv, char *buf)
{
 return sysfs_emit(buf, "%s v%s\n",
   TPACPI_DESC, TPACPI_VERSION);
}
static DRIVER_ATTR_RO(version);

/* --------------------------------------------------------------------- */

#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES

/* wlsw_emulstate ------------------------------------------------------ */
static ssize_t wlsw_emulstate_show(struct device_driver *drv, char *buf)
{
 return sysfs_emit(buf, "%d\n", !!tpacpi_wlsw_emulstate);
}

static ssize_t wlsw_emulstate_store(struct device_driver *drv, const char *buf,
        size_t count)
{
 unsigned long t;

 if (parse_strtoul(buf, 1, &t))
  return -EINVAL;

 if (tpacpi_wlsw_emulstate != !!t) {
  tpacpi_wlsw_emulstate = !!t;
  tpacpi_rfk_update_hwblock_state(!t); /* negative logic */
 }

 return count;
}
static DRIVER_ATTR_RW(wlsw_emulstate);

/* bluetooth_emulstate ------------------------------------------------- */
static ssize_t bluetooth_emulstate_show(struct device_driver *drv, char *buf)
{
 return sysfs_emit(buf, "%d\n", !!tpacpi_bluetooth_emulstate);
}

static ssize_t bluetooth_emulstate_store(struct device_driver *drv,
      const char *buf, size_t count)
{
 unsigned long t;

 if (parse_strtoul(buf, 1, &t))
  return -EINVAL;

 tpacpi_bluetooth_emulstate = !!t;

 return count;
}
static DRIVER_ATTR_RW(bluetooth_emulstate);

/* wwan_emulstate ------------------------------------------------- */
static ssize_t wwan_emulstate_show(struct device_driver *drv, char *buf)
{
 return sysfs_emit(buf, "%d\n", !!tpacpi_wwan_emulstate);
}

static ssize_t wwan_emulstate_store(struct device_driver *drv, const char *buf,
        size_t count)
{
 unsigned long t;

 if (parse_strtoul(buf, 1, &t))
  return -EINVAL;

 tpacpi_wwan_emulstate = !!t;

 return count;
}
static DRIVER_ATTR_RW(wwan_emulstate);

/* uwb_emulstate ------------------------------------------------- */
static ssize_t uwb_emulstate_show(struct device_driver *drv, char *buf)
{
 return sysfs_emit(buf, "%d\n", !!tpacpi_uwb_emulstate);
}

static ssize_t uwb_emulstate_store(struct device_driver *drv, const char *buf,
       size_t count)
{
 unsigned long t;

 if (parse_strtoul(buf, 1, &t))
  return -EINVAL;

 tpacpi_uwb_emulstate = !!t;

 return count;
}
static DRIVER_ATTR_RW(uwb_emulstate);
#endif

/*************************************************************************
 * Firmware Data
 */


/*
 * Table of recommended minimum BIOS versions
 *
 * Reasons for listing:
 *    1. Stable BIOS, listed because the unknown amount of
 *       bugs and bad ACPI behaviour on older versions
 *
 *    2. BIOS or EC fw with known bugs that trigger on Linux
 *
 *    3. BIOS with known reduced functionality in older versions
 *
 *  We recommend the latest BIOS and EC version.
 *  We only support the latest BIOS and EC fw version as a rule.
 *
 *  Sources: IBM ThinkPad Public Web Documents (update changelogs),
 *  Information from users in ThinkWiki
 *
 *  WARNING: we use this table also to detect that the machine is
 *  a ThinkPad in some cases, so don't remove entries lightly.
 */


#define TPV_Q(__v, __id1, __id2, __bv1, __bv2)  \
 { .vendor = (__v),   \
   .bios  = TPID(__id1, __id2),  \
   .ec  = TPACPI_MATCH_ANY,  \
   .quirks = TPACPI_MATCH_ANY_VERSION << 16 \
     | TPVER(__bv1, __bv2) }

#define TPV_Q_X(__v, __bid1, __bid2, __bv1, __bv2, \
  __eid, __ev1, __ev2)   \
 { .vendor = (__v),   \
   .bios  = TPID(__bid1, __bid2),  \
   .ec  = __eid,   \
   .quirks = TPVER(__ev1, __ev2) << 16 \
     | TPVER(__bv1, __bv2) }

#define TPV_QI0(__id1, __id2, __bv1, __bv2) \
 TPV_Q(PCI_VENDOR_ID_IBM, __id1, __id2, __bv1, __bv2)

/* Outdated IBM BIOSes often lack the EC id string */
#define TPV_QI1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
 TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2,  \
  __bv1, __bv2, TPID(__id1, __id2), \
  __ev1, __ev2),    \
 TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2,  \
  __bv1, __bv2, TPACPI_MATCH_UNKNOWN, \
  __ev1, __ev2)

/* Outdated IBM BIOSes often lack the EC id string */
#define TPV_QI2(__bid1, __bid2, __bv1, __bv2,  \
  __eid1, __eid2, __ev1, __ev2)   \
 TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2,  \
  __bv1, __bv2, TPID(__eid1, __eid2), \
  __ev1, __ev2),    \
 TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2,  \
  __bv1, __bv2, TPACPI_MATCH_UNKNOWN, \
  __ev1, __ev2)

#define TPV_QL0(__id1, __id2, __bv1, __bv2) \
 TPV_Q(PCI_VENDOR_ID_LENOVO, __id1, __id2, __bv1, __bv2)

#define TPV_QL1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
 TPV_Q_X(PCI_VENDOR_ID_LENOVO, __id1, __id2,  \
  __bv1, __bv2, TPID(__id1, __id2), \
  __ev1, __ev2)

#define TPV_QL2(__bid1, __bid2, __bv1, __bv2,  \
  __eid1, __eid2, __ev1, __ev2)   \
 TPV_Q_X(PCI_VENDOR_ID_LENOVO, __bid1, __bid2,  \
  __bv1, __bv2, TPID(__eid1, __eid2), \
  __ev1, __ev2)

static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
 /*  Numeric models ------------------ */
 /*      FW MODEL   BIOS VERS       */
 TPV_QI0('I''M',  '6''5'),   /* 570 */
 TPV_QI0('I''U',  '2''6'),   /* 570E */
 TPV_QI0('I''B',  '5''4'),   /* 600 */
 TPV_QI0('I''H',  '4''7'),   /* 600E */
 TPV_QI0('I''N',  '3''6'),   /* 600E */
 TPV_QI0('I''T',  '5''5'),   /* 600X */
 TPV_QI0('I''D',  '4''8'),   /* 770, 770E, 770ED */
 TPV_QI0('I''I',  '4''2'),   /* 770X */
 TPV_QI0('I''O',  '2''3'),   /* 770Z */

 /* A-series ------------------------- */
 /*      FW MODEL   BIOS VERS  EC VERS */
 TPV_QI0('I''W',  '5''9'),   /* A20m */
 TPV_QI0('I''V',  '6''9'),   /* A20p */
 TPV_QI0('1''0',  '2''6'),   /* A21e, A22e */
 TPV_QI0('K''U',  '3''6'),   /* A21e */
 TPV_QI0('K''X',  '3''6'),   /* A21m, A22m */
 TPV_QI0('K''Y',  '3''8'),   /* A21p, A22p */
 TPV_QI0('1''B',  '1''7'),   /* A22e */
 TPV_QI0('1''3',  '2''0'),   /* A22m */
 TPV_QI0('1''E',  '7''3'),   /* A30/p (0) */
 TPV_QI1('1''G',  '4''1',  '1''7'), /* A31/p (0) */
 TPV_QI1('1''N',  '1''6',  '0''7'), /* A31/p (0) */

 /* G-series ------------------------- */
 /*      FW MODEL   BIOS VERS       */
 TPV_QI0('1''T',  'A''6'),   /* G40 */
 TPV_QI0('1''X',  '5''7'),   /* G41 */

 /* R-series, T-series --------------- */
 /*      FW MODEL   BIOS VERS  EC VERS */
 TPV_QI0('1''C',  'F''0'),   /* R30 */
 TPV_QI0('1''F',  'F''1'),   /* R31 */
 TPV_QI0('1''M',  '9''7'),   /* R32 */
 TPV_QI0('1''O',  '6''1'),   /* R40 */
 TPV_QI0('1''P',  '6''5'),   /* R40 */
 TPV_QI0('1''S',  '7''0'),   /* R40e */
 TPV_QI1('1''R',  'D''R',  '7''1'), /* R50/p, R51,
    T40/p, T41/p, T42/p (1) */

 TPV_QI1('1''V',  '7''1',  '2''8'), /* R50e, R51 (1) */
 TPV_QI1('7''8',  '7''1',  '0''6'), /* R51e (1) */
 TPV_QI1('7''6',  '6''9',  '1''6'), /* R52 (1) */
 TPV_QI1('7''0',  '6''9',  '2''8'), /* R52, T43 (1) */

 TPV_QI0('I''Y',  '6''1'),   /* T20 */
 TPV_QI0('K''Z',  '3''4'),   /* T21 */
 TPV_QI0('1''6',  '3''2'),   /* T22 */
 TPV_QI1('1''A',  '6''4',  '2''3'), /* T23 (0) */
 TPV_QI1('1''I',  '7''1',  '2''0'), /* T30 (0) */
 TPV_QI1('1''Y',  '6''5',  '2''9'), /* T43/p (1) */

 TPV_QL1('7''9',  'E''3',  '5''0'), /* T60/p */
 TPV_QL1('7''C',  'D''2',  '2''2'), /* R60, R60i */
 TPV_QL1('7''E',  'D''0',  '1''5'), /* R60e, R60i */

 /*      BIOS FW    BIOS VERS  EC FW     EC VERS */
 TPV_QI2('1''W',  '9''0',  '1''V''2''8'), /* R50e (1) */
 TPV_QL2('7''I',  '3''4',  '7''9''5''0'), /* T60/p wide */

 /* X-series ------------------------- */
 /*      FW MODEL   BIOS VERS  EC VERS */
 TPV_QI0('I''Z',  '9''D'),   /* X20, X21 */
 TPV_QI0('1''D',  '7''0'),   /* X22, X23, X24 */
 TPV_QI1('1''K',  '4''8',  '1''8'), /* X30 (0) */
 TPV_QI1('1''Q',  '9''7',  '2''3'), /* X31, X32 (0) */
 TPV_QI1('1''U',  'D''3',  'B''2'), /* X40 (0) */
 TPV_QI1('7''4',  '6''4',  '2''7'), /* X41 (0) */
 TPV_QI1('7''5',  '6''0',  '2''0'), /* X41t (0) */

 TPV_QL1('7''B',  'D''7',  '4''0'), /* X60/s */
 TPV_QL1('7''J',  '3''0',  '1''3'), /* X60t */

 /* (0) - older versions lack DMI EC fw string and functionality */
 /* (1) - older versions known to lack functionality */
};

#undef TPV_QL1
#undef TPV_QL0
#undef TPV_QI2
#undef TPV_QI1
#undef TPV_QI0
#undef TPV_Q_X
#undef TPV_Q

static void __init tpacpi_check_outdated_fw(void)
{
 unsigned long fwvers;
 u16 ec_version, bios_version;

 fwvers = tpacpi_check_quirks(tpacpi_bios_version_qtable,
    ARRAY_SIZE(tpacpi_bios_version_qtable));

 if (!fwvers)
  return;

 bios_version = fwvers & 0xffffU;
 ec_version = (fwvers >> 16) & 0xffffU;

 /* note that unknown versions are set to 0x0000 and we use that */
 if ((bios_version > thinkpad_id.bios_release) ||
     (ec_version > thinkpad_id.ec_release &&
    ec_version != TPACPI_MATCH_ANY_VERSION)) {
  /*
 * The changelogs would let us track down the exact
 * reason, but it is just too much of a pain to track
 * it.  We only list BIOSes that are either really
 * broken, or really stable to begin with, so it is
 * best if the user upgrades the firmware anyway.
 */

  pr_warn("WARNING: Outdated ThinkPad BIOS/EC firmware\n");
  pr_warn("WARNING: This firmware may be missing critical bug fixes and/or important features\n");
 }
}

static bool __init tpacpi_is_fw_known(void)
{
 return tpacpi_check_quirks(tpacpi_bios_version_qtable,
   ARRAY_SIZE(tpacpi_bios_version_qtable)) != 0;
}

/****************************************************************************
 ****************************************************************************
 *
 * Subdrivers
 *
 ****************************************************************************
 ****************************************************************************/


/*************************************************************************
 * thinkpad-acpi metadata subdriver
 */


static int thinkpad_acpi_driver_read(struct seq_file *m)
{
 seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
 seq_printf(m, "version:\t%s\n", TPACPI_VERSION);
 return 0;
}

static struct ibm_struct thinkpad_acpi_driver_data = {
 .name = "driver",
 .read = thinkpad_acpi_driver_read,
};

/*************************************************************************
 * Hotkey subdriver
 */


/*
 * ThinkPad firmware event model
 *
 * The ThinkPad firmware has two main event interfaces: normal ACPI
 * notifications (which follow the ACPI standard), and a private event
 * interface.
 *
 * The private event interface also issues events for the hotkeys.  As
 * the driver gained features, the event handling code ended up being
 * built around the hotkey subdriver.  This will need to be refactored
 * to a more formal event API eventually.
 *
 * Some "hotkeys" are actually supposed to be used as event reports,
 * such as "brightness has changed", "volume has changed", depending on
 * the ThinkPad model and how the firmware is operating.
 *
 * Unlike other classes, hotkey-class events have mask/unmask control on
 * non-ancient firmware.  However, how it behaves changes a lot with the
 * firmware model and version.
 */


enum { /* hot key scan codes (derived from ACPI DSDT) */
 TP_ACPI_HOTKEYSCAN_FNF1  = 0,
 TP_ACPI_HOTKEYSCAN_FNF2,
 TP_ACPI_HOTKEYSCAN_FNF3,
 TP_ACPI_HOTKEYSCAN_FNF4,
 TP_ACPI_HOTKEYSCAN_FNF5,
 TP_ACPI_HOTKEYSCAN_FNF6,
 TP_ACPI_HOTKEYSCAN_FNF7,
 TP_ACPI_HOTKEYSCAN_FNF8,
 TP_ACPI_HOTKEYSCAN_FNF9,
 TP_ACPI_HOTKEYSCAN_FNF10,
 TP_ACPI_HOTKEYSCAN_FNF11,
 TP_ACPI_HOTKEYSCAN_FNF12,
 TP_ACPI_HOTKEYSCAN_FNBACKSPACE,
 TP_ACPI_HOTKEYSCAN_FNINSERT,
 TP_ACPI_HOTKEYSCAN_FNDELETE,
 TP_ACPI_HOTKEYSCAN_FNHOME,
 TP_ACPI_HOTKEYSCAN_FNEND,
 TP_ACPI_HOTKEYSCAN_FNPAGEUP,
 TP_ACPI_HOTKEYSCAN_FNPAGEDOWN,
 TP_ACPI_HOTKEYSCAN_FNSPACE,
 TP_ACPI_HOTKEYSCAN_VOLUMEUP,
 TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
 TP_ACPI_HOTKEYSCAN_MUTE,
 TP_ACPI_HOTKEYSCAN_THINKPAD,
 TP_ACPI_HOTKEYSCAN_UNK1,
 TP_ACPI_HOTKEYSCAN_UNK2,
 TP_ACPI_HOTKEYSCAN_MICMUTE,
 TP_ACPI_HOTKEYSCAN_UNK4,
 TP_ACPI_HOTKEYSCAN_CONFIG,
 TP_ACPI_HOTKEYSCAN_SEARCH,
 TP_ACPI_HOTKEYSCAN_SCALE,
 TP_ACPI_HOTKEYSCAN_FILE,

 /* Adaptive keyboard keycodes */
 TP_ACPI_HOTKEYSCAN_ADAPTIVE_START, /* 32 / 0x20 */
 TP_ACPI_HOTKEYSCAN_MUTE2        = TP_ACPI_HOTKEYSCAN_ADAPTIVE_START,
 TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO,
 TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL,
 TP_ACPI_HOTKEYSCAN_CLOUD,
 TP_ACPI_HOTKEYSCAN_UNK9,
 TP_ACPI_HOTKEYSCAN_VOICE,
 TP_ACPI_HOTKEYSCAN_UNK10,
 TP_ACPI_HOTKEYSCAN_GESTURES,
 TP_ACPI_HOTKEYSCAN_UNK11,
 TP_ACPI_HOTKEYSCAN_UNK12,
 TP_ACPI_HOTKEYSCAN_UNK13,
 TP_ACPI_HOTKEYSCAN_CONFIG2,
 TP_ACPI_HOTKEYSCAN_NEW_TAB,
 TP_ACPI_HOTKEYSCAN_RELOAD,
 TP_ACPI_HOTKEYSCAN_BACK,
 TP_ACPI_HOTKEYSCAN_MIC_DOWN,
 TP_ACPI_HOTKEYSCAN_MIC_UP,
 TP_ACPI_HOTKEYSCAN_MIC_CANCELLATION,
 TP_ACPI_HOTKEYSCAN_CAMERA_MODE,
 TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY,

 /* Lenovo extended keymap, starting at 0x1300 */
 TP_ACPI_HOTKEYSCAN_EXTENDED_START, /* 52 / 0x34 */
 /* first new observed key (star, favorites) is 0x1311 */
 TP_ACPI_HOTKEYSCAN_STAR = 69,
 TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL2,
 TP_ACPI_HOTKEYSCAN_CALCULATOR,
 TP_ACPI_HOTKEYSCAN_BLUETOOTH,
 TP_ACPI_HOTKEYSCAN_KEYBOARD,
 TP_ACPI_HOTKEYSCAN_FN_RIGHT_SHIFT, /* Used by "Lenovo Quick Clean" */
 TP_ACPI_HOTKEYSCAN_NOTIFICATION_CENTER,
 TP_ACPI_HOTKEYSCAN_PICKUP_PHONE,
 TP_ACPI_HOTKEYSCAN_HANGUP_PHONE,
};

enum { /* Keys/events available through NVRAM polling */
 TPACPI_HKEY_NVRAM_KNOWN_MASK = 0x00fb88c0U,
 TPACPI_HKEY_NVRAM_GOOD_MASK  = 0x00fb8000U,
};

enum { /* Positions of some of the keys in hotkey masks */
 TP_ACPI_HKEY_DISPSWTCH_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF7,
 TP_ACPI_HKEY_DISPXPAND_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF8,
 TP_ACPI_HKEY_HIBERNATE_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF12,
 TP_ACPI_HKEY_BRGHTUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNHOME,
 TP_ACPI_HKEY_BRGHTDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNEND,
 TP_ACPI_HKEY_KBD_LIGHT_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNPAGEUP,
 TP_ACPI_HKEY_ZOOM_MASK  = 1 << TP_ACPI_HOTKEYSCAN_FNSPACE,
 TP_ACPI_HKEY_VOLUP_MASK  = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEUP,
 TP_ACPI_HKEY_VOLDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
 TP_ACPI_HKEY_MUTE_MASK  = 1 << TP_ACPI_HOTKEYSCAN_MUTE,
 TP_ACPI_HKEY_THINKPAD_MASK = 1 << TP_ACPI_HOTKEYSCAN_THINKPAD,
};

enum { /* NVRAM to ACPI HKEY group map */
 TP_NVRAM_HKEY_GROUP_HK2  = TP_ACPI_HKEY_THINKPAD_MASK |
       TP_ACPI_HKEY_ZOOM_MASK |
       TP_ACPI_HKEY_DISPSWTCH_MASK |
       TP_ACPI_HKEY_HIBERNATE_MASK,
 TP_NVRAM_HKEY_GROUP_BRIGHTNESS = TP_ACPI_HKEY_BRGHTUP_MASK |
       TP_ACPI_HKEY_BRGHTDWN_MASK,
 TP_NVRAM_HKEY_GROUP_VOLUME = TP_ACPI_HKEY_VOLUP_MASK |
       TP_ACPI_HKEY_VOLDWN_MASK |
       TP_ACPI_HKEY_MUTE_MASK,
};

#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
struct tp_nvram_state {
       u16 thinkpad_toggle:1;
       u16 zoom_toggle:1;
       u16 display_toggle:1;
       u16 thinklight_toggle:1;
       u16 hibernate_toggle:1;
       u16 displayexp_toggle:1;
       u16 display_state:1;
       u16 brightness_toggle:1;
       u16 volume_toggle:1;
       u16 mute:1;

       u8 brightness_level;
       u8 volume_level;
};

/* kthread for the hotkey poller */
static struct task_struct *tpacpi_hotkey_task;

/*
 * Acquire mutex to write poller control variables as an
 * atomic block.
 *
 * Increment hotkey_config_change when changing them if you
 * want the kthread to forget old state.
 *
 * See HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
 */

static struct mutex hotkey_thread_data_mutex;
static unsigned int hotkey_config_change;

/*
 * hotkey poller control variables
 *
 * Must be atomic or readers will also need to acquire mutex
 *
 * HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
 * should be used only when the changes need to be taken as
 * a block, OR when one needs to force the kthread to forget
 * old state.
 */

static u32 hotkey_source_mask;  /* bit mask 0=ACPI,1=NVRAM */
static unsigned int hotkey_poll_freq = 10; /* Hz */

#define HOTKEY_CONFIG_CRITICAL_START \
 do { \
  mutex_lock(&hotkey_thread_data_mutex); \
  hotkey_config_change++; \
 } while (0);
#define HOTKEY_CONFIG_CRITICAL_END \
 mutex_unlock(&hotkey_thread_data_mutex);

#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */

#define hotkey_source_mask 0U
#define HOTKEY_CONFIG_CRITICAL_START
#define HOTKEY_CONFIG_CRITICAL_END

#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */

static struct mutex hotkey_mutex;

static enum { /* Reasons for waking up */
 TP_ACPI_WAKEUP_NONE = 0, /* None or unknown */
 TP_ACPI_WAKEUP_BAYEJ,  /* Bay ejection request */
 TP_ACPI_WAKEUP_UNDOCK,  /* Undock request */
} hotkey_wakeup_reason;

static int hotkey_autosleep_ack;

static u32 hotkey_orig_mask;  /* events the BIOS had enabled */
static u32 hotkey_all_mask;  /* all events supported in fw */
static u32 hotkey_adaptive_all_mask; /* all adaptive events supported in fw */
static u32 hotkey_reserved_mask; /* events better left disabled */
static u32 hotkey_driver_mask;  /* events needed by the driver */
static u32 hotkey_user_mask;  /* events visible to userspace */
static u32 hotkey_acpi_mask;  /* events enabled in firmware */

static bool tpacpi_driver_event(const unsigned int hkey_event);
static void hotkey_poll_setup(const bool may_warn);

/* HKEY.MHKG() return bits */
#define TP_HOTKEY_TABLET_MASK (1 << 3)
enum {
 TP_ACPI_MULTI_MODE_INVALID = 0,
 TP_ACPI_MULTI_MODE_UNKNOWN = 1 << 0,
 TP_ACPI_MULTI_MODE_LAPTOP = 1 << 1,
 TP_ACPI_MULTI_MODE_TABLET = 1 << 2,
 TP_ACPI_MULTI_MODE_FLAT  = 1 << 3,
 TP_ACPI_MULTI_MODE_STAND = 1 << 4,
 TP_ACPI_MULTI_MODE_TENT  = 1 << 5,
 TP_ACPI_MULTI_MODE_STAND_TENT = 1 << 6,
};

enum {
 /* The following modes are considered tablet mode for the purpose of
 * reporting the status to userspace. i.e. in all these modes it makes
 * sense to disable the laptop input devices such as touchpad and
 * keyboard.
 */

 TP_ACPI_MULTI_MODE_TABLET_LIKE = TP_ACPI_MULTI_MODE_TABLET |
       TP_ACPI_MULTI_MODE_STAND |
       TP_ACPI_MULTI_MODE_TENT |
       TP_ACPI_MULTI_MODE_STAND_TENT,
};

static int hotkey_get_wlsw(void)
{
 int status;

 if (!tp_features.hotkey_wlsw)
  return -ENODEV;

#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
 if (dbg_wlswemul)
  return (tpacpi_wlsw_emulstate) ?
    TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
#endif

 if (!acpi_evalf(hkey_handle, &status, "WLSW""d"))
  return -EIO;

 return (status) ? TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
}

static int hotkey_gmms_get_tablet_mode(int s, int *has_tablet_mode)
{
 int type = (s >> 16) & 0xffff;
 int value = s & 0xffff;
 int mode = TP_ACPI_MULTI_MODE_INVALID;
 int valid_modes = 0;

 if (has_tablet_mode)
  *has_tablet_mode = 0;

 switch (type) {
 case 1:
  valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
         TP_ACPI_MULTI_MODE_TABLET |
         TP_ACPI_MULTI_MODE_STAND_TENT;
  break;
 case 2:
  valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
         TP_ACPI_MULTI_MODE_FLAT |
         TP_ACPI_MULTI_MODE_TABLET |
         TP_ACPI_MULTI_MODE_STAND |
         TP_ACPI_MULTI_MODE_TENT;
  break;
 case 3:
  valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
         TP_ACPI_MULTI_MODE_FLAT;
  break;
 case 4:
 case 5:
  /* In mode 4, FLAT is not specified as a valid mode. However,
 * it can be seen at least on the X1 Yoga 2nd Generation.
 */

  valid_modes = TP_ACPI_MULTI_MODE_LAPTOP |
         TP_ACPI_MULTI_MODE_FLAT |
         TP_ACPI_MULTI_MODE_TABLET |
         TP_ACPI_MULTI_MODE_STAND |
         TP_ACPI_MULTI_MODE_TENT;
  break;
 default:
  pr_err("Unknown multi mode status type %d with value 0x%04X, please report this to %s\n",
         type, value, TPACPI_MAIL);
  return 0;
 }

 if (has_tablet_mode && (valid_modes & TP_ACPI_MULTI_MODE_TABLET_LIKE))
  *has_tablet_mode = 1;

 switch (value) {
 case 1:
  mode = TP_ACPI_MULTI_MODE_LAPTOP;
  break;
 case 2:
  mode = TP_ACPI_MULTI_MODE_FLAT;
  break;
 case 3:
  mode = TP_ACPI_MULTI_MODE_TABLET;
  break;
 case 4:
  if (type == 1)
   mode = TP_ACPI_MULTI_MODE_STAND_TENT;
  else
   mode = TP_ACPI_MULTI_MODE_STAND;
  break;
 case 5:
  mode = TP_ACPI_MULTI_MODE_TENT;
  break;
 default:
  if (type == 5 && value == 0xffff) {
   pr_warn("Multi mode status is undetected, assuming laptop\n");
   return 0;
  }
 }

 if (!(mode & valid_modes)) {
  pr_err("Unknown/reserved multi mode value 0x%04X for type %d, please report this to %s\n",
         value, type, TPACPI_MAIL);
  return 0;
 }

 return !!(mode & TP_ACPI_MULTI_MODE_TABLET_LIKE);
}

static int hotkey_get_tablet_mode(int *status)
{
 int s;

 switch (tp_features.hotkey_tablet) {
 case TP_HOTKEY_TABLET_USES_MHKG:
  if (!acpi_evalf(hkey_handle, &s, "MHKG""d"))
   return -EIO;

  *status = ((s & TP_HOTKEY_TABLET_MASK) != 0);
  break;
 case TP_HOTKEY_TABLET_USES_GMMS:
  if (!acpi_evalf(hkey_handle, &s, "GMMS""dd", 0))
   return -EIO;

  *status = hotkey_gmms_get_tablet_mode(s, NULL);
  break;
 default:
  break;
 }

 return 0;
}

/*
 * Reads current event mask from firmware, and updates
 * hotkey_acpi_mask accordingly.  Also resets any bits
 * from hotkey_user_mask that are unavailable to be
 * delivered (shadow requirement of the userspace ABI).
 */

static int hotkey_mask_get(void)
{
 lockdep_assert_held(&hotkey_mutex);

 if (tp_features.hotkey_mask) {
  u32 m = 0;

  if (!acpi_evalf(hkey_handle, &m, "DHKN""d"))
   return -EIO;

  hotkey_acpi_mask = m;
 } else {
  /* no mask support doesn't mean no event support... */
  hotkey_acpi_mask = hotkey_all_mask;
 }

 /* sync userspace-visible mask */
 hotkey_user_mask &= (hotkey_acpi_mask | hotkey_source_mask);

 return 0;
}

static void hotkey_mask_warn_incomplete_mask(void)
{
 /* log only what the user can fix... */
 const u32 wantedmask = hotkey_driver_mask &
  ~(hotkey_acpi_mask | hotkey_source_mask) &
  (hotkey_all_mask | TPACPI_HKEY_NVRAM_KNOWN_MASK);

 if (wantedmask)
  pr_notice("required events 0x%08x not enabled!\n", wantedmask);
}

/*
 * Set the firmware mask when supported
 *
 * Also calls hotkey_mask_get to update hotkey_acpi_mask.
 *
 * NOTE: does not set bits in hotkey_user_mask, but may reset them.
 */

static int hotkey_mask_set(u32 mask)
{
 int i;
 int rc = 0;

 const u32 fwmask = mask & ~hotkey_source_mask;

 lockdep_assert_held(&hotkey_mutex);

 if (tp_features.hotkey_mask) {
  for (i = 0; i < 32; i++) {
   if (!acpi_evalf(hkey_handle,
     NULL, "MHKM""vdd", i + 1,
     !!(mask & (1 << i)))) {
    rc = -EIO;
    break;
   }
  }
 }

 /*
 * We *must* make an inconditional call to hotkey_mask_get to
 * refresh hotkey_acpi_mask and update hotkey_user_mask
 *
 * Take the opportunity to also log when we cannot _enable_
 * a given event.
 */

 if (!hotkey_mask_get() && !rc && (fwmask & ~hotkey_acpi_mask)) {
  pr_notice("asked for hotkey mask 0x%08x, but firmware forced it to 0x%08x\n",
     fwmask, hotkey_acpi_mask);
 }

 if (tpacpi_lifecycle != TPACPI_LIFE_EXITING)
  hotkey_mask_warn_incomplete_mask();

 return rc;
}

/*
 * Sets hotkey_user_mask and tries to set the firmware mask
 */

static int hotkey_user_mask_set(const u32 mask)
{
 int rc;

 lockdep_assert_held(&hotkey_mutex);

 /* Give people a chance to notice they are doing something that
 * is bound to go boom on their users sooner or later */

 if (!tp_warned.hotkey_mask_ff &&
     (mask == 0xffff || mask == 0xffffff ||
      mask == 0xffffffff)) {
  tp_warned.hotkey_mask_ff = 1;
  pr_notice("setting the hotkey mask to 0x%08x is likely not the best way to go about it\n",
     mask);
  pr_notice("please consider using the driver defaults, and refer to up-to-date thinkpad-acpi documentation\n");
 }

 /* Try to enable what the user asked for, plus whatever we need.
 * this syncs everything but won't enable bits in hotkey_user_mask */

 rc = hotkey_mask_set((mask | hotkey_driver_mask) & ~hotkey_source_mask);

 /* Enable the available bits in hotkey_user_mask */
 hotkey_user_mask = mask & (hotkey_acpi_mask | hotkey_source_mask);

 return rc;
}

/*
 * Sets the driver hotkey mask.
 *
 * Can be called even if the hotkey subdriver is inactive
 */

static int tpacpi_hotkey_driver_mask_set(const u32 mask)
{
 int rc;

 /* Do the right thing if hotkey_init has not been called yet */
 if (!tp_features.hotkey) {
  hotkey_driver_mask = mask;
  return 0;
 }

 mutex_lock(&hotkey_mutex);

 HOTKEY_CONFIG_CRITICAL_START
 hotkey_driver_mask = mask;
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
 hotkey_source_mask |= (mask & ~hotkey_all_mask);
#endif
 HOTKEY_CONFIG_CRITICAL_END

 rc = hotkey_mask_set((hotkey_acpi_mask | hotkey_driver_mask) &
       ~hotkey_source_mask);
 hotkey_poll_setup(true);

 mutex_unlock(&hotkey_mutex);

 return rc;
}

static int hotkey_status_get(int *status)
{
 if (!acpi_evalf(hkey_handle, status, "DHKC""d"))
  return -EIO;

 return 0;
}

static int hotkey_status_set(bool enable)
{
 if (!acpi_evalf(hkey_handle, NULL, "MHKC""vd", enable ? 1 : 0))
  return -EIO;

 return 0;
}

static void tpacpi_input_send_tabletsw(void)
{
 int state;

 if (tp_features.hotkey_tablet &&
     !hotkey_get_tablet_mode(&state)) {
  mutex_lock(&tpacpi_inputdev_send_mutex);

  input_report_switch(tpacpi_inputdev,
        SW_TABLET_MODE, !!state);
  input_sync(tpacpi_inputdev);

  mutex_unlock(&tpacpi_inputdev_send_mutex);
 }
}

#define GCES_NO_SHUTTER_DEVICE BIT(31)

static int get_camera_shutter(void)
{
 acpi_handle gces_handle;
 int output;

 if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GCES", &gces_handle)))
  return -ENODEV;

 if (!acpi_evalf(gces_handle, &output, NULL, "dd", 0))
  return -EIO;

 if (output & GCES_NO_SHUTTER_DEVICE)
  return -ENODEV;

 return output;
}

static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev)
{
 bool known_ev;
 u32 scancode;

 if (tpacpi_driver_event(hkey))
  return true;

 /*
 * Before the conversion to using the sparse-keymap helpers the driver used to
 * map the hkey event codes to 0x00 - 0x4d scancodes so that a straight scancode
 * indexed array could be used to map scancodes to keycodes:
 *
 * 0x1001 - 0x1020  ->  0x00 - 0x1f  (Original ThinkPad events)
 * 0x1103 - 0x1116  ->  0x20 - 0x33  (Adaptive keyboard, 2014 X1 Carbon)
 * 0x1300 - 0x1319  ->  0x34 - 0x4d  (Additional keys send in 2017+ models)
 *
 * The sparse-keymap tables still use these scancodes for these ranges to
 * preserve userspace API compatibility (e.g. hwdb keymappings).
 */

 if (hkey >= TP_HKEY_EV_ORIG_KEY_START &&
     hkey <= TP_HKEY_EV_ORIG_KEY_END) {
  scancode = hkey - TP_HKEY_EV_ORIG_KEY_START;
  if (!(hotkey_user_mask & (1 << scancode)))
   return true/* Not reported but still a known code */
 } else if (hkey >= TP_HKEY_EV_ADAPTIVE_KEY_START &&
     hkey <= TP_HKEY_EV_ADAPTIVE_KEY_END) {
  scancode = hkey - TP_HKEY_EV_ADAPTIVE_KEY_START +
      TP_ACPI_HOTKEYSCAN_ADAPTIVE_START;
 } else if (hkey >= TP_HKEY_EV_EXTENDED_KEY_START &&
     hkey <= TP_HKEY_EV_EXTENDED_KEY_END) {
  scancode = hkey - TP_HKEY_EV_EXTENDED_KEY_START +
      TP_ACPI_HOTKEYSCAN_EXTENDED_START;
 } else {
  /*
 * Do not send ACPI netlink events for unknown hotkeys, to
 * avoid userspace starting to rely on them. Instead these
 * should be added to the keymap to send evdev events.
 */

  if (send_acpi_ev)
   *send_acpi_ev = false;

  scancode = hkey;
 }

 mutex_lock(&tpacpi_inputdev_send_mutex);
 known_ev = sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true);
 mutex_unlock(&tpacpi_inputdev_send_mutex);

 return known_ev;
}

#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;

/* Do NOT call without validating scancode first */
static void tpacpi_hotkey_send_key(unsigned int scancode)
{
 tpacpi_input_send_key(TP_HKEY_EV_ORIG_KEY_START + scancode, NULL);
}

static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m)
{
 u8 d;

 if (m & TP_NVRAM_HKEY_GROUP_HK2) {
  d = nvram_read_byte(TP_NVRAM_ADDR_HK2);
  n->thinkpad_toggle = !!(d & TP_NVRAM_MASK_HKT_THINKPAD);
  n->zoom_toggle = !!(d & TP_NVRAM_MASK_HKT_ZOOM);
  n->display_toggle = !!(d & TP_NVRAM_MASK_HKT_DISPLAY);
  n->hibernate_toggle = !!(d & TP_NVRAM_MASK_HKT_HIBERNATE);
 }
 if (m & TP_ACPI_HKEY_KBD_LIGHT_MASK) {
  d = nvram_read_byte(TP_NVRAM_ADDR_THINKLIGHT);
  n->thinklight_toggle = !!(d & TP_NVRAM_MASK_THINKLIGHT);
 }
 if (m & TP_ACPI_HKEY_DISPXPAND_MASK) {
  d = nvram_read_byte(TP_NVRAM_ADDR_VIDEO);
  n->displayexp_toggle =
    !!(d & TP_NVRAM_MASK_HKT_DISPEXPND);
 }
 if (m & TP_NVRAM_HKEY_GROUP_BRIGHTNESS) {
  d = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=88 H=97 G=92

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge