};
static struct {
-#ifdef CONFIG_THINKPAD_ACPI_BAY
- u32 bay_status:1;
- u32 bay_eject:1;
- u32 bay_status2:1;
- u32 bay_eject2:1;
-#endif
u32 bluetooth:1;
u32 hotkey:1;
u32 hotkey_mask:1;
return 1;
}
-#if defined(CONFIG_THINKPAD_ACPI_DOCK) || defined(CONFIG_THINKPAD_ACPI_BAY)
-static int _sta(acpi_handle handle)
-{
- int status;
-
- if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
- status = 0;
-
- return status;
-}
-#endif
-
static int issue_thinkpad_cmos_command(int cmos_cmd)
{
if (!cmos_handle)
if (!ibm || !ibm->write)
return -EINVAL;
+ if (count > PAGE_SIZE - 2)
+ return -EINVAL;
kernbuf = kmalloc(count + 2, GFP_KERNEL);
if (!kernbuf)
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);
}
#endif
}
+/*************************************************************************
+ * Firmware Data
+ */
+
+/*
+ * Table of recommended minimum BIOS versions
+ *
+ * Reasons for listing:
+ * 1. Stable BIOS, listed because the unknown ammount 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 << 16 \
+ | (__bv1) << 8 | (__bv2) }
+
+#define TPV_Q_X(__v, __bid1, __bid2, __bv1, __bv2, \
+ __eid1, __eid2, __ev1, __ev2) \
+ { .vendor = (__v), \
+ .bios = TPID(__bid1, __bid2), \
+ .ec = TPID(__eid1, __eid2), \
+ .quirks = (__ev1) << 24 | (__ev2) << 16 \
+ | (__bv1) << 8 | (__bv2) }
+
+#define TPV_QI0(__id1, __id2, __bv1, __bv2) \
+ TPV_Q(PCI_VENDOR_ID_IBM, __id1, __id2, __bv1, __bv2)
+
+#define TPV_QI1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
+ TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2, \
+ __bv1, __bv2, __id1, __id2, __ev1, __ev2)
+
+#define TPV_QI2(__bid1, __bid2, __bv1, __bv2, \
+ __eid1, __eid2, __ev1, __ev2) \
+ TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2, \
+ __bv1, __bv2, __eid1, __eid2, __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, __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, __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_QL0('7', 'E', 'D', '0'), /* 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_QL0('7', 'B', 'D', '7'), /* X60/s */
+ TPV_QL0('7', 'J', '3', '0'), /* 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)) {
+ /*
+ * 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.
+ */
+ printk(TPACPI_WARN
+ "WARNING: Outdated ThinkPad BIOS/EC firmware\n");
+ printk(TPACPI_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;
+}
+
/****************************************************************************
****************************************************************************
*
(thinkpad_id.nummodel_str) ?
thinkpad_id.nummodel_str : "unknown");
+ tpacpi_check_outdated_fw();
return 0;
}
u8 volume_level;
};
+/* kthread for the hotkey poller */
static struct task_struct *tpacpi_hotkey_task;
-static u32 hotkey_source_mask; /* bit mask 0=ACPI,1=NVRAM */
-static int hotkey_poll_freq = 10; /* Hz */
+
+/* Acquired while the poller kthread is running, use to sync start/stop */
static struct mutex hotkey_thread_mutex;
+
+/*
+ * Acquire mutex to write poller control variables.
+ * Increment hotkey_config_change when changing them.
+ *
+ * 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
+ */
+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 attribute_set *hotkey_dev_attributes;
-#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
-#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
-#define HOTKEY_CONFIG_CRITICAL_START
-#define HOTKEY_CONFIG_CRITICAL_END
-#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
-
/* HKEY.MHKG() return bits */
#define TP_HOTKEY_TABLET_MASK (1 << 3)
if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
return -EIO;
}
+ HOTKEY_CONFIG_CRITICAL_START
hotkey_mask = m | (hotkey_source_mask & hotkey_mask);
+ HOTKEY_CONFIG_CRITICAL_END
return 0;
}
unsigned int si, so;
unsigned long t;
unsigned int change_detector, must_reset;
+ unsigned int poll_freq;
mutex_lock(&hotkey_thread_mutex);
mutex_lock(&hotkey_thread_data_mutex);
change_detector = hotkey_config_change;
mask = hotkey_source_mask & hotkey_mask;
+ poll_freq = hotkey_poll_freq;
mutex_unlock(&hotkey_thread_data_mutex);
hotkey_read_nvram(&s[so], mask);
- while (!kthread_should_stop() && hotkey_poll_freq) {
- if (t == 0)
- t = 1000/hotkey_poll_freq;
+ while (!kthread_should_stop()) {
+ if (t == 0) {
+ if (likely(poll_freq))
+ t = 1000/poll_freq;
+ else
+ t = 100; /* should never happen... */
+ }
t = msleep_interruptible(t);
if (unlikely(kthread_should_stop()))
break;
change_detector = hotkey_config_change;
}
mask = hotkey_source_mask & hotkey_mask;
+ poll_freq = hotkey_poll_freq;
mutex_unlock(&hotkey_thread_data_mutex);
if (likely(mask)) {
return 0;
}
+/* call with hotkey_mutex held */
static void hotkey_poll_stop_sync(void)
{
if (tpacpi_hotkey_task) {
}
/* call with hotkey_mutex held */
-static void hotkey_poll_setup(int may_warn)
+static void hotkey_poll_setup(bool may_warn)
{
- if ((hotkey_source_mask & hotkey_mask) != 0 &&
- hotkey_poll_freq > 0 &&
+ u32 hotkeys_to_poll = hotkey_source_mask & hotkey_mask;
+
+ if (hotkeys_to_poll != 0 && hotkey_poll_freq > 0 &&
(tpacpi_inputdev->users > 0 || hotkey_report_mode < 2)) {
if (!tpacpi_hotkey_task) {
tpacpi_hotkey_task = kthread_run(hotkey_kthread,
}
} else {
hotkey_poll_stop_sync();
- if (may_warn &&
- hotkey_source_mask != 0 && hotkey_poll_freq == 0) {
+ if (may_warn && hotkeys_to_poll != 0 &&
+ hotkey_poll_freq == 0) {
printk(TPACPI_NOTICE
"hot keys 0x%08x require polling, "
"which is currently disabled\n",
- hotkey_source_mask);
+ hotkeys_to_poll);
}
}
}
-static void hotkey_poll_setup_safe(int may_warn)
+static void hotkey_poll_setup_safe(bool may_warn)
{
mutex_lock(&hotkey_mutex);
hotkey_poll_setup(may_warn);
mutex_unlock(&hotkey_mutex);
}
+/* call with hotkey_mutex held */
+static void hotkey_poll_set_freq(unsigned int freq)
+{
+ if (!freq)
+ hotkey_poll_stop_sync();
+
+ HOTKEY_CONFIG_CRITICAL_START
+ hotkey_poll_freq = freq;
+ HOTKEY_CONFIG_CRITICAL_END
+}
+
#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
-static void hotkey_poll_setup_safe(int __unused)
+static void hotkey_poll_setup_safe(bool __unused)
{
}
case TPACPI_LIFE_EXITING:
return -EBUSY;
case TPACPI_LIFE_RUNNING:
- hotkey_poll_setup_safe(0);
+ hotkey_poll_setup_safe(false);
return 0;
}
{
/* disable hotkey polling when possible */
if (tpacpi_lifecycle == TPACPI_LIFE_RUNNING)
- hotkey_poll_setup_safe(0);
+ hotkey_poll_setup_safe(false);
}
/* sysfs hotkey enable ------------------------------------------------- */
res = hotkey_mask_set(t);
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
- hotkey_poll_setup(1);
+ hotkey_poll_setup(true);
#endif
mutex_unlock(&hotkey_mutex);
struct device_attribute *attr,
char *buf)
{
+ printk_deprecated_attribute("hotkey_bios_mask",
+ "This attribute is useless.");
return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask);
}
hotkey_source_mask = t;
HOTKEY_CONFIG_CRITICAL_END
- hotkey_poll_setup(1);
+ hotkey_poll_setup(true);
+ hotkey_mask_set(hotkey_mask);
mutex_unlock(&hotkey_mutex);
if (mutex_lock_killable(&hotkey_mutex))
return -ERESTARTSYS;
- hotkey_poll_freq = t;
+ hotkey_poll_set_freq(t);
+ hotkey_poll_setup(true);
- hotkey_poll_setup(1);
mutex_unlock(&hotkey_mutex);
tpacpi_disclose_usertask("hotkey_poll_freq", "set to %lu\n", t);
static void hotkey_exit(void)
{
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ mutex_lock(&hotkey_mutex);
hotkey_poll_stop_sync();
+ mutex_unlock(&hotkey_mutex);
#endif
if (hotkey_dev_attributes)
}
}
+static void __init hotkey_unmap(const unsigned int scancode)
+{
+ if (hotkey_keycode_map[scancode] != KEY_RESERVED) {
+ clear_bit(hotkey_keycode_map[scancode],
+ tpacpi_inputdev->keybit);
+ hotkey_keycode_map[scancode] = KEY_RESERVED;
+ }
+}
+
static int __init hotkey_init(struct ibm_init_struct *iibm)
{
/* Requirements for changing the default keymaps:
KEY_UNKNOWN, /* 0x0D: FN+INSERT */
KEY_UNKNOWN, /* 0x0E: FN+DELETE */
- /* These either have to go through ACPI video, or
- * act like in the IBM ThinkPads, so don't ever
- * enable them by default */
- KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */
- KEY_RESERVED, /* 0x10: FN+END (brightness down) */
+ /* These should be enabled --only-- when ACPI video
+ * is disabled (i.e. in "vendor" mode), and are handled
+ * in a special way by the init code */
+ KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */
+ KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */
KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
goto err_exit;
}
-#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
- if (tp_features.hotkey_mask) {
- hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
- & ~hotkey_all_mask;
- } else {
- hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK;
- }
-
- vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
- "hotkey source mask 0x%08x, polling freq %d\n",
- hotkey_source_mask, hotkey_poll_freq);
-#endif
-
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
if (dbg_wlswemul) {
tp_features.hotkey_wlsw = 1;
"Disabling thinkpad-acpi brightness events "
"by default...\n");
- /* The hotkey_reserved_mask change below is not
- * necessary while the keys are at KEY_RESERVED in the
- * default map, but better safe than sorry, leave it
- * here as a marker of what we have to do, especially
- * when we finally become able to set this at runtime
- * on response to X.org requests */
+ /* Disable brightness up/down on Lenovo thinkpads when
+ * ACPI is handling them, otherwise it is plain impossible
+ * for userspace to do something even remotely sane */
hotkey_reserved_mask |=
(1 << TP_ACPI_HOTKEYSCAN_FNHOME)
| (1 << TP_ACPI_HOTKEYSCAN_FNEND);
+ hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNHOME);
+ hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNEND);
}
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ if (tp_features.hotkey_mask) {
+ hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
+ & ~hotkey_all_mask
+ & ~hotkey_reserved_mask;
+ } else {
+ hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
+ & ~hotkey_reserved_mask;
+ }
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "hotkey source mask 0x%08x, polling freq %u\n",
+ hotkey_source_mask, hotkey_poll_freq);
+#endif
+
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
"enabling firmware HKEY event interface...\n");
res = hotkey_status_set(true);
tpacpi_inputdev->open = &hotkey_inputdev_open;
tpacpi_inputdev->close = &hotkey_inputdev_close;
- hotkey_poll_setup_safe(1);
+ hotkey_poll_setup_safe(true);
tpacpi_send_radiosw_update();
tpacpi_input_send_tabletsw();
hotkey_tablet_mode_notify_change();
hotkey_wakeup_reason_notify_change();
hotkey_wakeup_hotunplug_complete_notify_change();
- hotkey_poll_setup_safe(0);
+ hotkey_poll_setup_safe(false);
}
/* procfs -------------------------------------------------------------- */
hotkey_enabledisable_warn(0);
res = -EPERM;
} else if (strlencmp(cmd, "reset") == 0) {
- mask = hotkey_orig_mask;
+ mask = (hotkey_all_mask | hotkey_source_mask)
+ & ~hotkey_reserved_mask;
} else if (sscanf(cmd, "0x%x", &mask) == 1) {
/* mask set */
} else if (sscanf(cmd, "%x", &mask) == 1) {
.exit = light_exit,
};
-/*************************************************************************
- * Dock subdriver
- */
-
-#ifdef CONFIG_THINKPAD_ACPI_DOCK
-
-static void dock_notify(struct ibm_struct *ibm, u32 event);
-static int dock_read(char *p);
-static int dock_write(char *buf);
-
-TPACPI_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */
- "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */
- "\\_SB.PCI0.PCI1.DOCK", /* all others */
- "\\_SB.PCI.ISA.SLCE", /* 570 */
- ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */
-
-/* don't list other alternatives as we install a notify handler on the 570 */
-TPACPI_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */
-
-static const struct acpi_device_id ibm_pci_device_ids[] = {
- {PCI_ROOT_HID_STRING, 0},
- {"", 0},
-};
-
-static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = {
- {
- .notify = dock_notify,
- .handle = &dock_handle,
- .type = ACPI_SYSTEM_NOTIFY,
- },
- {
- /* THIS ONE MUST NEVER BE USED FOR DRIVER AUTOLOADING.
- * We just use it to get notifications of dock hotplug
- * in very old thinkpads */
- .hid = ibm_pci_device_ids,
- .notify = dock_notify,
- .handle = &pci_handle,
- .type = ACPI_SYSTEM_NOTIFY,
- },
-};
-
-static struct ibm_struct dock_driver_data[2] = {
- {
- .name = "dock",
- .read = dock_read,
- .write = dock_write,
- .acpi = &ibm_dock_acpidriver[0],
- },
- {
- .name = "dock",
- .acpi = &ibm_dock_acpidriver[1],
- },
-};
-
-#define dock_docked() (_sta(dock_handle) & 1)
-
-static int __init dock_init(struct ibm_init_struct *iibm)
-{
- vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n");
-
- TPACPI_ACPIHANDLE_INIT(dock);
-
- vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n",
- str_supported(dock_handle != NULL));
-
- return (dock_handle)? 0 : 1;
-}
-
-static int __init dock_init2(struct ibm_init_struct *iibm)
-{
- int dock2_needed;
-
- vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver part 2\n");
-
- if (dock_driver_data[0].flags.acpi_driver_registered &&
- dock_driver_data[0].flags.acpi_notify_installed) {
- TPACPI_ACPIHANDLE_INIT(pci);
- dock2_needed = (pci_handle != NULL);
- vdbg_printk(TPACPI_DBG_INIT,
- "dock PCI handler for the TP 570 is %s\n",
- str_supported(dock2_needed));
- } else {
- vdbg_printk(TPACPI_DBG_INIT,
- "dock subdriver part 2 not required\n");
- dock2_needed = 0;
- }
-
- return (dock2_needed)? 0 : 1;
-}
-
-static void dock_notify(struct ibm_struct *ibm, u32 event)
-{
- int docked = dock_docked();
- int pci = ibm->acpi->hid && ibm->acpi->device &&
- acpi_match_device_ids(ibm->acpi->device, ibm_pci_device_ids);
- int data;
-
- if (event == 1 && !pci) /* 570 */
- data = 1; /* button */
- else if (event == 1 && pci) /* 570 */
- data = 3; /* dock */
- else if (event == 3 && docked)
- data = 1; /* button */
- else if (event == 3 && !docked)
- data = 2; /* undock */
- else if (event == 0 && docked)
- data = 3; /* dock */
- else {
- printk(TPACPI_ERR "unknown dock event %d, status %d\n",
- event, _sta(dock_handle));
- data = 0; /* unknown */
- }
- acpi_bus_generate_proc_event(ibm->acpi->device, event, data);
- acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
- dev_name(&ibm->acpi->device->dev),
- event, data);
-}
-
-static int dock_read(char *p)
-{
- int len = 0;
- int docked = dock_docked();
-
- if (!dock_handle)
- len += sprintf(p + len, "status:\t\tnot supported\n");
- else if (!docked)
- len += sprintf(p + len, "status:\t\tundocked\n");
- else {
- len += sprintf(p + len, "status:\t\tdocked\n");
- len += sprintf(p + len, "commands:\tdock, undock\n");
- }
-
- return len;
-}
-
-static int dock_write(char *buf)
-{
- char *cmd;
-
- if (!dock_docked())
- return -ENODEV;
-
- while ((cmd = next_cmd(&buf))) {
- if (strlencmp(cmd, "undock") == 0) {
- if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) ||
- !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1))
- return -EIO;
- } else if (strlencmp(cmd, "dock") == 0) {
- if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1))
- return -EIO;
- } else
- return -EINVAL;
- }
-
- return 0;
-}
-
-#endif /* CONFIG_THINKPAD_ACPI_DOCK */
-
-/*************************************************************************
- * Bay subdriver
- */
-
-#ifdef CONFIG_THINKPAD_ACPI_BAY
-
-TPACPI_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */
- "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */
- "\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */
- "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */
- ); /* A21e, R30, R31 */
-TPACPI_HANDLE(bay_ej, bay, "_EJ3", /* 600e/x, A2xm/p, A3x */
- "_EJ0", /* all others */
- ); /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */
-TPACPI_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV", /* A3x, R32 */
- "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */
- ); /* all others */
-TPACPI_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */
- "_EJ0", /* 770x */
- ); /* all others */
-
-static int __init bay_init(struct ibm_init_struct *iibm)
-{
- vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n");
-
- TPACPI_ACPIHANDLE_INIT(bay);
- if (bay_handle)
- TPACPI_ACPIHANDLE_INIT(bay_ej);
- TPACPI_ACPIHANDLE_INIT(bay2);
- if (bay2_handle)
- TPACPI_ACPIHANDLE_INIT(bay2_ej);
-
- tp_features.bay_status = bay_handle &&
- acpi_evalf(bay_handle, NULL, "_STA", "qv");
- tp_features.bay_status2 = bay2_handle &&
- acpi_evalf(bay2_handle, NULL, "_STA", "qv");
-
- tp_features.bay_eject = bay_handle && bay_ej_handle &&
- (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental);
- tp_features.bay_eject2 = bay2_handle && bay2_ej_handle &&
- (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental);
-
- vdbg_printk(TPACPI_DBG_INIT,
- "bay 1: status %s, eject %s; bay 2: status %s, eject %s\n",
- str_supported(tp_features.bay_status),
- str_supported(tp_features.bay_eject),
- str_supported(tp_features.bay_status2),
- str_supported(tp_features.bay_eject2));
-
- return (tp_features.bay_status || tp_features.bay_eject ||
- tp_features.bay_status2 || tp_features.bay_eject2)? 0 : 1;
-}
-
-static void bay_notify(struct ibm_struct *ibm, u32 event)
-{
- acpi_bus_generate_proc_event(ibm->acpi->device, event, 0);
- acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
- dev_name(&ibm->acpi->device->dev),
- event, 0);
-}
-
-#define bay_occupied(b) (_sta(b##_handle) & 1)
-
-static int bay_read(char *p)
-{
- int len = 0;
- int occupied = bay_occupied(bay);
- int occupied2 = bay_occupied(bay2);
- int eject, eject2;
-
- len += sprintf(p + len, "status:\t\t%s\n",
- tp_features.bay_status ?
- (occupied ? "occupied" : "unoccupied") :
- "not supported");
- if (tp_features.bay_status2)
- len += sprintf(p + len, "status2:\t%s\n", occupied2 ?
- "occupied" : "unoccupied");
-
- eject = tp_features.bay_eject && occupied;
- eject2 = tp_features.bay_eject2 && occupied2;
-
- if (eject && eject2)
- len += sprintf(p + len, "commands:\teject, eject2\n");
- else if (eject)
- len += sprintf(p + len, "commands:\teject\n");
- else if (eject2)
- len += sprintf(p + len, "commands:\teject2\n");
-
- return len;
-}
-
-static int bay_write(char *buf)
-{
- char *cmd;
-
- if (!tp_features.bay_eject && !tp_features.bay_eject2)
- return -ENODEV;
-
- while ((cmd = next_cmd(&buf))) {
- if (tp_features.bay_eject && strlencmp(cmd, "eject") == 0) {
- if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1))
- return -EIO;
- } else if (tp_features.bay_eject2 &&
- strlencmp(cmd, "eject2") == 0) {
- if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1))
- return -EIO;
- } else
- return -EINVAL;
- }
-
- return 0;
-}
-
-static struct tp_acpi_drv_struct ibm_bay_acpidriver = {
- .notify = bay_notify,
- .handle = &bay_handle,
- .type = ACPI_SYSTEM_NOTIFY,
-};
-
-static struct ibm_struct bay_driver_data = {
- .name = "bay",
- .read = bay_read,
- .write = bay_write,
- .acpi = &ibm_bay_acpidriver,
-};
-
-#endif /* CONFIG_THINKPAD_ACPI_BAY */
-
/*************************************************************************
* CMOS subdriver
*/
/* --------------------------------------------------------------------- */
+/*
+ * These are only useful for models that have only one possibility
+ * of GPU. If the BIOS model handles both ATI and Intel, don't use
+ * these quirks.
+ */
+#define TPACPI_BRGHT_Q_NOEC 0x0001 /* Must NOT use EC HBRV */
+#define TPACPI_BRGHT_Q_EC 0x0002 /* Should or must use EC HBRV */
+#define TPACPI_BRGHT_Q_ASK 0x8000 /* Ask for user report */
+
+static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
+ /* Models with ATI GPUs known to require ECNVRAM mode */
+ TPACPI_Q_IBM('1', 'Y', TPACPI_BRGHT_Q_EC), /* T43/p ATI */
+
+ /* Models with ATI GPUs that can use ECNVRAM */
+ TPACPI_Q_IBM('1', 'R', TPACPI_BRGHT_Q_EC),
+ TPACPI_Q_IBM('1', 'Q', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+ TPACPI_Q_IBM('7', '6', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+ TPACPI_Q_IBM('7', '8', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+
+ /* Models with Intel Extreme Graphics 2 */
+ TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC),
+ TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC),
+ TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC),
+
+ /* Models with Intel GMA900 */
+ TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC), /* T43, R52 */
+ TPACPI_Q_IBM('7', '4', TPACPI_BRGHT_Q_NOEC), /* X41 */
+ TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC), /* X41 Tablet */
+};
+
static int __init brightness_init(struct ibm_init_struct *iibm)
{
int b;
+ unsigned long quirks;
vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
mutex_init(&brightness_mutex);
+ quirks = tpacpi_check_quirks(brightness_quirk_table,
+ ARRAY_SIZE(brightness_quirk_table));
+
/*
* We always attempt to detect acpi support, so as to switch
* Lenovo Vista BIOS to ACPI brightness mode even if we are not
/* TPACPI_BRGHT_MODE_AUTO not implemented yet, just use default */
if (brightness_mode == TPACPI_BRGHT_MODE_AUTO ||
brightness_mode == TPACPI_BRGHT_MODE_MAX) {
- if (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) {
- /*
- * IBM models that define HBRV probably have
- * EC-based backlight level control
- */
- if (acpi_evalf(ec_handle, NULL, "HBRV", "qd"))
- /* T40-T43, R50-R52, R50e, R51e, X31-X41 */
- brightness_mode = TPACPI_BRGHT_MODE_ECNVRAM;
- else
- /* all other IBM ThinkPads */
- brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
- } else
- /* All Lenovo ThinkPads */
+ if (quirks & TPACPI_BRGHT_Q_EC)
+ brightness_mode = TPACPI_BRGHT_MODE_ECNVRAM;
+ else
brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
dbg_printk(TPACPI_DBG_BRGHT,
- "selected brightness_mode=%d\n",
+ "driver auto-selected brightness_mode=%d\n",
brightness_mode);
}
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
"brightness is supported\n");
+ if (quirks & TPACPI_BRGHT_Q_ASK) {
+ printk(TPACPI_NOTICE
+ "brightness: will use unverified default: "
+ "brightness_mode=%d\n", brightness_mode);
+ printk(TPACPI_NOTICE
+ "brightness: please report to %s whether it works well "
+ "or not on your ThinkPad\n", TPACPI_MAIL);
+ }
+
ibm_backlight_device->props.max_brightness =
(tp_features.bright_16levels)? 15 : 7;
ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
/*
* Non-ancient models have better DMI tagging, but very old models
- * don't.
+ * don't. tpacpi_is_fw_known() is a cheat to help in that case.
*/
- is_thinkpad = (thinkpad_id.model_str != NULL);
+ is_thinkpad = (thinkpad_id.model_str != NULL) ||
+ (thinkpad_id.ec_model != 0) ||
+ tpacpi_is_fw_known();
/* ec is required because many other handles are relative to it */
TPACPI_ACPIHANDLE_INIT(ec);
return -ENODEV;
}
- /*
- * Risks a regression on very old machines, but reduces potential
- * false positives a damn great deal
- */
- if (!is_thinkpad)
- is_thinkpad = (thinkpad_id.vendor == PCI_VENDOR_ID_IBM);
-
if (!is_thinkpad && !force_load)
return -ENODEV;
.init = light_init,
.data = &light_driver_data,
},
-#ifdef CONFIG_THINKPAD_ACPI_DOCK
- {
- .init = dock_init,
- .data = &dock_driver_data[0],
- },
- {
- .init = dock_init2,
- .data = &dock_driver_data[1],
- },
-#endif
-#ifdef CONFIG_THINKPAD_ACPI_BAY
- {
- .init = bay_init,
- .data = &bay_driver_data,
- },
-#endif
{
.init = cmos_init,
.data = &cmos_driver_data,
TPACPI_PARAM(bluetooth);
TPACPI_PARAM(video);
TPACPI_PARAM(light);
-#ifdef CONFIG_THINKPAD_ACPI_DOCK
-TPACPI_PARAM(dock);
-#endif
-#ifdef CONFIG_THINKPAD_ACPI_BAY
-TPACPI_PARAM(bay);
-#endif /* CONFIG_THINKPAD_ACPI_BAY */
TPACPI_PARAM(cmos);
TPACPI_PARAM(led);
TPACPI_PARAM(beep);