+ u32 m = 0;
+
+ if (tp_features.hotkey_mask) {
+ if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
+ return -EIO;
+ }
+ hotkey_mask = m | (hotkey_source_mask & hotkey_mask);
+
+ return 0;
+}
+
+/*
+ * Call with hotkey_mutex held
+ */
+static int hotkey_mask_set(u32 mask)
+{
+ int i;
+ int rc = 0;
+
+ if (tp_features.hotkey_mask) {
+ HOTKEY_CONFIG_CRITICAL_START
+ for (i = 0; i < 32; i++) {
+ u32 m = 1 << i;
+ /* enable in firmware mask only keys not in NVRAM
+ * mode, but enable the key in the cached hotkey_mask
+ * regardless of mode, or the key will end up
+ * disabled by hotkey_mask_get() */
+ if (!acpi_evalf(hkey_handle,
+ NULL, "MHKM", "vdd", i + 1,
+ !!((mask & ~hotkey_source_mask) & m))) {
+ rc = -EIO;
+ break;
+ } else {
+ hotkey_mask = (hotkey_mask & ~m) | (mask & m);
+ }
+ }
+ HOTKEY_CONFIG_CRITICAL_END
+
+ /* hotkey_mask_get must be called unconditionally below */
+ if (!hotkey_mask_get() && !rc &&
+ (hotkey_mask & ~hotkey_source_mask) !=
+ (mask & ~hotkey_source_mask)) {
+ printk(TPACPI_NOTICE
+ "requested hot key mask 0x%08x, but "
+ "firmware forced it to 0x%08x\n",
+ mask, hotkey_mask);
+ }
+ } else {
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ HOTKEY_CONFIG_CRITICAL_START
+ hotkey_mask = mask & hotkey_source_mask;
+ HOTKEY_CONFIG_CRITICAL_END
+ hotkey_mask_get();
+ if (hotkey_mask != mask) {
+ printk(TPACPI_NOTICE
+ "requested hot key mask 0x%08x, "
+ "forced to 0x%08x (NVRAM poll mask is "
+ "0x%08x): no firmware mask support\n",
+ mask, hotkey_mask, hotkey_source_mask);
+ }
+#else
+ hotkey_mask_get();
+ rc = -ENXIO;
+#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+ }
+
+ 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(int status)
+{
+ if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status))
+ return -EIO;
+
+ return 0;
+}
+
+static void tpacpi_input_send_radiosw(void)
+{
+ int wlsw;
+
+ mutex_lock(&tpacpi_inputdev_send_mutex);
+
+ if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
+ input_report_switch(tpacpi_inputdev,
+ SW_RADIO, !!wlsw);
+ input_sync(tpacpi_inputdev);
+ }
+
+ mutex_unlock(&tpacpi_inputdev_send_mutex);
+}
+
+static void tpacpi_input_send_key(unsigned int scancode)
+{
+ unsigned int keycode;
+
+ keycode = hotkey_keycode_map[scancode];
+
+ if (keycode != KEY_RESERVED) {
+ mutex_lock(&tpacpi_inputdev_send_mutex);
+
+ input_report_key(tpacpi_inputdev, keycode, 1);
+ if (keycode == KEY_UNKNOWN)
+ input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
+ scancode);
+ input_sync(tpacpi_inputdev);
+
+ input_report_key(tpacpi_inputdev, keycode, 0);
+ if (keycode == KEY_UNKNOWN)
+ input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
+ scancode);
+ input_sync(tpacpi_inputdev);
+
+ mutex_unlock(&tpacpi_inputdev_send_mutex);
+ }
+}
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;
+
+static void tpacpi_hotkey_send_key(unsigned int scancode)
+{
+ tpacpi_input_send_key(scancode);
+ if (hotkey_report_mode < 2) {
+ acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device,
+ 0x80, 0x1001 + scancode);
+ }
+}
+
+static void hotkey_read_nvram(struct tp_nvram_state *n, 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_THNKLGHT_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);
+ n->brightness_level = (d & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+ >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+ n->brightness_toggle =
+ !!(d & TP_NVRAM_MASK_HKT_BRIGHTNESS);
+ }
+ if (m & TP_NVRAM_HKEY_GROUP_VOLUME) {
+ d = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+ n->volume_level = (d & TP_NVRAM_MASK_LEVEL_VOLUME)
+ >> TP_NVRAM_POS_LEVEL_VOLUME;
+ n->mute = !!(d & TP_NVRAM_MASK_MUTE);
+ n->volume_toggle = !!(d & TP_NVRAM_MASK_HKT_VOLUME);
+ }
+}
+
+#define TPACPI_COMPARE_KEY(__scancode, __member) \
+ do { \
+ if ((mask & (1 << __scancode)) && \
+ oldn->__member != newn->__member) \
+ tpacpi_hotkey_send_key(__scancode); \
+ } while (0)
+
+#define TPACPI_MAY_SEND_KEY(__scancode) \
+ do { if (mask & (1 << __scancode)) \
+ tpacpi_hotkey_send_key(__scancode); } while (0)
+
+static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
+ struct tp_nvram_state *newn,
+ u32 mask)
+{
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF12, hibernate_toggle);
+
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNPAGEUP, thinklight_toggle);
+
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
+
+ /* handle volume */
+ if (oldn->volume_toggle != newn->volume_toggle) {
+ if (oldn->mute != newn->mute) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
+ }
+ if (oldn->volume_level > newn->volume_level) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+ } else if (oldn->volume_level < newn->volume_level) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+ } else if (oldn->mute == newn->mute) {
+ /* repeated key presses that didn't change state */
+ if (newn->mute) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
+ } else if (newn->volume_level != 0) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+ } else {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+ }
+ }
+ }
+
+ /* handle brightness */
+ if (oldn->brightness_toggle != newn->brightness_toggle) {
+ if (oldn->brightness_level < newn->brightness_level) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+ } else if (oldn->brightness_level > newn->brightness_level) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+ } else {
+ /* repeated key presses that didn't change state */
+ if (newn->brightness_level != 0) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+ } else {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+ }
+ }
+ }
+}
+
+#undef TPACPI_COMPARE_KEY
+#undef TPACPI_MAY_SEND_KEY
+
+static int hotkey_kthread(void *data)
+{
+ struct tp_nvram_state s[2];