diff options
Diffstat (limited to 'drivers/platform/x86')
55 files changed, 26507 insertions, 5831 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index db32c25e360..172f26ce59a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -5,6 +5,7 @@ menuconfig X86_PLATFORM_DEVICES bool "X86 Platform Specific Device Drivers" default y + depends on X86 ---help--- Say Y here to get to see options for device drivers for various x86 platforms, including vendor-specific laptop extension drivers. @@ -17,26 +18,27 @@ if X86_PLATFORM_DEVICES config ACER_WMI tristate "Acer WMI Laptop Extras" depends on ACPI - depends on LEDS_CLASS - depends on NEW_LEDS + select LEDS_CLASS + select NEW_LEDS depends on BACKLIGHT_CLASS_DEVICE depends on SERIO_I8042 + depends on INPUT depends on RFKILL || RFKILL = n - select ACPI_WMI + depends on ACPI_WMI + select INPUT_SPARSEKMAP + # Acer WMI depends on ACPI_VIDEO when ACPI is enabled + select ACPI_VIDEO if ACPI ---help--- This is a driver for newer Acer (and Wistron) laptops. It adds wireless radio and bluetooth control, and on some laptops, exposes the mail LED and LCD backlight. - For more information about this driver see - <file:Documentation/laptops/acer-wmi.txt> - If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M here. config ACERHDF tristate "Acer Aspire One temperature and fan driver" - depends on THERMAL && THERMAL_HWMON && ACPI + depends on THERMAL && ACPI ---help--- This is a driver for Acer Aspire One netbooks. It allows to access the temperature sensor and to control the fan. @@ -51,49 +53,91 @@ config ACERHDF If you have an Acer Aspire One netbook, say Y or M here. +config ALIENWARE_WMI + tristate "Alienware Special feature control" + depends on ACPI + depends on LEDS_CLASS + depends on NEW_LEDS + depends on ACPI_WMI + ---help--- + This is a driver for controlling Alienware BIOS driven + features. It exposes an interface for controlling the AlienFX + zones on Alienware machines that don't contain a dedicated AlienFX + USB MCU such as the X51 and X51-R2. + config ASUS_LAPTOP tristate "Asus Laptop Extras" depends on ACPI - depends on !ACPI_ASUS select LEDS_CLASS select NEW_LEDS select BACKLIGHT_CLASS_DEVICE depends on INPUT + depends on RFKILL || RFKILL = n + select INPUT_SPARSEKMAP + select INPUT_POLLDEV ---help--- - This is the new Linux driver for Asus laptops. It may also support some - MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate - standard ACPI events and input events. It also adds - support for video output switching, LCD backlight control, Bluetooth and - Wlan control, and most importantly, allows you to blink those fancy LEDs. + This is a driver for Asus laptops, Lenovo SL and the Pegatron + Lucid tablet. It may also support some MEDION, JVC or VICTOR + laptops. It makes all the extra buttons generate standard + ACPI events and input events, and on the Lucid the built-in + accelerometer appears as an input device. It also adds + support for video output switching, LCD backlight control, + Bluetooth and Wlan control, and most importantly, allows you + to blink those fancy LEDs. - For more information and a userspace daemon for handling the extra - buttons see <http://acpi4asus.sf.net>. + For more information see <http://acpi4asus.sf.net>. If you have an ACPI-compatible ASUS laptop, say Y or M here. config DELL_LAPTOP - tristate "Dell Laptop Extras (EXPERIMENTAL)" + tristate "Dell Laptop Extras" depends on X86 depends on DCDBAS - depends on EXPERIMENTAL depends on BACKLIGHT_CLASS_DEVICE depends on RFKILL || RFKILL = n - depends on POWER_SUPPLY + depends on SERIO_I8042 + select POWER_SUPPLY + select LEDS_CLASS + select NEW_LEDS default n ---help--- This driver adds support for rfkill and backlight control to Dell - laptops. + laptops (except for some models covered by the Compal driver). config DELL_WMI tristate "Dell WMI extras" depends on ACPI_WMI depends on INPUT + select INPUT_SPARSEKMAP ---help--- Say Y here if you want to support WMI-based hotkeys on Dell laptops. To compile this driver as a module, choose M here: the module will be called dell-wmi. +config DELL_WMI_AIO + tristate "WMI Hotkeys for Dell All-In-One series" + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + ---help--- + Say Y here if you want to support WMI-based hotkeys on Dell + All-In-One machines. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-aio. + +config DELL_SMO8800 + tristate "Dell Latitude freefall driver (ACPI SMO8800/SMO8810)" + depends on ACPI + ---help--- + Say Y here if you want to support SMO8800/SMO8810 freefall device + on Dell Latitude laptops. + + To compile this driver as a module, choose M here: the module will + be called dell-smo8800. + + config FUJITSU_LAPTOP tristate "Fujitsu Laptop Extras" depends on ACPI @@ -121,21 +165,75 @@ config FUJITSU_LAPTOP_DEBUG If you are not sure, say N here. +config FUJITSU_TABLET + tristate "Fujitsu Tablet Extras" + depends on ACPI + depends on INPUT + ---help--- + This is a driver for tablets built by Fujitsu: + + * Lifebook P1510/P1610/P1620/Txxxx + * Stylistic ST5xxx + * Possibly other Fujitsu tablet models + + It adds support for the panel buttons, docking station detection, + tablet/notebook mode detection for convertible and + orientation detection for docked slates. + + If you have a Fujitsu convertible or slate, say Y or M here. + +config AMILO_RFKILL + tristate "Fujitsu-Siemens Amilo rfkill support" + depends on RFKILL + depends on SERIO_I8042 + ---help--- + This is a driver for enabling wifi on some Fujitsu-Siemens Amilo + laptops. + config TC1100_WMI - tristate "HP Compaq TC1100 Tablet WMI Extras (EXPERIMENTAL)" + tristate "HP Compaq TC1100 Tablet WMI Extras" depends on !X86_64 - depends on EXPERIMENTAL depends on ACPI - select ACPI_WMI + depends on ACPI_WMI ---help--- This is a driver for the WMI extensions (wireless and bluetooth power control) of the HP Compaq TC1100 tablet. +config HP_ACCEL + tristate "HP laptop accelerometer" + depends on INPUT && ACPI + select SENSORS_LIS3LV02D + select NEW_LEDS + select LEDS_CLASS + help + This driver provides support for the "Mobile Data Protection System 3D" + or "3D DriveGuard" feature of HP laptops. On such systems the driver + should load automatically (via ACPI alias). + + Support for a led indicating disk protection will be provided as + hp::hddprotect. For more information on the feature, refer to + Documentation/misc-devices/lis3lv02d. + + To compile this driver as a module, choose M here: the module will + be called hp_accel. + +config HP_WIRELESS + tristate "HP wireless button" + depends on ACPI + depends on INPUT + help + This driver provides supports for new HP wireless button for Windows 8. + On such systems the driver should load automatically (via ACPI alias). + + To compile this driver as a module, choose M here: the module will + be called hp-wireless. + config HP_WMI tristate "HP WMI extras" depends on ACPI_WMI depends on INPUT depends on RFKILL || RFKILL = n + select INPUT_SPARSEKMAP help Say Y here if you want to support WMI-based hotkeys on HP laptops and to read data from WMI such as docking or ambient light sensor state. @@ -147,6 +245,9 @@ config MSI_LAPTOP tristate "MSI Laptop Extras" depends on ACPI depends on BACKLIGHT_CLASS_DEVICE + depends on RFKILL + depends on INPUT && SERIO_I8042 + select INPUT_SPARSEKMAP ---help--- This is a driver for laptops built by MSI (MICRO-STAR INTERNATIONAL): @@ -165,6 +266,7 @@ config PANASONIC_LAPTOP tristate "Panasonic Laptop Extras" depends on INPUT && ACPI depends on BACKLIGHT_CLASS_DEVICE + select INPUT_SPARSEKMAP ---help--- This driver adds support for access to backlight control and hotkeys on Panasonic Let's Note laptops. @@ -173,20 +275,21 @@ config PANASONIC_LAPTOP R2, R3, R5, T2, W2 and Y2 series), say Y. config COMPAL_LAPTOP - tristate "Compal Laptop Extras" + tristate "Compal (and others) Laptop Extras" depends on ACPI depends on BACKLIGHT_CLASS_DEVICE + depends on RFKILL + depends on HWMON + depends on POWER_SUPPLY ---help--- - This is a driver for laptops built by Compal: - - Compal FL90/IFL90 - Compal FL91/IFL91 - Compal FL92/JFL92 - Compal FT00/IFT00 + This is a driver for laptops built by Compal, and some models by + other brands (e.g. Dell, Toshiba). - It adds support for Bluetooth, WLAN and LCD brightness control. + It adds support for rfkill, Bluetooth, WLAN and LCD brightness + control. - If you have an Compal FL9x/IFL9x/FT00 laptop, say Y or M here. + For a (possibly incomplete) list of supported laptops, please refer + to: Documentation/platform/x86-laptop-drivers.txt config SONY_LAPTOP tristate "Sony Laptop Extras" @@ -210,6 +313,17 @@ config SONYPI_COMPAT ---help--- Build the sonypi driver compatibility code into the sony-laptop driver. +config IDEAPAD_LAPTOP + tristate "Lenovo IdeaPad Laptop Extras" + depends on ACPI + depends on RFKILL && INPUT + depends on SERIO_I8042 + depends on BACKLIGHT_CLASS_DEVICE + select INPUT_SPARSEKMAP + help + This is a driver for Lenovo IdeaPad netbooks contains drivers for + rfkill switch, hotkey, fan control and backlight control. + config THINKPAD_ACPI tristate "ThinkPad ACPI Laptop Extras" depends on ACPI @@ -319,9 +433,15 @@ config THINKPAD_ACPI_VIDEO server running, phase of the moon, and the current mood of Schroedinger's cat. If you can use X.org's RandR to control your ThinkPad's video output ports instead of this feature, - don't think twice: do it and say N here to save some memory. + don't think twice: do it and say N here to save memory and avoid + bad interactions with X.org. + + NOTE: access to this feature is limited to processes with the + CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms + where it interacts badly with X.org. - If you are not sure, say Y here. + If you are not sure, say Y here but do try to check if you could + be using X.org RandR instead. config THINKPAD_ACPI_HOTKEY_POLL bool "Support NVRAM polling for hot keys" @@ -343,6 +463,26 @@ config THINKPAD_ACPI_HOTKEY_POLL If you are not sure, say Y here. The driver enables polling only if it is strictly necessary to do so. +config SENSORS_HDAPS + tristate "Thinkpad Hard Drive Active Protection System (hdaps)" + depends on INPUT && X86 + select INPUT_POLLDEV + default n + help + This driver provides support for the IBM Hard Drive Active Protection + System (hdaps), which provides an accelerometer and other misc. data. + ThinkPads starting with the R50, T41, and X40 are supported. The + accelerometer data is readable via sysfs. + + This driver also provides an absolute input class device, allowing + the laptop to act as a pinball machine-esque joystick. + + If your ThinkPad is not recognized by the driver, please update to latest + BIOS. This is especially the case for some R52 ThinkPads. + + Say Y here if you have an applicable laptop and want to experience + the awesome power of hdaps. + config INTEL_MENLOW tristate "Thermal Management driver for Intel menlow platform" depends on ACPI_THERMAL @@ -354,16 +494,16 @@ config INTEL_MENLOW If unsure, say N. config EEEPC_LAPTOP - tristate "Eee PC Hotkey Driver (EXPERIMENTAL)" + tristate "Eee PC Hotkey Driver" depends on ACPI depends on INPUT - depends on EXPERIMENTAL depends on RFKILL || RFKILL = n depends on HOTPLUG_PCI select BACKLIGHT_CLASS_DEVICE select HWMON select LEDS_CLASS select NEW_LEDS + select INPUT_SPARSEKMAP ---help--- This driver supports the Fn-Fx keys on Eee PC laptops. @@ -371,8 +511,53 @@ config EEEPC_LAPTOP Bluetooth, backlight and allows powering on/off some other devices. - If you have an Eee PC laptop, say Y or M here. + If you have an Eee PC laptop, say Y or M here. If this driver + doesn't work on your Eee PC, try eeepc-wmi instead. + +config ASUS_WMI + tristate "ASUS WMI Driver" + depends on ACPI_WMI + depends on INPUT + depends on HWMON + depends on BACKLIGHT_CLASS_DEVICE + depends on RFKILL || RFKILL = n + depends on HOTPLUG_PCI + depends on ACPI_VIDEO || ACPI_VIDEO = n + select INPUT_SPARSEKMAP + select LEDS_CLASS + select NEW_LEDS + ---help--- + Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new + Asus Notebooks). + + To compile this driver as a module, choose M here: the module will + be called asus-wmi. + +config ASUS_NB_WMI + tristate "Asus Notebook WMI Driver" + depends on ASUS_WMI + ---help--- + This is a driver for newer Asus notebooks. It adds extra features + like wireless radio and bluetooth control, leds, hotkeys, backlight... + + For more informations, see + <file:Documentation/ABI/testing/sysfs-platform-asus-wmi> + + If you have an ACPI-WMI compatible Asus Notebook, say Y or M + here. + +config EEEPC_WMI + tristate "Eee PC WMI Driver" + depends on ASUS_WMI + ---help--- + This is a driver for newer Eee PC laptops. It adds extra features + like wireless radio and bluetooth control, leds, hotkeys, backlight... + For more informations, see + <file:Documentation/ABI/testing/sysfs-platform-asus-wmi> + + If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M + here. config ACPI_WMI tristate "WMI" @@ -407,42 +592,11 @@ config MSI_WMI To compile this driver as a module, choose M here: the module will be called msi-wmi. -config ACPI_ASUS - tristate "ASUS/Medion Laptop Extras (DEPRECATED)" - depends on ACPI - select BACKLIGHT_CLASS_DEVICE - ---help--- - This driver provides support for extra features of ACPI-compatible - ASUS laptops. As some of Medion laptops are made by ASUS, it may also - support some Medion laptops (such as 9675 for example). It makes all - the extra buttons generate standard ACPI events that go through - /proc/acpi/events, and (on some models) adds support for changing the - display brightness and output, switching the LCD backlight on and off, - and most importantly, allows you to blink those fancy LEDs intended - for reporting mail and wireless status. - - Note: display switching code is currently considered EXPERIMENTAL, - toying with these values may even lock your machine. - - All settings are changed via /proc/acpi/asus directory entries. Owner - and group for these entries can be set with asus_uid and asus_gid - parameters. - - More information and a userspace daemon for handling the extra buttons - at <http://acpi4asus.sf.net>. - - If you have an ACPI-compatible ASUS laptop, say Y or M here. This - driver is still under development, so if your laptop is unsupported or - something works not quite as expected, please use the mailing list - available on the above page (acpi4asus-user@lists.sourceforge.net). - - NOTE: This driver is deprecated and will probably be removed soon, - use asus-laptop instead. - config TOPSTAR_LAPTOP tristate "Topstar Laptop Extras" depends on ACPI depends on INPUT + select INPUT_SPARSEKMAP ---help--- This driver adds support for hotkeys found on Topstar laptops. @@ -451,10 +605,15 @@ config TOPSTAR_LAPTOP config ACPI_TOSHIBA tristate "Toshiba Laptop Extras" depends on ACPI + depends on ACPI_WMI + select LEDS_CLASS + select NEW_LEDS + depends on BACKLIGHT_CLASS_DEVICE depends on INPUT depends on RFKILL || RFKILL = n + depends on SERIO_I8042 || SERIO_I8042 = n select INPUT_POLLDEV - select BACKLIGHT_CLASS_DEVICE + select INPUT_SPARSEKMAP ---help--- This driver adds support for access to certain system settings on "legacy free" Toshiba laptops. These laptops can be recognized by @@ -496,6 +655,7 @@ config TOSHIBA_BT_RFKILL config ACPI_CMPC tristate "CMPC Laptop Extras" depends on X86 && ACPI + depends on RFKILL || RFKILL=n select INPUT select BACKLIGHT_CLASS_DEVICE default n @@ -504,4 +664,178 @@ config ACPI_CMPC keys as input device, backlight device, tablet and accelerometer devices. +config INTEL_SCU_IPC + bool "Intel SCU IPC Support" + depends on X86_INTEL_MID + default y + ---help--- + IPC is used to bridge the communications between kernel and SCU on + some embedded Intel x86 platforms. This is not needed for PC-type + machines. + +config INTEL_SCU_IPC_UTIL + tristate "Intel SCU IPC utility driver" + depends on INTEL_SCU_IPC + default y + ---help--- + The IPC Util driver provides an interface with the SCU enabling + low level access for debug work and updating the firmware. Say + N unless you will be doing this on an Intel MID platform. + +config GPIO_INTEL_PMIC + bool "Intel PMIC GPIO support" + depends on INTEL_SCU_IPC && GPIOLIB + ---help--- + Say Y here to support GPIO via the SCU IPC interface + on Intel MID platforms. + +config INTEL_MID_POWER_BUTTON + tristate "power button driver for Intel MID platforms" + depends on INTEL_SCU_IPC && INPUT + help + This driver handles the power button on the Intel MID platforms. + + If unsure, say N. + +config INTEL_MFLD_THERMAL + tristate "Thermal driver for Intel Medfield platform" + depends on MFD_INTEL_MSIC && THERMAL + help + Say Y here to enable thermal driver support for the Intel Medfield + platform. + +config INTEL_IPS + tristate "Intel Intelligent Power Sharing" + depends on ACPI + ---help--- + Intel Calpella platforms support dynamic power sharing between the + CPU and GPU, maximizing performance in a given TDP. This driver, + along with the CPU frequency and i915 drivers, provides that + functionality. If in doubt, say Y here; it will only load on + supported platforms. + +config IBM_RTL + tristate "Device driver to enable PRTL support" + depends on X86 && PCI + ---help--- + Enable support for IBM Premium Real Time Mode (PRTM). + This module will allow you the enter and exit PRTM in the BIOS via + sysfs on platforms that support this feature. System in PRTM will + not receive CPU-generated SMIs for recoverable errors. Use of this + feature without proper support may void your hardware warranty. + + If the proper BIOS support is found the driver will load and create + /sys/devices/system/ibm_rtl/. The "state" variable will indicate + whether or not the BIOS is in PRTM. + state = 0 (BIOS SMIs on) + state = 1 (BIOS SMIs off) + +config XO1_RFKILL + tristate "OLPC XO-1 software RF kill switch" + depends on OLPC || COMPILE_TEST + depends on RFKILL + ---help--- + Support for enabling/disabling the WLAN interface on the OLPC XO-1 + laptop. + +config XO15_EBOOK + tristate "OLPC XO-1.5 ebook switch" + depends on OLPC || COMPILE_TEST + depends on ACPI && INPUT + ---help--- + Support for the ebook switch on the OLPC XO-1.5 laptop. + + This switch is triggered as the screen is rotated and folded down to + convert the device into ebook form. + +config SAMSUNG_LAPTOP + tristate "Samsung Laptop driver" + depends on X86 + depends on RFKILL || RFKILL = n + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on BACKLIGHT_CLASS_DEVICE + select LEDS_CLASS + select NEW_LEDS + ---help--- + This module implements a driver for a wide range of different + Samsung laptops. It offers control over the different + function keys, wireless LED, LCD backlight level. + + It may also provide some sysfs files described in + <file:Documentation/ABI/testing/sysfs-platform-samsung-laptop> + + To compile this driver as a module, choose M here: the module + will be called samsung-laptop. + +config MXM_WMI + tristate "WMI support for MXM Laptop Graphics" + depends on ACPI_WMI + ---help--- + MXM is a standard for laptop graphics cards, the WMI interface + is required for switchable nvidia graphics machines + +config INTEL_OAKTRAIL + tristate "Intel Oaktrail Platform Extras" + depends on ACPI + depends on RFKILL && BACKLIGHT_CLASS_DEVICE && ACPI + ---help--- + Intel Oaktrail platform need this driver to provide interfaces to + enable/disable the Camera, WiFi, BT etc. devices. If in doubt, say Y + here; it will only load on supported platforms. + +config SAMSUNG_Q10 + tristate "Samsung Q10 Extras" + depends on ACPI + select BACKLIGHT_CLASS_DEVICE + ---help--- + This driver provides support for backlight control on Samsung Q10 + and related laptops, including Dell Latitude X200. + +config APPLE_GMUX + tristate "Apple Gmux Driver" + depends on ACPI + depends on PNP + depends on BACKLIGHT_CLASS_DEVICE + depends on BACKLIGHT_APPLE=n || BACKLIGHT_APPLE + depends on ACPI_VIDEO=n || ACPI_VIDEO + ---help--- + This driver provides support for the gmux device found on many + Apple laptops, which controls the display mux for the hybrid + graphics as well as the backlight. Currently only backlight + control is supported by the driver. + +config INTEL_RST + tristate "Intel Rapid Start Technology Driver" + depends on ACPI + ---help--- + This driver provides support for modifying paramaters on systems + equipped with Intel's Rapid Start Technology. When put in an ACPI + sleep state, these devices will wake after either a configured + timeout or when the system battery reaches a critical state, + automatically copying memory contents to disk. On resume, the + firmware will copy the memory contents back to RAM and resume the OS + as usual. + +config INTEL_SMARTCONNECT + tristate "Intel Smart Connect disabling driver" + depends on ACPI + ---help--- + Intel Smart Connect is a technology intended to permit devices to + update state by resuming for a short period of time at regular + intervals. If a user enables this functionality under Windows and + then reboots into Linux, the system may remain configured to resume + on suspend. In the absence of any userspace to support it, the system + will then remain awake until something triggers another suspend. + + This driver checks to determine whether the device has Intel Smart + Connect enabled, and if so disables it. + +config PVPANIC + tristate "pvpanic device support" + depends on ACPI + ---help--- + This driver provides support for the pvpanic device. pvpanic is + a paravirtualized device provided by QEMU; it lets a virtual machine + (guest) communicate panic events to the host. + endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 9cd9fa0a27e..c4ca428fd3d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -3,24 +3,57 @@ # x86 Platform-Specific Drivers # obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o +obj-$(CONFIG_ASUS_WMI) += asus-wmi.o +obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o +obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o +obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o +obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o +obj-$(CONFIG_HP_ACCEL) += hp_accel.o +obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o obj-$(CONFIG_HP_WMI) += hp-wmi.o +obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o +obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o +obj-$(CONFIG_FUJITSU_TABLET) += fujitsu-tablet.o obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o -obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o + +# toshiba_acpi must link after wmi to ensure that wmi devices are found +# before toshiba_acpi initializes obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o + obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o +obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o +obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o +obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o +obj-$(CONFIG_INTEL_IPS) += intel_ips.o +obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o +obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o +obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o +obj-$(CONFIG_IBM_RTL) += ibm_rtl.o +obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o +obj-$(CONFIG_MXM_WMI) += mxm-wmi.o +obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o +obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o +obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o +obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o +obj-$(CONFIG_INTEL_RST) += intel-rst.o +obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o + +obj-$(CONFIG_PVPANIC) += pvpanic.o +obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 07d14dfdf0b..bbf78b2d6d9 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> @@ -36,29 +38,15 @@ #include <linux/rfkill.h> #include <linux/workqueue.h> #include <linux/debugfs.h> - -#include <acpi/acpi_drivers.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <acpi/video.h> MODULE_AUTHOR("Carlos Corbacho"); MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); MODULE_LICENSE("GPL"); -#define ACER_LOGPREFIX "acer-wmi: " -#define ACER_ERR KERN_ERR ACER_LOGPREFIX -#define ACER_NOTICE KERN_NOTICE ACER_LOGPREFIX -#define ACER_INFO KERN_INFO ACER_LOGPREFIX - -/* - * The following defines quirks to get some specific functions to work - * which are known to not be supported over ACPI-WMI (such as the mail LED - * on WMID based Acer's) - */ -struct acer_quirks { - const char *vendor; - const char *model; - u16 quirks; -}; - /* * Magic Number * Meaning is unknown - this number is required for writing to ACPI for AMW0 @@ -90,11 +78,123 @@ struct acer_quirks { */ #define AMW0_GUID1 "67C3371D-95A3-4C37-BB61-DD47B491DAAB" #define AMW0_GUID2 "431F16ED-0C2B-444C-B267-27DEB140CF9C" -#define WMID_GUID1 "6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3" -#define WMID_GUID2 "95764E09-FB56-4e83-B31A-37761F60994A" +#define WMID_GUID1 "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3" +#define WMID_GUID2 "95764E09-FB56-4E83-B31A-37761F60994A" +#define WMID_GUID3 "61EF69EA-865C-4BC3-A502-A0DEBA0CB531" + +/* + * Acer ACPI event GUIDs + */ +#define ACERWMID_EVENT_GUID "676AA15E-6A47-4D9F-A2CC-1E6D18D14026" MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB"); -MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"); +MODULE_ALIAS("wmi:6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3"); +MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026"); + +enum acer_wmi_event_ids { + WMID_HOTKEY_EVENT = 0x1, + WMID_ACCEL_EVENT = 0x5, +}; + +static const struct key_entry acer_wmi_keymap[] = { + {KE_KEY, 0x01, {KEY_WLAN} }, /* WiFi */ + {KE_KEY, 0x03, {KEY_WLAN} }, /* WiFi */ + {KE_KEY, 0x04, {KEY_WLAN} }, /* WiFi */ + {KE_KEY, 0x12, {KEY_BLUETOOTH} }, /* BT */ + {KE_KEY, 0x21, {KEY_PROG1} }, /* Backup */ + {KE_KEY, 0x22, {KEY_PROG2} }, /* Arcade */ + {KE_KEY, 0x23, {KEY_PROG3} }, /* P_Key */ + {KE_KEY, 0x24, {KEY_PROG4} }, /* Social networking_Key */ + {KE_KEY, 0x29, {KEY_PROG3} }, /* P_Key for TM8372 */ + {KE_IGNORE, 0x41, {KEY_MUTE} }, + {KE_IGNORE, 0x42, {KEY_PREVIOUSSONG} }, + {KE_IGNORE, 0x4d, {KEY_PREVIOUSSONG} }, + {KE_IGNORE, 0x43, {KEY_NEXTSONG} }, + {KE_IGNORE, 0x4e, {KEY_NEXTSONG} }, + {KE_IGNORE, 0x44, {KEY_PLAYPAUSE} }, + {KE_IGNORE, 0x4f, {KEY_PLAYPAUSE} }, + {KE_IGNORE, 0x45, {KEY_STOP} }, + {KE_IGNORE, 0x50, {KEY_STOP} }, + {KE_IGNORE, 0x48, {KEY_VOLUMEUP} }, + {KE_IGNORE, 0x49, {KEY_VOLUMEDOWN} }, + {KE_IGNORE, 0x4a, {KEY_VOLUMEDOWN} }, + {KE_IGNORE, 0x61, {KEY_SWITCHVIDEOMODE} }, + {KE_IGNORE, 0x62, {KEY_BRIGHTNESSUP} }, + {KE_IGNORE, 0x63, {KEY_BRIGHTNESSDOWN} }, + {KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} }, /* Display Switch */ + {KE_IGNORE, 0x81, {KEY_SLEEP} }, + {KE_KEY, 0x82, {KEY_TOUCHPAD_TOGGLE} }, /* Touch Pad Toggle */ + {KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} }, + {KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} }, + {KE_IGNORE, 0x83, {KEY_TOUCHPAD_TOGGLE} }, + {KE_KEY, 0x85, {KEY_TOUCHPAD_TOGGLE} }, + {KE_END, 0} +}; + +static struct input_dev *acer_wmi_input_dev; +static struct input_dev *acer_wmi_accel_dev; + +struct event_return_value { + u8 function; + u8 key_num; + u16 device_state; + u32 reserved; +} __attribute__((packed)); + +/* + * GUID3 Get Device Status device flags + */ +#define ACER_WMID3_GDS_WIRELESS (1<<0) /* WiFi */ +#define ACER_WMID3_GDS_THREEG (1<<6) /* 3G */ +#define ACER_WMID3_GDS_WIMAX (1<<7) /* WiMAX */ +#define ACER_WMID3_GDS_BLUETOOTH (1<<11) /* BT */ +#define ACER_WMID3_GDS_TOUCHPAD (1<<1) /* Touchpad */ + +struct lm_input_params { + u8 function_num; /* Function Number */ + u16 commun_devices; /* Communication type devices default status */ + u16 devices; /* Other type devices default status */ + u8 lm_status; /* Launch Manager Status */ + u16 reserved; +} __attribute__((packed)); + +struct lm_return_value { + u8 error_code; /* Error Code */ + u8 ec_return_value; /* EC Return Value */ + u16 reserved; +} __attribute__((packed)); + +struct wmid3_gds_set_input_param { /* Set Device Status input parameter */ + u8 function_num; /* Function Number */ + u8 hotkey_number; /* Hotkey Number */ + u16 devices; /* Set Device */ + u8 volume_value; /* Volume Value */ +} __attribute__((packed)); + +struct wmid3_gds_get_input_param { /* Get Device Status input parameter */ + u8 function_num; /* Function Number */ + u8 hotkey_number; /* Hotkey Number */ + u16 devices; /* Get Device */ +} __attribute__((packed)); + +struct wmid3_gds_return_value { /* Get Device Status return value*/ + u8 error_code; /* Error Code */ + u8 ec_return_value; /* EC Return Value */ + u16 devices; /* Current Device Status */ + u32 reserved; +} __attribute__((packed)); + +struct hotkey_function_type_aa { + u8 type; + u8 length; + u16 handle; + u16 commun_func_bitmap; + u16 application_func_bitmap; + u16 media_func_bitmap; + u16 display_func_bitmap; + u16 others_func_bitmap; + u8 commun_fn_key_number; +} __attribute__((packed)); /* * Interface capability flags @@ -104,6 +204,7 @@ MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"); #define ACER_CAP_BLUETOOTH (1<<2) #define ACER_CAP_BRIGHTNESS (1<<3) #define ACER_CAP_THREEG (1<<4) +#define ACER_CAP_ACCEL (1<<5) #define ACER_CAP_ANY (0xFFFFFFFF) /* @@ -113,6 +214,7 @@ enum interface_flags { ACER_AMW0, ACER_AMW0_V2, ACER_WMID, + ACER_WMID_v2, }; #define ACER_DEFAULT_WIRELESS 0 @@ -126,15 +228,21 @@ static int mailled = -1; static int brightness = -1; static int threeg = -1; static int force_series; +static bool ec_raw_mode; +static bool has_type_aa; +static u16 commun_func_bitmap; +static u8 commun_fn_key_number; module_param(mailled, int, 0444); module_param(brightness, int, 0444); module_param(threeg, int, 0444); module_param(force_series, int, 0444); +module_param(ec_raw_mode, bool, 0444); MODULE_PARM_DESC(mailled, "Set initial state of Mail LED"); MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness"); MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware"); MODULE_PARM_DESC(force_series, "Force a different laptop series"); +MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode"); struct acer_data { int mailled; @@ -150,6 +258,8 @@ struct acer_debug { static struct rfkill *wireless_rfkill; static struct rfkill *bluetooth_rfkill; +static struct rfkill *threeg_rfkill; +static bool rfkill_inited; /* Each low-level interface must define at least some of the following */ struct wmi_interface { @@ -199,7 +309,7 @@ static void set_quirks(void) static int dmi_matched(const struct dmi_system_id *dmi) { quirks = dmi->driver_data; - return 0; + return 1; } static struct quirk_entry quirk_unknown = { @@ -222,8 +332,12 @@ static struct quirk_entry quirk_fujitsu_amilo_li_1718 = { .wireless = 2, }; +static struct quirk_entry quirk_lenovo_ideapad_s205 = { + .wireless = 3, +}; + /* The Aspire One has a dummy ACPI-WMI interface - disable it */ -static struct dmi_system_id __devinitdata acer_blacklist[] = { +static struct dmi_system_id acer_blacklist[] = { { .ident = "Acer Aspire One (SSD)", .matches = { @@ -368,6 +482,102 @@ static struct dmi_system_id acer_quirks[] = { }, .driver_data = &quirk_medion_md_98300, }, + { + .callback = dmi_matched, + .ident = "Lenovo Ideapad S205", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "10382LG"), + }, + .driver_data = &quirk_lenovo_ideapad_s205, + }, + { + .callback = dmi_matched, + .ident = "Lenovo Ideapad S205 (Brazos)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "Brazos"), + }, + .driver_data = &quirk_lenovo_ideapad_s205, + }, + { + .callback = dmi_matched, + .ident = "Lenovo 3000 N200", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "0687A31"), + }, + .driver_data = &quirk_fujitsu_amilo_li_1718, + }, + { + .callback = dmi_matched, + .ident = "Lenovo Ideapad S205-10382JG", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "10382JG"), + }, + .driver_data = &quirk_lenovo_ideapad_s205, + }, + { + .callback = dmi_matched, + .ident = "Lenovo Ideapad S205-1038DPG", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "1038DPG"), + }, + .driver_data = &quirk_lenovo_ideapad_s205, + }, + {} +}; + +static int video_set_backlight_video_vendor(const struct dmi_system_id *d) +{ + interface->capability &= ~ACER_CAP_BRIGHTNESS; + pr_info("Brightness must be controlled by generic video driver\n"); + return 0; +} + +static const struct dmi_system_id video_vendor_dmi_table[] = { + { + .callback = video_set_backlight_video_vendor, + .ident = "Acer TravelMate 4750", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4750"), + }, + }, + { + .callback = video_set_backlight_video_vendor, + .ident = "Acer Extensa 5235", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Extensa 5235"), + }, + }, + { + .callback = video_set_backlight_video_vendor, + .ident = "Acer TravelMate 5760", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 5760"), + }, + }, + { + .callback = video_set_backlight_video_vendor, + .ident = "Acer Aspire 5750", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5750"), + }, + }, + { + .callback = video_set_backlight_video_vendor, + .ident = "Acer Aspire 5741", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5741"), + }, + }, {} }; @@ -429,8 +639,7 @@ struct acpi_buffer *result) return status; } -static acpi_status AMW0_get_u32(u32 *value, u32 cap, -struct wmi_interface *iface) +static acpi_status AMW0_get_u32(u32 *value, u32 cap) { int err; u8 result; @@ -460,6 +669,12 @@ struct wmi_interface *iface) return AE_ERROR; *value = result & 0x1; return AE_OK; + case 3: + err = ec_read(0x78, &result); + if (err) + return AE_ERROR; + *value = result & 0x1; + return AE_OK; default: err = ec_read(0xA, &result); if (err) @@ -494,7 +709,7 @@ struct wmi_interface *iface) return AE_OK; } -static acpi_status AMW0_set_u32(u32 value, u32 cap, struct wmi_interface *iface) +static acpi_status AMW0_set_u32(u32 value, u32 cap) { struct wmab_args args; @@ -554,6 +769,7 @@ static acpi_status AMW0_find_mailled(void) obj->buffer.length == sizeof(struct wmab_ret)) { ret = *((struct wmab_ret *) obj->buffer.pointer); } else { + kfree(out.pointer); return AE_ERROR; } @@ -565,11 +781,38 @@ static acpi_status AMW0_find_mailled(void) return AE_OK; } +static int AMW0_set_cap_acpi_check_device_found; + +static acpi_status AMW0_set_cap_acpi_check_device_cb(acpi_handle handle, + u32 level, void *context, void **retval) +{ + AMW0_set_cap_acpi_check_device_found = 1; + return AE_OK; +} + +static const struct acpi_device_id norfkill_ids[] = { + { "VPC2004", 0}, + { "IBM0068", 0}, + { "LEN0068", 0}, + { "SNY5001", 0}, /* sony-laptop in charge */ + { "", 0}, +}; + +static int AMW0_set_cap_acpi_check_device(void) +{ + const struct acpi_device_id *id; + + for (id = norfkill_ids; id->id[0]; id++) + acpi_get_devices(id->id, AMW0_set_cap_acpi_check_device_cb, + NULL, NULL); + return AMW0_set_cap_acpi_check_device_found; +} + static acpi_status AMW0_set_capabilities(void) { struct wmab_args args; struct wmab_ret ret; - acpi_status status = AE_OK; + acpi_status status; struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; @@ -578,7 +821,9 @@ static acpi_status AMW0_set_capabilities(void) * work. */ if (wmi_has_guid(AMW0_GUID2)) { - interface->capability |= ACER_CAP_WIRELESS; + if ((quirks != &quirk_unknown) || + !AMW0_set_cap_acpi_check_device()) + interface->capability |= ACER_CAP_WIRELESS; return AE_OK; } @@ -592,12 +837,13 @@ static acpi_status AMW0_set_capabilities(void) if (ACPI_FAILURE(status)) return status; - obj = (union acpi_object *) out.pointer; + obj = out.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == sizeof(struct wmab_ret)) { ret = *((struct wmab_ret *) obj->buffer.pointer); } else { - return AE_ERROR; + status = AE_ERROR; + goto out; } if (ret.eax & 0x1) @@ -606,23 +852,26 @@ static acpi_status AMW0_set_capabilities(void) args.ebx = 2 << 8; args.ebx |= ACER_AMW0_BLUETOOTH_MASK; + /* + * It's ok to use existing buffer for next wmab_execute call. + * But we need to kfree(out.pointer) if next wmab_execute fail. + */ status = wmab_execute(&args, &out); if (ACPI_FAILURE(status)) - return status; + goto out; obj = (union acpi_object *) out.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == sizeof(struct wmab_ret)) { ret = *((struct wmab_ret *) obj->buffer.pointer); } else { - return AE_ERROR; + status = AE_ERROR; + goto out; } if (ret.eax & 0x1) interface->capability |= ACER_CAP_BLUETOOTH; - kfree(out.pointer); - /* * This appears to be safe to enable, since all Wistron based laptops * appear to use the same EC register for brightness, even if they @@ -631,7 +880,10 @@ static acpi_status AMW0_set_capabilities(void) if (quirks->brightness >= 0) interface->capability |= ACER_CAP_BRIGHTNESS; - return AE_OK; + status = AE_OK; +out: + kfree(out.pointer); + return status; } static struct wmi_interface AMW0_interface = { @@ -651,7 +903,7 @@ WMI_execute_u32(u32 method_id, u32 in, u32 *out) struct acpi_buffer input = { (acpi_size) sizeof(u32), (void *)(&in) }; struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; - u32 tmp; + u32 tmp = 0; acpi_status status; status = wmi_evaluate_method(WMID_GUID1, 1, method_id, &input, &result); @@ -660,11 +912,14 @@ WMI_execute_u32(u32 method_id, u32 in, u32 *out) return status; obj = (union acpi_object *) result.pointer; - if (obj && obj->type == ACPI_TYPE_BUFFER && - obj->buffer.length == sizeof(u32)) { - tmp = *((u32 *) obj->buffer.pointer); - } else { - tmp = 0; + if (obj) { + if (obj->type == ACPI_TYPE_BUFFER && + (obj->buffer.length == sizeof(u32) || + obj->buffer.length == sizeof(u64))) { + tmp = *((u32 *) obj->buffer.pointer); + } else if (obj->type == ACPI_TYPE_INTEGER) { + tmp = (u32) obj->integer.value; + } } if (out) @@ -675,8 +930,7 @@ WMI_execute_u32(u32 method_id, u32 in, u32 *out) return status; } -static acpi_status WMID_get_u32(u32 *value, u32 cap, -struct wmi_interface *iface) +static acpi_status WMID_get_u32(u32 *value, u32 cap) { acpi_status status; u8 tmp; @@ -712,7 +966,7 @@ struct wmi_interface *iface) return status; } -static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface) +static acpi_status WMID_set_u32(u32 value, u32 cap) { u32 method_id = 0; char param; @@ -755,6 +1009,206 @@ static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface) return WMI_execute_u32(method_id, (u32)value, NULL); } +static acpi_status wmid3_get_device_status(u32 *value, u16 device) +{ + struct wmid3_gds_return_value return_value; + acpi_status status; + union acpi_object *obj; + struct wmid3_gds_get_input_param params = { + .function_num = 0x1, + .hotkey_number = commun_fn_key_number, + .devices = device, + }; + struct acpi_buffer input = { + sizeof(struct wmid3_gds_get_input_param), + ¶ms + }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + + status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input, &output); + if (ACPI_FAILURE(status)) + return status; + + obj = output.pointer; + + if (!obj) + return AE_ERROR; + else if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return AE_ERROR; + } + if (obj->buffer.length != 8) { + pr_warn("Unknown buffer length %d\n", obj->buffer.length); + kfree(obj); + return AE_ERROR; + } + + return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer); + kfree(obj); + + if (return_value.error_code || return_value.ec_return_value) + pr_warn("Get 0x%x Device Status failed: 0x%x - 0x%x\n", + device, + return_value.error_code, + return_value.ec_return_value); + else + *value = !!(return_value.devices & device); + + return status; +} + +static acpi_status wmid_v2_get_u32(u32 *value, u32 cap) +{ + u16 device; + + switch (cap) { + case ACER_CAP_WIRELESS: + device = ACER_WMID3_GDS_WIRELESS; + break; + case ACER_CAP_BLUETOOTH: + device = ACER_WMID3_GDS_BLUETOOTH; + break; + case ACER_CAP_THREEG: + device = ACER_WMID3_GDS_THREEG; + break; + default: + return AE_ERROR; + } + return wmid3_get_device_status(value, device); +} + +static acpi_status wmid3_set_device_status(u32 value, u16 device) +{ + struct wmid3_gds_return_value return_value; + acpi_status status; + union acpi_object *obj; + u16 devices; + struct wmid3_gds_get_input_param get_params = { + .function_num = 0x1, + .hotkey_number = commun_fn_key_number, + .devices = commun_func_bitmap, + }; + struct acpi_buffer get_input = { + sizeof(struct wmid3_gds_get_input_param), + &get_params + }; + struct wmid3_gds_set_input_param set_params = { + .function_num = 0x2, + .hotkey_number = commun_fn_key_number, + .devices = commun_func_bitmap, + }; + struct acpi_buffer set_input = { + sizeof(struct wmid3_gds_set_input_param), + &set_params + }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer output2 = { ACPI_ALLOCATE_BUFFER, NULL }; + + status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &get_input, &output); + if (ACPI_FAILURE(status)) + return status; + + obj = output.pointer; + + if (!obj) + return AE_ERROR; + else if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return AE_ERROR; + } + if (obj->buffer.length != 8) { + pr_warn("Unknown buffer length %d\n", obj->buffer.length); + kfree(obj); + return AE_ERROR; + } + + return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer); + kfree(obj); + + if (return_value.error_code || return_value.ec_return_value) { + pr_warn("Get Current Device Status failed: 0x%x - 0x%x\n", + return_value.error_code, + return_value.ec_return_value); + return status; + } + + devices = return_value.devices; + set_params.devices = (value) ? (devices | device) : (devices & ~device); + + status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &set_input, &output2); + if (ACPI_FAILURE(status)) + return status; + + obj = output2.pointer; + + if (!obj) + return AE_ERROR; + else if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return AE_ERROR; + } + if (obj->buffer.length != 4) { + pr_warn("Unknown buffer length %d\n", obj->buffer.length); + kfree(obj); + return AE_ERROR; + } + + return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer); + kfree(obj); + + if (return_value.error_code || return_value.ec_return_value) + pr_warn("Set Device Status failed: 0x%x - 0x%x\n", + return_value.error_code, + return_value.ec_return_value); + + return status; +} + +static acpi_status wmid_v2_set_u32(u32 value, u32 cap) +{ + u16 device; + + switch (cap) { + case ACER_CAP_WIRELESS: + device = ACER_WMID3_GDS_WIRELESS; + break; + case ACER_CAP_BLUETOOTH: + device = ACER_WMID3_GDS_BLUETOOTH; + break; + case ACER_CAP_THREEG: + device = ACER_WMID3_GDS_THREEG; + break; + default: + return AE_ERROR; + } + return wmid3_set_device_status(value, device); +} + +static void type_aa_dmi_decode(const struct dmi_header *header, void *dummy) +{ + struct hotkey_function_type_aa *type_aa; + + /* We are looking for OEM-specific Type AAh */ + if (header->type != 0xAA) + return; + + has_type_aa = true; + type_aa = (struct hotkey_function_type_aa *) header; + + pr_info("Function bitmap for Communication Button: 0x%x\n", + type_aa->commun_func_bitmap); + commun_func_bitmap = type_aa->commun_func_bitmap; + + if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_WIRELESS) + interface->capability |= ACER_CAP_WIRELESS; + if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_THREEG) + interface->capability |= ACER_CAP_THREEG; + if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH) + interface->capability |= ACER_CAP_BLUETOOTH; + + commun_fn_key_number = type_aa->commun_fn_key_number; +} + static acpi_status WMID_set_capabilities(void) { struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; @@ -767,26 +1221,34 @@ static acpi_status WMID_set_capabilities(void) return status; obj = (union acpi_object *) out.pointer; - if (obj && obj->type == ACPI_TYPE_BUFFER && - obj->buffer.length == sizeof(u32)) { - devices = *((u32 *) obj->buffer.pointer); + if (obj) { + if (obj->type == ACPI_TYPE_BUFFER && + (obj->buffer.length == sizeof(u32) || + obj->buffer.length == sizeof(u64))) { + devices = *((u32 *) obj->buffer.pointer); + } else if (obj->type == ACPI_TYPE_INTEGER) { + devices = (u32) obj->integer.value; + } else { + kfree(out.pointer); + return AE_ERROR; + } } else { + kfree(out.pointer); return AE_ERROR; } - /* Not sure on the meaning of the relevant bits yet to detect these */ - interface->capability |= ACER_CAP_WIRELESS; - interface->capability |= ACER_CAP_THREEG; - - /* WMID always provides brightness methods */ - interface->capability |= ACER_CAP_BRIGHTNESS; - + pr_info("Function bitmap for Communication Device: 0x%x\n", devices); + if (devices & 0x07) + interface->capability |= ACER_CAP_WIRELESS; + if (devices & 0x40) + interface->capability |= ACER_CAP_THREEG; if (devices & 0x10) interface->capability |= ACER_CAP_BLUETOOTH; if (!(devices & 0x20)) max_brightness = 0x9; + kfree(out.pointer); return status; } @@ -794,6 +1256,10 @@ static struct wmi_interface wmid_interface = { .type = ACER_WMID, }; +static struct wmi_interface wmid_v2_interface = { + .type = ACER_WMID_v2, +}; + /* * Generic Device (interface-independent) */ @@ -804,15 +1270,23 @@ static acpi_status get_u32(u32 *value, u32 cap) switch (interface->type) { case ACER_AMW0: - status = AMW0_get_u32(value, cap, interface); + status = AMW0_get_u32(value, cap); break; case ACER_AMW0_V2: if (cap == ACER_CAP_MAILLED) { - status = AMW0_get_u32(value, cap, interface); + status = AMW0_get_u32(value, cap); break; } case ACER_WMID: - status = WMID_get_u32(value, cap, interface); + status = WMID_get_u32(value, cap); + break; + case ACER_WMID_v2: + if (cap & (ACER_CAP_WIRELESS | + ACER_CAP_BLUETOOTH | + ACER_CAP_THREEG)) + status = wmid_v2_get_u32(value, cap); + else if (wmi_has_guid(WMID_GUID2)) + status = WMID_get_u32(value, cap); break; } @@ -826,10 +1300,10 @@ static acpi_status set_u32(u32 value, u32 cap) if (interface->capability & cap) { switch (interface->type) { case ACER_AMW0: - return AMW0_set_u32(value, cap, interface); + return AMW0_set_u32(value, cap); case ACER_AMW0_V2: if (cap == ACER_CAP_MAILLED) - return AMW0_set_u32(value, cap, interface); + return AMW0_set_u32(value, cap); /* * On some models, some WMID methods don't toggle @@ -839,14 +1313,21 @@ static acpi_status set_u32(u32 value, u32 cap) */ if (cap == ACER_CAP_WIRELESS || cap == ACER_CAP_BLUETOOTH) { - status = WMID_set_u32(value, cap, interface); + status = WMID_set_u32(value, cap); if (ACPI_FAILURE(status)) return status; - return AMW0_set_u32(value, cap, interface); + return AMW0_set_u32(value, cap); } case ACER_WMID: - return WMID_set_u32(value, cap, interface); + return WMID_set_u32(value, cap); + case ACER_WMID_v2: + if (cap & (ACER_CAP_WIRELESS | + ACER_CAP_BLUETOOTH | + ACER_CAP_THREEG)) + return wmid_v2_set_u32(value, cap); + else if (wmi_has_guid(WMID_GUID2)) + return WMID_set_u32(value, cap); default: return AE_BAD_PARAMETER; } @@ -860,9 +1341,12 @@ static void __init acer_commandline_init(void) * These will all fail silently if the value given is invalid, or the * capability isn't available on the given interface */ - set_u32(mailled, ACER_CAP_MAILLED); - set_u32(threeg, ACER_CAP_THREEG); - set_u32(brightness, ACER_CAP_BRIGHTNESS); + if (mailled >= 0) + set_u32(mailled, ACER_CAP_MAILLED); + if (!has_type_aa && threeg >= 0) + set_u32(threeg, ACER_CAP_THREEG); + if (brightness >= 0) + set_u32(brightness, ACER_CAP_BRIGHTNESS); } /* @@ -879,13 +1363,14 @@ static struct led_classdev mail_led = { .brightness_set = mail_led_set, }; -static int __devinit acer_led_init(struct device *dev) +static int acer_led_init(struct device *dev) { return led_classdev_register(dev, &mail_led); } static void acer_led_exit(void) { + set_u32(LED_OFF, ACER_CAP_MAILLED); led_classdev_unregister(&mail_led); } @@ -915,18 +1400,23 @@ static int update_bl_status(struct backlight_device *bd) return 0; } -static struct backlight_ops acer_bl_ops = { +static const struct backlight_ops acer_bl_ops = { .get_brightness = read_brightness, .update_status = update_bl_status, }; -static int __devinit acer_backlight_init(struct device *dev) +static int acer_backlight_init(struct device *dev) { + struct backlight_properties props; struct backlight_device *bd; - bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops); + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = max_brightness; + bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops, + &props); if (IS_ERR(bd)) { - printk(ACER_ERR "Could not register Acer backlight device\n"); + pr_err("Could not register Acer backlight device\n"); acer_backlight_device = NULL; return PTR_ERR(bd); } @@ -934,8 +1424,7 @@ static int __devinit acer_backlight_init(struct device *dev) acer_backlight_device = bd; bd->props.power = FB_BLANK_UNBLANK; - bd->props.brightness = max_brightness; - bd->props.max_brightness = max_brightness; + bd->props.brightness = read_brightness(bd); backlight_update_status(bd); return 0; } @@ -946,6 +1435,60 @@ static void acer_backlight_exit(void) } /* + * Accelerometer device + */ +static acpi_handle gsensor_handle; + +static int acer_gsensor_init(void) +{ + acpi_status status; + struct acpi_buffer output; + union acpi_object out_obj; + + output.length = sizeof(out_obj); + output.pointer = &out_obj; + status = acpi_evaluate_object(gsensor_handle, "_INI", NULL, &output); + if (ACPI_FAILURE(status)) + return -1; + + return 0; +} + +static int acer_gsensor_open(struct input_dev *input) +{ + return acer_gsensor_init(); +} + +static int acer_gsensor_event(void) +{ + acpi_status status; + struct acpi_buffer output; + union acpi_object out_obj[5]; + + if (!has_cap(ACER_CAP_ACCEL)) + return -1; + + output.length = sizeof(out_obj); + output.pointer = out_obj; + + status = acpi_evaluate_object(gsensor_handle, "RDVL", NULL, &output); + if (ACPI_FAILURE(status)) + return -1; + + if (out_obj->package.count != 4) + return -1; + + input_report_abs(acer_wmi_accel_dev, ABS_X, + (s16)out_obj->package.elements[0].integer.value); + input_report_abs(acer_wmi_accel_dev, ABS_Y, + (s16)out_obj->package.elements[1].integer.value); + input_report_abs(acer_wmi_accel_dev, ABS_Z, + (s16)out_obj->package.elements[2].integer.value); + input_sync(acer_wmi_accel_dev); + return 0; +} + +/* * Rfkill devices */ static void acer_rfkill_update(struct work_struct *ignored); @@ -955,9 +1498,15 @@ static void acer_rfkill_update(struct work_struct *ignored) u32 state; acpi_status status; - status = get_u32(&state, ACER_CAP_WIRELESS); - if (ACPI_SUCCESS(status)) - rfkill_set_sw_state(wireless_rfkill, !state); + if (has_cap(ACER_CAP_WIRELESS)) { + status = get_u32(&state, ACER_CAP_WIRELESS); + if (ACPI_SUCCESS(status)) { + if (quirks->wireless == 3) + rfkill_set_hw_state(wireless_rfkill, !state); + else + rfkill_set_sw_state(wireless_rfkill, !state); + } + } if (has_cap(ACER_CAP_BLUETOOTH)) { status = get_u32(&state, ACER_CAP_BLUETOOTH); @@ -965,6 +1514,12 @@ static void acer_rfkill_update(struct work_struct *ignored) rfkill_set_sw_state(bluetooth_rfkill, !state); } + if (has_cap(ACER_CAP_THREEG) && wmi_has_guid(WMID_GUID3)) { + status = get_u32(&state, ACER_WMID3_GDS_THREEG); + if (ACPI_SUCCESS(status)) + rfkill_set_sw_state(threeg_rfkill, !state); + } + schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ)); } @@ -972,9 +1527,13 @@ static int acer_rfkill_set(void *data, bool blocked) { acpi_status status; u32 cap = (unsigned long)data; - status = set_u32(!blocked, cap); - if (ACPI_FAILURE(status)) - return -ENODEV; + + if (rfkill_inited) { + status = set_u32(!blocked, cap); + if (ACPI_FAILURE(status)) + return -ENODEV; + } + return 0; } @@ -988,6 +1547,8 @@ static struct rfkill *acer_rfkill_register(struct device *dev, { int err; struct rfkill *rfkill_dev; + u32 state; + acpi_status status; rfkill_dev = rfkill_alloc(name, dev, type, &acer_rfkill_ops, @@ -995,48 +1556,96 @@ static struct rfkill *acer_rfkill_register(struct device *dev, if (!rfkill_dev) return ERR_PTR(-ENOMEM); + status = get_u32(&state, cap); + err = rfkill_register(rfkill_dev); if (err) { rfkill_destroy(rfkill_dev); return ERR_PTR(err); } + + if (ACPI_SUCCESS(status)) + rfkill_set_sw_state(rfkill_dev, !state); + return rfkill_dev; } static int acer_rfkill_init(struct device *dev) { - wireless_rfkill = acer_rfkill_register(dev, RFKILL_TYPE_WLAN, - "acer-wireless", ACER_CAP_WIRELESS); - if (IS_ERR(wireless_rfkill)) - return PTR_ERR(wireless_rfkill); + int err; + + if (has_cap(ACER_CAP_WIRELESS)) { + wireless_rfkill = acer_rfkill_register(dev, RFKILL_TYPE_WLAN, + "acer-wireless", ACER_CAP_WIRELESS); + if (IS_ERR(wireless_rfkill)) { + err = PTR_ERR(wireless_rfkill); + goto error_wireless; + } + } if (has_cap(ACER_CAP_BLUETOOTH)) { bluetooth_rfkill = acer_rfkill_register(dev, RFKILL_TYPE_BLUETOOTH, "acer-bluetooth", ACER_CAP_BLUETOOTH); if (IS_ERR(bluetooth_rfkill)) { - rfkill_unregister(wireless_rfkill); - rfkill_destroy(wireless_rfkill); - return PTR_ERR(bluetooth_rfkill); + err = PTR_ERR(bluetooth_rfkill); + goto error_bluetooth; } } - schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ)); + if (has_cap(ACER_CAP_THREEG)) { + threeg_rfkill = acer_rfkill_register(dev, + RFKILL_TYPE_WWAN, "acer-threeg", + ACER_CAP_THREEG); + if (IS_ERR(threeg_rfkill)) { + err = PTR_ERR(threeg_rfkill); + goto error_threeg; + } + } + + rfkill_inited = true; + + if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) && + has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG)) + schedule_delayed_work(&acer_rfkill_work, + round_jiffies_relative(HZ)); return 0; + +error_threeg: + if (has_cap(ACER_CAP_BLUETOOTH)) { + rfkill_unregister(bluetooth_rfkill); + rfkill_destroy(bluetooth_rfkill); + } +error_bluetooth: + if (has_cap(ACER_CAP_WIRELESS)) { + rfkill_unregister(wireless_rfkill); + rfkill_destroy(wireless_rfkill); + } +error_wireless: + return err; } static void acer_rfkill_exit(void) { - cancel_delayed_work_sync(&acer_rfkill_work); + if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) && + has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG)) + cancel_delayed_work_sync(&acer_rfkill_work); - rfkill_unregister(wireless_rfkill); - rfkill_destroy(wireless_rfkill); + if (has_cap(ACER_CAP_WIRELESS)) { + rfkill_unregister(wireless_rfkill); + rfkill_destroy(wireless_rfkill); + } if (has_cap(ACER_CAP_BLUETOOTH)) { rfkill_unregister(bluetooth_rfkill); rfkill_destroy(bluetooth_rfkill); } + + if (has_cap(ACER_CAP_THREEG)) { + rfkill_unregister(threeg_rfkill); + rfkill_destroy(threeg_rfkill); + } return; } @@ -1047,7 +1656,11 @@ static ssize_t show_bool_threeg(struct device *dev, struct device_attribute *attr, char *buf) { u32 result; \ - acpi_status status = get_u32(&result, ACER_CAP_THREEG); + acpi_status status; + + pr_info("This threeg sysfs will be removed in 2012 - used by: %s\n", + current->comm); + status = get_u32(&result, ACER_CAP_THREEG); if (ACPI_SUCCESS(status)) return sprintf(buf, "%u\n", result); return sprintf(buf, "Read error\n"); @@ -1058,16 +1671,20 @@ static ssize_t set_bool_threeg(struct device *dev, { u32 tmp = simple_strtoul(buf, NULL, 10); acpi_status status = set_u32(tmp, ACER_CAP_THREEG); - if (ACPI_FAILURE(status)) - return -EINVAL; + pr_info("This threeg sysfs will be removed in 2012 - used by: %s\n", + current->comm); + if (ACPI_FAILURE(status)) + return -EINVAL; return count; } -static DEVICE_ATTR(threeg, S_IWUGO | S_IRUGO | S_IWUSR, show_bool_threeg, +static DEVICE_ATTR(threeg, S_IRUGO | S_IWUSR, show_bool_threeg, set_bool_threeg); static ssize_t show_interface(struct device *dev, struct device_attribute *attr, char *buf) { + pr_info("This interface sysfs will be removed in 2012 - used by: %s\n", + current->comm); switch (interface->type) { case ACER_AMW0: return sprintf(buf, "AMW0\n"); @@ -1075,13 +1692,281 @@ static ssize_t show_interface(struct device *dev, struct device_attribute *attr, return sprintf(buf, "AMW0 v2\n"); case ACER_WMID: return sprintf(buf, "WMID\n"); + case ACER_WMID_v2: + return sprintf(buf, "WMID v2\n"); default: return sprintf(buf, "Error!\n"); } } -static DEVICE_ATTR(interface, S_IWUGO | S_IRUGO | S_IWUSR, - show_interface, NULL); +static DEVICE_ATTR(interface, S_IRUGO, show_interface, NULL); + +static void acer_wmi_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + struct event_return_value return_value; + acpi_status status; + u16 device_state; + const struct key_entry *key; + u32 scancode; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_warn("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + + if (!obj) + return; + if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("Unknown response received %d\n", obj->type); + kfree(obj); + return; + } + if (obj->buffer.length != 8) { + pr_warn("Unknown buffer length %d\n", obj->buffer.length); + kfree(obj); + return; + } + + return_value = *((struct event_return_value *)obj->buffer.pointer); + kfree(obj); + + switch (return_value.function) { + case WMID_HOTKEY_EVENT: + device_state = return_value.device_state; + pr_debug("device state: 0x%x\n", device_state); + + key = sparse_keymap_entry_from_scancode(acer_wmi_input_dev, + return_value.key_num); + if (!key) { + pr_warn("Unknown key number - 0x%x\n", + return_value.key_num); + } else { + scancode = return_value.key_num; + switch (key->keycode) { + case KEY_WLAN: + case KEY_BLUETOOTH: + if (has_cap(ACER_CAP_WIRELESS)) + rfkill_set_sw_state(wireless_rfkill, + !(device_state & ACER_WMID3_GDS_WIRELESS)); + if (has_cap(ACER_CAP_THREEG)) + rfkill_set_sw_state(threeg_rfkill, + !(device_state & ACER_WMID3_GDS_THREEG)); + if (has_cap(ACER_CAP_BLUETOOTH)) + rfkill_set_sw_state(bluetooth_rfkill, + !(device_state & ACER_WMID3_GDS_BLUETOOTH)); + break; + case KEY_TOUCHPAD_TOGGLE: + scancode = (device_state & ACER_WMID3_GDS_TOUCHPAD) ? + KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF; + } + sparse_keymap_report_event(acer_wmi_input_dev, scancode, 1, true); + } + break; + case WMID_ACCEL_EVENT: + acer_gsensor_event(); + break; + default: + pr_warn("Unknown function number - %d - %d\n", + return_value.function, return_value.key_num); + break; + } +} + +static acpi_status +wmid3_set_lm_mode(struct lm_input_params *params, + struct lm_return_value *return_value) +{ + acpi_status status; + union acpi_object *obj; + + struct acpi_buffer input = { sizeof(struct lm_input_params), params }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + + status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &input, &output); + if (ACPI_FAILURE(status)) + return status; + + obj = output.pointer; + + if (!obj) + return AE_ERROR; + else if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return AE_ERROR; + } + if (obj->buffer.length != 4) { + pr_warn("Unknown buffer length %d\n", obj->buffer.length); + kfree(obj); + return AE_ERROR; + } + + *return_value = *((struct lm_return_value *)obj->buffer.pointer); + kfree(obj); + + return status; +} + +static int acer_wmi_enable_ec_raw(void) +{ + struct lm_return_value return_value; + acpi_status status; + struct lm_input_params params = { + .function_num = 0x1, + .commun_devices = 0xFFFF, + .devices = 0xFFFF, + .lm_status = 0x00, /* Launch Manager Deactive */ + }; + + status = wmid3_set_lm_mode(¶ms, &return_value); + + if (return_value.error_code || return_value.ec_return_value) + pr_warn("Enabling EC raw mode failed: 0x%x - 0x%x\n", + return_value.error_code, + return_value.ec_return_value); + else + pr_info("Enabled EC raw mode\n"); + + return status; +} + +static int acer_wmi_enable_lm(void) +{ + struct lm_return_value return_value; + acpi_status status; + struct lm_input_params params = { + .function_num = 0x1, + .commun_devices = 0xFFFF, + .devices = 0xFFFF, + .lm_status = 0x01, /* Launch Manager Active */ + }; + + status = wmid3_set_lm_mode(¶ms, &return_value); + + if (return_value.error_code || return_value.ec_return_value) + pr_warn("Enabling Launch Manager failed: 0x%x - 0x%x\n", + return_value.error_code, + return_value.ec_return_value); + + return status; +} + +static acpi_status __init acer_wmi_get_handle_cb(acpi_handle ah, u32 level, + void *ctx, void **retval) +{ + *(acpi_handle *)retval = ah; + return AE_OK; +} + +static int __init acer_wmi_get_handle(const char *name, const char *prop, + acpi_handle *ah) +{ + acpi_status status; + acpi_handle handle; + + BUG_ON(!name || !ah); + + handle = NULL; + status = acpi_get_devices(prop, acer_wmi_get_handle_cb, + (void *)name, &handle); + + if (ACPI_SUCCESS(status)) { + *ah = handle; + return 0; + } else { + return -ENODEV; + } +} + +static int __init acer_wmi_accel_setup(void) +{ + int err; + + err = acer_wmi_get_handle("SENR", "BST0001", &gsensor_handle); + if (err) + return err; + + interface->capability |= ACER_CAP_ACCEL; + + acer_wmi_accel_dev = input_allocate_device(); + if (!acer_wmi_accel_dev) + return -ENOMEM; + + acer_wmi_accel_dev->open = acer_gsensor_open; + + acer_wmi_accel_dev->name = "Acer BMA150 accelerometer"; + acer_wmi_accel_dev->phys = "wmi/input1"; + acer_wmi_accel_dev->id.bustype = BUS_HOST; + acer_wmi_accel_dev->evbit[0] = BIT_MASK(EV_ABS); + input_set_abs_params(acer_wmi_accel_dev, ABS_X, -16384, 16384, 0, 0); + input_set_abs_params(acer_wmi_accel_dev, ABS_Y, -16384, 16384, 0, 0); + input_set_abs_params(acer_wmi_accel_dev, ABS_Z, -16384, 16384, 0, 0); + + err = input_register_device(acer_wmi_accel_dev); + if (err) + goto err_free_dev; + + return 0; + +err_free_dev: + input_free_device(acer_wmi_accel_dev); + return err; +} + +static void acer_wmi_accel_destroy(void) +{ + input_unregister_device(acer_wmi_accel_dev); +} + +static int __init acer_wmi_input_setup(void) +{ + acpi_status status; + int err; + + acer_wmi_input_dev = input_allocate_device(); + if (!acer_wmi_input_dev) + return -ENOMEM; + + acer_wmi_input_dev->name = "Acer WMI hotkeys"; + acer_wmi_input_dev->phys = "wmi/input0"; + acer_wmi_input_dev->id.bustype = BUS_HOST; + + err = sparse_keymap_setup(acer_wmi_input_dev, acer_wmi_keymap, NULL); + if (err) + goto err_free_dev; + + status = wmi_install_notify_handler(ACERWMID_EVENT_GUID, + acer_wmi_notify, NULL); + if (ACPI_FAILURE(status)) { + err = -EIO; + goto err_free_keymap; + } + + err = input_register_device(acer_wmi_input_dev); + if (err) + goto err_uninstall_notifier; + + return 0; + +err_uninstall_notifier: + wmi_remove_notify_handler(ACERWMID_EVENT_GUID); +err_free_keymap: + sparse_keymap_free(acer_wmi_input_dev); +err_free_dev: + input_free_device(acer_wmi_input_dev); + return err; +} + +static void acer_wmi_input_destroy(void) +{ + wmi_remove_notify_handler(ACERWMID_EVENT_GUID); + sparse_keymap_free(acer_wmi_input_dev); + input_unregister_device(acer_wmi_input_dev); +} /* * debugfs functions @@ -1091,24 +1976,31 @@ static u32 get_wmid_devices(void) struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; union acpi_object *obj; acpi_status status; + u32 devices = 0; status = wmi_query_block(WMID_GUID2, 1, &out); if (ACPI_FAILURE(status)) return 0; obj = (union acpi_object *) out.pointer; - if (obj && obj->type == ACPI_TYPE_BUFFER && - obj->buffer.length == sizeof(u32)) { - return *((u32 *) obj->buffer.pointer); - } else { - return 0; + if (obj) { + if (obj->type == ACPI_TYPE_BUFFER && + (obj->buffer.length == sizeof(u32) || + obj->buffer.length == sizeof(u64))) { + devices = *((u32 *) obj->buffer.pointer); + } else if (obj->type == ACPI_TYPE_INTEGER) { + devices = (u32) obj->integer.value; + } } + + kfree(out.pointer); + return devices; } /* * Platform device */ -static int __devinit acer_platform_probe(struct platform_device *device) +static int acer_platform_probe(struct platform_device *device) { int err; @@ -1151,8 +2043,7 @@ static int acer_platform_remove(struct platform_device *device) return 0; } -static int acer_platform_suspend(struct platform_device *dev, -pm_message_t state) +static int acer_suspend(struct device *dev) { u32 value; struct acer_data *data = &interface->data; @@ -1162,6 +2053,7 @@ pm_message_t state) if (has_cap(ACER_CAP_MAILLED)) { get_u32(&value, ACER_CAP_MAILLED); + set_u32(LED_OFF, ACER_CAP_MAILLED); data->mailled = value; } @@ -1173,7 +2065,7 @@ pm_message_t state) return 0; } -static int acer_platform_resume(struct platform_device *device) +static int acer_resume(struct device *dev) { struct acer_data *data = &interface->data; @@ -1186,18 +2078,34 @@ static int acer_platform_resume(struct platform_device *device) if (has_cap(ACER_CAP_BRIGHTNESS)) set_u32(data->brightness, ACER_CAP_BRIGHTNESS); + if (has_cap(ACER_CAP_ACCEL)) + acer_gsensor_init(); + return 0; } +static SIMPLE_DEV_PM_OPS(acer_pm, acer_suspend, acer_resume); + +static void acer_platform_shutdown(struct platform_device *device) +{ + struct acer_data *data = &interface->data; + + if (!data) + return; + + if (has_cap(ACER_CAP_MAILLED)) + set_u32(LED_OFF, ACER_CAP_MAILLED); +} + static struct platform_driver acer_platform_driver = { .driver = { .name = "acer-wmi", .owner = THIS_MODULE, + .pm = &acer_pm, }, .probe = acer_platform_probe, .remove = acer_platform_remove, - .suspend = acer_platform_suspend, - .resume = acer_platform_resume, + .shutdown = acer_platform_shutdown, }; static struct platform_device *acer_platform_device; @@ -1245,7 +2153,7 @@ static int create_debugfs(void) { interface->debug.root = debugfs_create_dir("acer-wmi", NULL); if (!interface->debug.root) { - printk(ACER_ERR "Failed to create debugfs directory"); + pr_err("Failed to create debugfs directory"); return -ENOMEM; } @@ -1266,11 +2174,10 @@ static int __init acer_wmi_init(void) { int err; - printk(ACER_INFO "Acer Laptop ACPI-WMI Extras\n"); + pr_info("Acer Laptop ACPI-WMI Extras\n"); if (dmi_check_system(acer_blacklist)) { - printk(ACER_INFO "Blacklisted hardware detected - " - "not loading\n"); + pr_info("Blacklisted hardware detected - not loading\n"); return -ENODEV; } @@ -1285,14 +2192,21 @@ static int __init acer_wmi_init(void) if (!wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1)) interface = &wmid_interface; + if (wmi_has_guid(WMID_GUID3)) + interface = &wmid_v2_interface; + + if (interface) + dmi_walk(type_aa_dmi_decode, NULL); + if (wmi_has_guid(WMID_GUID2) && interface) { - if (ACPI_FAILURE(WMID_set_capabilities())) { - printk(ACER_ERR "Unable to detect available WMID " - "devices\n"); + if (!has_type_aa && ACPI_FAILURE(WMID_set_capabilities())) { + pr_err("Unable to detect available WMID devices\n"); return -ENODEV; } - } else if (!wmi_has_guid(WMID_GUID2) && interface) { - printk(ACER_ERR "No WMID device detection method found\n"); + /* WMID always provides brightness methods */ + interface->capability |= ACER_CAP_BRIGHTNESS; + } else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa) { + pr_err("No WMID device detection method found\n"); return -ENODEV; } @@ -1300,8 +2214,7 @@ static int __init acer_wmi_init(void) interface = &AMW0_interface; if (ACPI_FAILURE(AMW0_set_capabilities())) { - printk(ACER_ERR "Unable to detect available AMW0 " - "devices\n"); + pr_err("Unable to detect available AMW0 devices\n"); return -ENODEV; } } @@ -1310,35 +2223,69 @@ static int __init acer_wmi_init(void) AMW0_find_mailled(); if (!interface) { - printk(ACER_ERR "No or unsupported WMI interface, unable to " - "load\n"); + pr_err("No or unsupported WMI interface, unable to load\n"); return -ENODEV; } set_quirks(); - if (acpi_video_backlight_support() && has_cap(ACER_CAP_BRIGHTNESS)) { + if (dmi_check_system(video_vendor_dmi_table)) + acpi_video_dmi_promote_vendor(); + if (acpi_video_backlight_support()) { interface->capability &= ~ACER_CAP_BRIGHTNESS; - printk(ACER_INFO "Brightness must be controlled by " - "generic video driver\n"); + pr_info("Brightness must be controlled by acpi video driver\n"); + } else { + pr_info("Disabling ACPI video driver\n"); + acpi_video_unregister_backlight(); + } + + if (wmi_has_guid(WMID_GUID3)) { + if (ec_raw_mode) { + if (ACPI_FAILURE(acer_wmi_enable_ec_raw())) { + pr_err("Cannot enable EC raw mode\n"); + return -ENODEV; + } + } else if (ACPI_FAILURE(acer_wmi_enable_lm())) { + pr_err("Cannot enable Launch Manager mode\n"); + return -ENODEV; + } + } else if (ec_raw_mode) { + pr_info("No WMID EC raw mode enable method\n"); } - if (platform_driver_register(&acer_platform_driver)) { - printk(ACER_ERR "Unable to register platform driver.\n"); + if (wmi_has_guid(ACERWMID_EVENT_GUID)) { + err = acer_wmi_input_setup(); + if (err) + return err; + } + + acer_wmi_accel_setup(); + + err = platform_driver_register(&acer_platform_driver); + if (err) { + pr_err("Unable to register platform driver\n"); goto error_platform_register; } + acer_platform_device = platform_device_alloc("acer-wmi", -1); - platform_device_add(acer_platform_device); + if (!acer_platform_device) { + err = -ENOMEM; + goto error_device_alloc; + } + + err = platform_device_add(acer_platform_device); + if (err) + goto error_device_add; err = create_sysfs(); if (err) - return err; + goto error_create_sys; if (wmi_has_guid(WMID_GUID2)) { interface->debug.wmid_devices = get_wmid_devices(); err = create_debugfs(); if (err) - return err; + goto error_create_debugfs; } /* Override any initial settings with values from the commandline */ @@ -1346,18 +2293,37 @@ static int __init acer_wmi_init(void) return 0; +error_create_debugfs: + remove_sysfs(acer_platform_device); +error_create_sys: + platform_device_del(acer_platform_device); +error_device_add: + platform_device_put(acer_platform_device); +error_device_alloc: + platform_driver_unregister(&acer_platform_driver); error_platform_register: - return -ENODEV; + if (wmi_has_guid(ACERWMID_EVENT_GUID)) + acer_wmi_input_destroy(); + if (has_cap(ACER_CAP_ACCEL)) + acer_wmi_accel_destroy(); + + return err; } static void __exit acer_wmi_exit(void) { + if (wmi_has_guid(ACERWMID_EVENT_GUID)) + acer_wmi_input_destroy(); + + if (has_cap(ACER_CAP_ACCEL)) + acer_wmi_accel_destroy(); + remove_sysfs(acer_platform_device); remove_debugfs(); - platform_device_del(acer_platform_device); + platform_device_unregister(acer_platform_device); platform_driver_unregister(&acer_platform_driver); - printk(ACER_INFO "Acer Laptop WMI Extras unloaded\n"); + pr_info("Acer Laptop WMI Extras unloaded\n"); return; } diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index 7b2384d674d..f94467c0522 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -5,7 +5,7 @@ * * (C) 2009 - Peter Feuerer peter (a) piie.net * http://piie.net - * 2009 Borislav Petkov <petkovbb@gmail.com> + * 2009 Borislav Petkov bp (a) alien8.de * * Inspired by and many thanks to: * o acerfand - Rachel Greenham @@ -35,10 +35,8 @@ #include <linux/kernel.h> #include <linux/module.h> -#include <linux/fs.h> #include <linux/dmi.h> -#include <acpi/acpi_drivers.h> -#include <linux/sched.h> +#include <linux/acpi.h> #include <linux/thermal.h> #include <linux/platform_device.h> @@ -52,7 +50,7 @@ */ #undef START_IN_KERNEL_MODE -#define DRV_VER "0.5.22" +#define DRV_VER "0.5.26" /* * According to the Atom N270 datasheet, @@ -85,16 +83,16 @@ static int kernelmode; #endif static unsigned int interval = 10; -static unsigned int fanon = 63000; -static unsigned int fanoff = 58000; +static unsigned int fanon = 60000; +static unsigned int fanoff = 53000; static unsigned int verbose; static unsigned int fanstate = ACERHDF_FAN_AUTO; static char force_bios[16]; static char force_product[16]; static unsigned int prev_interval; -struct thermal_zone_device *thz_dev; -struct thermal_cooling_device *cl_dev; -struct platform_device *acerhdf_dev; +static struct thermal_zone_device *thz_dev; +static struct thermal_cooling_device *cl_dev; +static struct platform_device *acerhdf_dev; module_param(kernelmode, uint, 0); MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off"); @@ -112,14 +110,12 @@ module_param_string(force_product, force_product, 16, 0); MODULE_PARM_DESC(force_product, "Force BIOS product and omit BIOS check"); /* - * cmd_off: to switch the fan completely off - * chk_off: to check if the fan is off + * cmd_off: to switch the fan completely off and check if the fan is off * cmd_auto: to set the BIOS in control of the fan. The BIOS regulates then * the fan speed depending on the temperature */ struct fancmd { u8 cmd_off; - u8 chk_off; u8 cmd_auto; }; @@ -136,47 +132,100 @@ struct bios_settings_t { /* Register addresses and values for different BIOS versions */ static const struct bios_settings_t bios_tbl[] = { /* AOA110 */ - {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x21, 0x00} }, + {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00} }, /* AOA150 */ - {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00} }, + /* LT1005u */ + {"Acer", "LT-10Q", "v0.3310", 0x55, 0x58, {0x20, 0x00} }, /* Acer 1410 */ - {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3314", 0x55, 0x58, {0x9e, 0x00} }, /* Acer 1810xx */ - {"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3314", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3314", 0x55, 0x58, {0x9e, 0x00} }, + /* Acer 531 */ + {"Acer", "AO531h", "v0.3104", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AO531h", "v0.3201", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AO531h", "v0.3304", 0x55, 0x58, {0x20, 0x00} }, + /* Acer 751 */ + {"Acer", "AO751h", "V0.3212", 0x55, 0x58, {0x21, 0x00} }, + /* Acer 1825 */ + {"Acer", "Aspire 1825PTZ", "V1.3118", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1825PTZ", "V1.3127", 0x55, 0x58, {0x9e, 0x00} }, + /* Acer TravelMate 7730 */ + {"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00} }, /* Gateway */ - {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Gateway", "LT31", "v1.3103", 0x55, 0x58, {0x10, 0x0f, 0x00} }, - {"Gateway", "LT31", "v1.3201", 0x55, 0x58, {0x10, 0x0f, 0x00} }, - {"Gateway", "LT31", "v1.3302", 0x55, 0x58, {0x10, 0x0f, 0x00} }, + {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x00} }, + {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x00} }, + {"Gateway", "LT31", "v1.3103", 0x55, 0x58, {0x9e, 0x00} }, + {"Gateway", "LT31", "v1.3201", 0x55, 0x58, {0x9e, 0x00} }, + {"Gateway", "LT31", "v1.3302", 0x55, 0x58, {0x9e, 0x00} }, + {"Gateway", "LT31", "v1.3303t", 0x55, 0x58, {0x9e, 0x00} }, /* Packard Bell */ - {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Packard Bell", "DOTMU", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, + {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x00} }, + {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} }, + {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x00} }, + {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} }, + {"Packard Bell", "ENBFT", "V1.3118", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "ENBFT", "V1.3127", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMA", "v1.3201", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMA", "v1.3302", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMA", "v1.3303t", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTVR46", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, /* pewpew-terminator */ - {"", "", "", 0, 0, {0, 0, 0} } + {"", "", "", 0, 0, {0, 0} } }; static const struct bios_settings_t *bios_cfg __read_mostly; @@ -200,7 +249,7 @@ static int acerhdf_get_fanstate(int *state) if (ec_read(bios_cfg->fanreg, &fan)) return -EINVAL; - if (fan != bios_cfg->cmd.chk_off) + if (fan != bios_cfg->cmd.cmd_off) *state = ACERHDF_FAN_AUTO; else *state = ACERHDF_FAN_OFF; @@ -213,12 +262,11 @@ static void acerhdf_change_fanstate(int state) unsigned char cmd; if (verbose) - pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ? - "OFF" : "ON"); + pr_notice("fan %s\n", state == ACERHDF_FAN_OFF ? "OFF" : "ON"); if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) { pr_err("invalid fan state %d requested, setting to auto!\n", - state); + state); state = ACERHDF_FAN_AUTO; } @@ -233,19 +281,18 @@ static void acerhdf_check_param(struct thermal_zone_device *thermal) { if (fanon > ACERHDF_MAX_FANON) { pr_err("fanon temperature too high, set to %d\n", - ACERHDF_MAX_FANON); + ACERHDF_MAX_FANON); fanon = ACERHDF_MAX_FANON; } if (kernelmode && prev_interval != interval) { if (interval > ACERHDF_MAX_INTERVAL) { pr_err("interval too high, set to %d\n", - ACERHDF_MAX_INTERVAL); + ACERHDF_MAX_INTERVAL); interval = ACERHDF_MAX_INTERVAL; } if (verbose) - pr_notice("interval changed to: %d\n", - interval); + pr_notice("interval changed to: %d\n", interval); thermal->polling_delay = interval*1000; prev_interval = interval; } @@ -282,7 +329,8 @@ static int acerhdf_bind(struct thermal_zone_device *thermal, if (cdev != cl_dev) return 0; - if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) { + if (thermal_zone_bind_cooling_device(thermal, 0, cdev, + THERMAL_NO_LIMIT, THERMAL_NO_LIMIT)) { pr_err("error binding cooling dev\n"); return -EINVAL; } @@ -374,7 +422,7 @@ static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal, } /* bind callback functions to thermalzone */ -struct thermal_zone_device_ops acerhdf_dev_ops = { +static struct thermal_zone_device_ops acerhdf_dev_ops = { .bind = acerhdf_bind, .unbind = acerhdf_unbind, .get_temp = acerhdf_get_ec_temp, @@ -449,7 +497,7 @@ err_out: } /* bind fan callbacks to fan device */ -struct thermal_cooling_device_ops acerhdf_cooling_ops = { +static struct thermal_cooling_device_ops acerhdf_cooling_ops = { .get_max_state = acerhdf_get_max_state, .get_cur_state = acerhdf_get_cur_state, .set_cur_state = acerhdf_set_cur_state, @@ -467,7 +515,7 @@ static int acerhdf_suspend(struct device *dev) return 0; } -static int __devinit acerhdf_probe(struct platform_device *device) +static int acerhdf_probe(struct platform_device *device) { return 0; } @@ -518,6 +566,10 @@ static int acerhdf_check_hardware(void) version = dmi_get_system_info(DMI_BIOS_VERSION); product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (!vendor || !version || !product) { + pr_err("error getting hardware information\n"); + return -EINVAL; + } pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER); @@ -552,8 +604,8 @@ static int acerhdf_check_hardware(void) } if (!bios_cfg) { - pr_err("unknown (unsupported) BIOS version %s/%s/%s, " - "please report, aborting!\n", vendor, product, version); + pr_err("unknown (unsupported) BIOS version %s/%s/%s, please report, aborting!\n", + vendor, product, version); return -EINVAL; } @@ -563,8 +615,7 @@ static int acerhdf_check_hardware(void) */ if (!kernelmode) { pr_notice("Fan control off, to enable do:\n"); - pr_notice("echo -n \"enabled\" > " - "/sys/class/thermal/thermal_zone0/mode\n"); + pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zone0/mode\n"); } return 0; @@ -579,17 +630,26 @@ static int acerhdf_register_platform(void) return err; acerhdf_dev = platform_device_alloc("acerhdf", -1); - platform_device_add(acerhdf_dev); + if (!acerhdf_dev) { + err = -ENOMEM; + goto err_device_alloc; + } + err = platform_device_add(acerhdf_dev); + if (err) + goto err_device_add; return 0; + +err_device_add: + platform_device_put(acerhdf_dev); +err_device_alloc: + platform_driver_unregister(&acerhdf_driver); + return err; } static void acerhdf_unregister_platform(void) { - if (!acerhdf_dev) - return; - - platform_device_del(acerhdf_dev); + platform_device_unregister(acerhdf_dev); platform_driver_unregister(&acerhdf_driver); } @@ -601,8 +661,8 @@ static int acerhdf_register_thermal(void) if (IS_ERR(cl_dev)) return -EINVAL; - thz_dev = thermal_zone_device_register("acerhdf", 1, NULL, - &acerhdf_dev_ops, 0, 0, 0, + thz_dev = thermal_zone_device_register("acerhdf", 1, 0, NULL, + &acerhdf_dev_ops, NULL, 0, (kernelmode) ? interval*1000 : 0); if (IS_ERR(thz_dev)) return -EINVAL; @@ -633,7 +693,7 @@ static int __init acerhdf_init(void) err = acerhdf_register_platform(); if (err) - goto err_unreg; + goto out_err; err = acerhdf_register_thermal(); if (err) @@ -646,7 +706,7 @@ err_unreg: acerhdf_unregister_platform(); out_err: - return -ENODEV; + return err; } static void __exit acerhdf_exit(void) @@ -660,13 +720,20 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Peter Feuerer"); MODULE_DESCRIPTION("Aspire One temperature and fan driver"); MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:"); -MODULE_ALIAS("dmi:*:*Acer*:pnAspire 1410*:"); -MODULE_ALIAS("dmi:*:*Acer*:pnAspire 1810*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1410*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1810*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1825PTZ:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAO531*:"); +MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:"); MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:"); -MODULE_ALIAS("dmi:*:*Packard Bell*:pnAOA*:"); -MODULE_ALIAS("dmi:*:*Packard Bell*:pnDOA*:"); -MODULE_ALIAS("dmi:*:*Packard Bell*:pnDOTMU*:"); +MODULE_ALIAS("dmi:*:*Packard*Bell*:pnAOA*:"); +MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOA*:"); +MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMU*:"); +MODULE_ALIAS("dmi:*:*Packard*Bell*:pnENBFT*:"); +MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMA*:"); +MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTVR46*:"); module_init(acerhdf_init); module_exit(acerhdf_exit); diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c new file mode 100644 index 00000000000..297b6640213 --- /dev/null +++ b/drivers/platform/x86/alienware-wmi.c @@ -0,0 +1,622 @@ +/* + * Alienware AlienFX control + * + * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dmi.h> +#include <linux/acpi.h> +#include <linux/leds.h> + +#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" +#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" +#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" + +#define WMAX_METHOD_HDMI_SOURCE 0x1 +#define WMAX_METHOD_HDMI_STATUS 0x2 +#define WMAX_METHOD_BRIGHTNESS 0x3 +#define WMAX_METHOD_ZONE_CONTROL 0x4 +#define WMAX_METHOD_HDMI_CABLE 0x5 + +MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>"); +MODULE_DESCRIPTION("Alienware special feature control"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); +MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); + +enum INTERFACE_FLAGS { + LEGACY, + WMAX, +}; + +enum LEGACY_CONTROL_STATES { + LEGACY_RUNNING = 1, + LEGACY_BOOTING = 0, + LEGACY_SUSPEND = 3, +}; + +enum WMAX_CONTROL_STATES { + WMAX_RUNNING = 0xFF, + WMAX_BOOTING = 0, + WMAX_SUSPEND = 3, +}; + +struct quirk_entry { + u8 num_zones; +}; + +static struct quirk_entry *quirks; + +static struct quirk_entry quirk_unknown = { + .num_zones = 2, +}; + +static struct quirk_entry quirk_x51_family = { + .num_zones = 3, +}; + +static int dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +static struct dmi_system_id alienware_quirks[] = { + { + .callback = dmi_matched, + .ident = "Alienware X51 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), + }, + .driver_data = &quirk_x51_family, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), + }, + .driver_data = &quirk_x51_family, + }, + {} +}; + +struct color_platform { + u8 blue; + u8 green; + u8 red; +} __packed; + +struct platform_zone { + u8 location; + struct device_attribute *attr; + struct color_platform colors; +}; + +struct wmax_brightness_args { + u32 led_mask; + u32 percentage; +}; + +struct hdmi_args { + u8 arg; +}; + +struct legacy_led_args { + struct color_platform colors; + u8 brightness; + u8 state; +} __packed; + +struct wmax_led_args { + u32 led_mask; + struct color_platform colors; + u8 state; +} __packed; + +static struct platform_device *platform_device; +static struct device_attribute *zone_dev_attrs; +static struct attribute **zone_attrs; +static struct platform_zone *zone_data; + +static struct platform_driver platform_driver = { + .driver = { + .name = "alienware-wmi", + .owner = THIS_MODULE, + } +}; + +static struct attribute_group zone_attribute_group = { + .name = "rgb_zones", +}; + +static u8 interface; +static u8 lighting_control_state; +static u8 global_brightness; + +/* + * Helpers used for zone control +*/ +static int parse_rgb(const char *buf, struct platform_zone *zone) +{ + long unsigned int rgb; + int ret; + union color_union { + struct color_platform cp; + int package; + } repackager; + + ret = kstrtoul(buf, 16, &rgb); + if (ret) + return ret; + + /* RGB triplet notation is 24-bit hexadecimal */ + if (rgb > 0xFFFFFF) + return -EINVAL; + + repackager.package = rgb & 0x0f0f0f0f; + pr_debug("alienware-wmi: r: %d g:%d b: %d\n", + repackager.cp.red, repackager.cp.green, repackager.cp.blue); + zone->colors = repackager.cp; + return 0; +} + +static struct platform_zone *match_zone(struct device_attribute *attr) +{ + int i; + for (i = 0; i < quirks->num_zones; i++) { + if ((struct device_attribute *)zone_data[i].attr == attr) { + pr_debug("alienware-wmi: matched zone location: %d\n", + zone_data[i].location); + return &zone_data[i]; + } + } + return NULL; +} + +/* + * Individual RGB zone control +*/ +static int alienware_update_led(struct platform_zone *zone) +{ + int method_id; + acpi_status status; + char *guid; + struct acpi_buffer input; + struct legacy_led_args legacy_args; + struct wmax_led_args wmax_args; + if (interface == WMAX) { + wmax_args.led_mask = 1 << zone->location; + wmax_args.colors = zone->colors; + wmax_args.state = lighting_control_state; + guid = WMAX_CONTROL_GUID; + method_id = WMAX_METHOD_ZONE_CONTROL; + + input.length = (acpi_size) sizeof(wmax_args); + input.pointer = &wmax_args; + } else { + legacy_args.colors = zone->colors; + legacy_args.brightness = global_brightness; + legacy_args.state = 0; + if (lighting_control_state == LEGACY_BOOTING || + lighting_control_state == LEGACY_SUSPEND) { + guid = LEGACY_POWER_CONTROL_GUID; + legacy_args.state = lighting_control_state; + } else + guid = LEGACY_CONTROL_GUID; + method_id = zone->location + 1; + + input.length = (acpi_size) sizeof(legacy_args); + input.pointer = &legacy_args; + } + pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); + + status = wmi_evaluate_method(guid, 1, method_id, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: zone set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static ssize_t zone_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_zone *target_zone; + target_zone = match_zone(attr); + if (target_zone == NULL) + return sprintf(buf, "red: -1, green: -1, blue: -1\n"); + return sprintf(buf, "red: %d, green: %d, blue: %d\n", + target_zone->colors.red, + target_zone->colors.green, target_zone->colors.blue); + +} + +static ssize_t zone_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_zone *target_zone; + int ret; + target_zone = match_zone(attr); + if (target_zone == NULL) { + pr_err("alienware-wmi: invalid target zone\n"); + return 1; + } + ret = parse_rgb(buf, target_zone); + if (ret) + return ret; + ret = alienware_update_led(target_zone); + return ret ? ret : count; +} + +/* + * LED Brightness (Global) +*/ +static int wmax_brightness(int brightness) +{ + acpi_status status; + struct acpi_buffer input; + struct wmax_brightness_args args = { + .led_mask = 0xFF, + .percentage = brightness, + }; + input.length = (acpi_size) sizeof(args); + input.pointer = &args; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, + WMAX_METHOD_BRIGHTNESS, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: brightness set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static void global_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int ret; + global_brightness = brightness; + if (interface == WMAX) + ret = wmax_brightness(brightness); + else + ret = alienware_update_led(&zone_data[0]); + if (ret) + pr_err("LED brightness update failed\n"); +} + +static enum led_brightness global_led_get(struct led_classdev *led_cdev) +{ + return global_brightness; +} + +static struct led_classdev global_led = { + .brightness_set = global_led_set, + .brightness_get = global_led_get, + .name = "alienware::global_brightness", +}; + +/* + * Lighting control state device attribute (Global) +*/ +static ssize_t show_control_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (lighting_control_state == LEGACY_BOOTING) + return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); + else if (lighting_control_state == LEGACY_SUSPEND) + return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); + return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); +} + +static ssize_t store_control_state(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long unsigned int val; + if (strcmp(buf, "booting\n") == 0) + val = LEGACY_BOOTING; + else if (strcmp(buf, "suspend\n") == 0) + val = LEGACY_SUSPEND; + else if (interface == LEGACY) + val = LEGACY_RUNNING; + else + val = WMAX_RUNNING; + lighting_control_state = val; + pr_debug("alienware-wmi: updated control state to %d\n", + lighting_control_state); + return count; +} + +static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, + store_control_state); + +static int alienware_zone_init(struct platform_device *dev) +{ + int i; + char buffer[10]; + char *name; + + if (interface == WMAX) { + lighting_control_state = WMAX_RUNNING; + } else if (interface == LEGACY) { + lighting_control_state = LEGACY_RUNNING; + } + global_led.max_brightness = 0x0F; + global_brightness = global_led.max_brightness; + + /* + * - zone_dev_attrs num_zones + 1 is for individual zones and then + * null terminated + * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + + * the lighting control + null terminated + * - zone_data num_zones is for the distinct zones + */ + zone_dev_attrs = + kzalloc(sizeof(struct device_attribute) * (quirks->num_zones + 1), + GFP_KERNEL); + if (!zone_dev_attrs) + return -ENOMEM; + + zone_attrs = + kzalloc(sizeof(struct attribute *) * (quirks->num_zones + 2), + GFP_KERNEL); + if (!zone_attrs) + return -ENOMEM; + + zone_data = + kzalloc(sizeof(struct platform_zone) * (quirks->num_zones), + GFP_KERNEL); + if (!zone_data) + return -ENOMEM; + + for (i = 0; i < quirks->num_zones; i++) { + sprintf(buffer, "zone%02X", i); + name = kstrdup(buffer, GFP_KERNEL); + if (name == NULL) + return 1; + sysfs_attr_init(&zone_dev_attrs[i].attr); + zone_dev_attrs[i].attr.name = name; + zone_dev_attrs[i].attr.mode = 0644; + zone_dev_attrs[i].show = zone_show; + zone_dev_attrs[i].store = zone_set; + zone_data[i].location = i; + zone_attrs[i] = &zone_dev_attrs[i].attr; + zone_data[i].attr = &zone_dev_attrs[i]; + } + zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; + zone_attribute_group.attrs = zone_attrs; + + led_classdev_register(&dev->dev, &global_led); + + return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); +} + +static void alienware_zone_exit(struct platform_device *dev) +{ + sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); + led_classdev_unregister(&global_led); + if (zone_dev_attrs) { + int i; + for (i = 0; i < quirks->num_zones; i++) + kfree(zone_dev_attrs[i].attr.name); + } + kfree(zone_dev_attrs); + kfree(zone_data); + kfree(zone_attrs); +} + +/* + The HDMI mux sysfs node indicates the status of the HDMI input mux. + It can toggle between standard system GPU output and HDMI input. +*/ +static acpi_status alienware_hdmi_command(struct hdmi_args *in_args, + u32 command, int *out_data) +{ + acpi_status status; + union acpi_object *obj; + struct acpi_buffer input; + struct acpi_buffer output; + + input.length = (acpi_size) sizeof(*in_args); + input.pointer = in_args; + if (out_data != NULL) { + output.length = ACPI_ALLOCATE_BUFFER; + output.pointer = NULL; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, + command, &input, &output); + } else + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 1, + command, &input, NULL); + + if (ACPI_SUCCESS(status) && out_data != NULL) { + obj = (union acpi_object *)output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + *out_data = (u32) obj->integer.value; + } + return status; + +} + +static ssize_t show_hdmi_cable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct hdmi_args in_args = { + .arg = 0, + }; + status = + alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_CABLE, + (u32 *) &out_data); + if (ACPI_SUCCESS(status)) { + if (out_data == 0) + return scnprintf(buf, PAGE_SIZE, + "[unconnected] connected unknown\n"); + else if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "unconnected [connected] unknown\n"); + } + pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); +} + +static ssize_t show_hdmi_source(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct hdmi_args in_args = { + .arg = 0, + }; + status = + alienware_hdmi_command(&in_args, WMAX_METHOD_HDMI_STATUS, + (u32 *) &out_data); + + if (ACPI_SUCCESS(status)) { + if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "[input] gpu unknown\n"); + else if (out_data == 2) + return scnprintf(buf, PAGE_SIZE, + "input [gpu] unknown\n"); + } + pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data); + return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); +} + +static ssize_t toggle_hdmi_source(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + acpi_status status; + struct hdmi_args args; + if (strcmp(buf, "gpu\n") == 0) + args.arg = 1; + else if (strcmp(buf, "input\n") == 0) + args.arg = 2; + else + args.arg = 3; + pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); + + status = alienware_hdmi_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL); + + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", + status); + return count; +} + +static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); +static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, + toggle_hdmi_source); + +static struct attribute *hdmi_attrs[] = { + &dev_attr_cable.attr, + &dev_attr_source.attr, + NULL, +}; + +static struct attribute_group hdmi_attribute_group = { + .name = "hdmi", + .attrs = hdmi_attrs, +}; + +static void remove_hdmi(struct platform_device *dev) +{ + sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); +} + +static int create_hdmi(struct platform_device *dev) +{ + int ret; + + ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); + if (ret) + goto error_create_hdmi; + return 0; + +error_create_hdmi: + remove_hdmi(dev); + return ret; +} + +static int __init alienware_wmi_init(void) +{ + int ret; + + if (wmi_has_guid(LEGACY_CONTROL_GUID)) + interface = LEGACY; + else if (wmi_has_guid(WMAX_CONTROL_GUID)) + interface = WMAX; + else { + pr_warn("alienware-wmi: No known WMI GUID found\n"); + return -ENODEV; + } + + dmi_check_system(alienware_quirks); + if (quirks == NULL) + quirks = &quirk_unknown; + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + platform_device = platform_device_alloc("alienware-wmi", -1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device1; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device2; + + if (interface == WMAX) { + ret = create_hdmi(platform_device); + if (ret) + goto fail_prep_hdmi; + } + + ret = alienware_zone_init(platform_device); + if (ret) + goto fail_prep_zones; + + return 0; + +fail_prep_zones: + alienware_zone_exit(platform_device); +fail_prep_hdmi: + platform_device_del(platform_device); +fail_platform_device2: + platform_device_put(platform_device); +fail_platform_device1: + platform_driver_unregister(&platform_driver); +fail_platform_driver: + return ret; +} + +module_init(alienware_wmi_init); + +static void __exit alienware_wmi_exit(void) +{ + if (platform_device) { + alienware_zone_exit(platform_device); + remove_hdmi(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } +} + +module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/amilo-rfkill.c b/drivers/platform/x86/amilo-rfkill.c new file mode 100644 index 00000000000..da36b5e824d --- /dev/null +++ b/drivers/platform/x86/amilo-rfkill.c @@ -0,0 +1,183 @@ +/* + * Support for rfkill on some Fujitsu-Siemens Amilo laptops. + * Copyright 2011 Ben Hutchings. + * + * Based in part on the fsam7440 driver, which is: + * Copyright 2005 Alejandro Vidal Mata & Javier Vidal Mata. + * and on the fsaa1655g driver, which is: + * Copyright 2006 Martin Večeřa. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/dmi.h> +#include <linux/i8042.h> +#include <linux/io.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/rfkill.h> + +/* + * These values were obtained from disassembling and debugging the + * PM.exe program installed in the Fujitsu-Siemens AMILO A1655G + */ +#define A1655_WIFI_COMMAND 0x10C5 +#define A1655_WIFI_ON 0x25 +#define A1655_WIFI_OFF 0x45 + +static int amilo_a1655_rfkill_set_block(void *data, bool blocked) +{ + u8 param = blocked ? A1655_WIFI_OFF : A1655_WIFI_ON; + int rc; + + i8042_lock_chip(); + rc = i8042_command(¶m, A1655_WIFI_COMMAND); + i8042_unlock_chip(); + return rc; +} + +static const struct rfkill_ops amilo_a1655_rfkill_ops = { + .set_block = amilo_a1655_rfkill_set_block +}; + +/* + * These values were obtained from disassembling the PM.exe program + * installed in the Fujitsu-Siemens AMILO M 7440 + */ +#define M7440_PORT1 0x118f +#define M7440_PORT2 0x118e +#define M7440_RADIO_ON1 0x12 +#define M7440_RADIO_ON2 0x80 +#define M7440_RADIO_OFF1 0x10 +#define M7440_RADIO_OFF2 0x00 + +static int amilo_m7440_rfkill_set_block(void *data, bool blocked) +{ + u8 val1 = blocked ? M7440_RADIO_OFF1 : M7440_RADIO_ON1; + u8 val2 = blocked ? M7440_RADIO_OFF2 : M7440_RADIO_ON2; + + outb(val1, M7440_PORT1); + outb(val2, M7440_PORT2); + + /* Check whether the state has changed correctly */ + if (inb(M7440_PORT1) != val1 || inb(M7440_PORT2) != val2) + return -EIO; + + return 0; +} + +static const struct rfkill_ops amilo_m7440_rfkill_ops = { + .set_block = amilo_m7440_rfkill_set_block +}; + +static const struct dmi_system_id amilo_rfkill_id_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_BOARD_NAME, "AMILO A1655"), + }, + .driver_data = (void *)&amilo_a1655_rfkill_ops + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_BOARD_NAME, "AMILO L1310"), + }, + .driver_data = (void *)&amilo_a1655_rfkill_ops + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_BOARD_NAME, "AMILO M7440"), + }, + .driver_data = (void *)&amilo_m7440_rfkill_ops + }, + {} +}; + +static struct platform_device *amilo_rfkill_pdev; +static struct rfkill *amilo_rfkill_dev; + +static int amilo_rfkill_probe(struct platform_device *device) +{ + int rc; + const struct dmi_system_id *system_id = + dmi_first_match(amilo_rfkill_id_table); + + if (!system_id) + return -ENXIO; + + amilo_rfkill_dev = rfkill_alloc(KBUILD_MODNAME, &device->dev, + RFKILL_TYPE_WLAN, + system_id->driver_data, NULL); + if (!amilo_rfkill_dev) + return -ENOMEM; + + rc = rfkill_register(amilo_rfkill_dev); + if (rc) + goto fail; + + return 0; + +fail: + rfkill_destroy(amilo_rfkill_dev); + return rc; +} + +static int amilo_rfkill_remove(struct platform_device *device) +{ + rfkill_unregister(amilo_rfkill_dev); + rfkill_destroy(amilo_rfkill_dev); + return 0; +} + +static struct platform_driver amilo_rfkill_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + }, + .probe = amilo_rfkill_probe, + .remove = amilo_rfkill_remove, +}; + +static int __init amilo_rfkill_init(void) +{ + int rc; + + if (dmi_first_match(amilo_rfkill_id_table) == NULL) + return -ENODEV; + + rc = platform_driver_register(&amilo_rfkill_driver); + if (rc) + return rc; + + amilo_rfkill_pdev = platform_device_register_simple(KBUILD_MODNAME, -1, + NULL, 0); + if (IS_ERR(amilo_rfkill_pdev)) { + rc = PTR_ERR(amilo_rfkill_pdev); + goto fail; + } + + return 0; + +fail: + platform_driver_unregister(&amilo_rfkill_driver); + return rc; +} + +static void __exit amilo_rfkill_exit(void) +{ + platform_device_unregister(amilo_rfkill_pdev); + platform_driver_unregister(&amilo_rfkill_driver); +} + +MODULE_AUTHOR("Ben Hutchings <ben@decadent.org.uk>"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(dmi, amilo_rfkill_id_table); + +module_init(amilo_rfkill_init); +module_exit(amilo_rfkill_exit); diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c new file mode 100644 index 00000000000..b9429fbf1cd --- /dev/null +++ b/drivers/platform/x86/apple-gmux.c @@ -0,0 +1,643 @@ +/* + * Gmux driver for Apple laptops + * + * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> + * Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/backlight.h> +#include <linux/acpi.h> +#include <linux/pnp.h> +#include <linux/apple_bl.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/vga_switcheroo.h> +#include <acpi/video.h> +#include <asm/io.h> + +struct apple_gmux_data { + unsigned long iostart; + unsigned long iolen; + bool indexed; + struct mutex index_lock; + + struct backlight_device *bdev; + + /* switcheroo data */ + acpi_handle dhandle; + int gpe; + enum vga_switcheroo_client_id resume_client_id; + enum vga_switcheroo_state power_state; + struct completion powerchange_done; +}; + +static struct apple_gmux_data *apple_gmux_data; + +/* + * gmux port offsets. Many of these are not yet used, but may be in the + * future, and it's useful to have them documented here anyhow. + */ +#define GMUX_PORT_VERSION_MAJOR 0x04 +#define GMUX_PORT_VERSION_MINOR 0x05 +#define GMUX_PORT_VERSION_RELEASE 0x06 +#define GMUX_PORT_SWITCH_DISPLAY 0x10 +#define GMUX_PORT_SWITCH_GET_DISPLAY 0x11 +#define GMUX_PORT_INTERRUPT_ENABLE 0x14 +#define GMUX_PORT_INTERRUPT_STATUS 0x16 +#define GMUX_PORT_SWITCH_DDC 0x28 +#define GMUX_PORT_SWITCH_EXTERNAL 0x40 +#define GMUX_PORT_SWITCH_GET_EXTERNAL 0x41 +#define GMUX_PORT_DISCRETE_POWER 0x50 +#define GMUX_PORT_MAX_BRIGHTNESS 0x70 +#define GMUX_PORT_BRIGHTNESS 0x74 +#define GMUX_PORT_VALUE 0xc2 +#define GMUX_PORT_READ 0xd0 +#define GMUX_PORT_WRITE 0xd4 + +#define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4) + +#define GMUX_INTERRUPT_ENABLE 0xff +#define GMUX_INTERRUPT_DISABLE 0x00 + +#define GMUX_INTERRUPT_STATUS_ACTIVE 0 +#define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0) +#define GMUX_INTERRUPT_STATUS_POWER (1 << 2) +#define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3) + +#define GMUX_BRIGHTNESS_MASK 0x00ffffff +#define GMUX_MAX_BRIGHTNESS GMUX_BRIGHTNESS_MASK + +static u8 gmux_pio_read8(struct apple_gmux_data *gmux_data, int port) +{ + return inb(gmux_data->iostart + port); +} + +static void gmux_pio_write8(struct apple_gmux_data *gmux_data, int port, + u8 val) +{ + outb(val, gmux_data->iostart + port); +} + +static u32 gmux_pio_read32(struct apple_gmux_data *gmux_data, int port) +{ + return inl(gmux_data->iostart + port); +} + +static void gmux_pio_write32(struct apple_gmux_data *gmux_data, int port, + u32 val) +{ + int i; + u8 tmpval; + + for (i = 0; i < 4; i++) { + tmpval = (val >> (i * 8)) & 0xff; + outb(tmpval, gmux_data->iostart + port + i); + } +} + +static int gmux_index_wait_ready(struct apple_gmux_data *gmux_data) +{ + int i = 200; + u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE); + + while (i && (gwr & 0x01)) { + inb(gmux_data->iostart + GMUX_PORT_READ); + gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE); + udelay(100); + i--; + } + + return !!i; +} + +static int gmux_index_wait_complete(struct apple_gmux_data *gmux_data) +{ + int i = 200; + u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE); + + while (i && !(gwr & 0x01)) { + gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE); + udelay(100); + i--; + } + + if (gwr & 0x01) + inb(gmux_data->iostart + GMUX_PORT_READ); + + return !!i; +} + +static u8 gmux_index_read8(struct apple_gmux_data *gmux_data, int port) +{ + u8 val; + + mutex_lock(&gmux_data->index_lock); + gmux_index_wait_ready(gmux_data); + outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ); + gmux_index_wait_complete(gmux_data); + val = inb(gmux_data->iostart + GMUX_PORT_VALUE); + mutex_unlock(&gmux_data->index_lock); + + return val; +} + +static void gmux_index_write8(struct apple_gmux_data *gmux_data, int port, + u8 val) +{ + mutex_lock(&gmux_data->index_lock); + outb(val, gmux_data->iostart + GMUX_PORT_VALUE); + gmux_index_wait_ready(gmux_data); + outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE); + gmux_index_wait_complete(gmux_data); + mutex_unlock(&gmux_data->index_lock); +} + +static u32 gmux_index_read32(struct apple_gmux_data *gmux_data, int port) +{ + u32 val; + + mutex_lock(&gmux_data->index_lock); + gmux_index_wait_ready(gmux_data); + outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ); + gmux_index_wait_complete(gmux_data); + val = inl(gmux_data->iostart + GMUX_PORT_VALUE); + mutex_unlock(&gmux_data->index_lock); + + return val; +} + +static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port, + u32 val) +{ + int i; + u8 tmpval; + + mutex_lock(&gmux_data->index_lock); + + for (i = 0; i < 4; i++) { + tmpval = (val >> (i * 8)) & 0xff; + outb(tmpval, gmux_data->iostart + GMUX_PORT_VALUE + i); + } + + gmux_index_wait_ready(gmux_data); + outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE); + gmux_index_wait_complete(gmux_data); + mutex_unlock(&gmux_data->index_lock); +} + +static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port) +{ + if (gmux_data->indexed) + return gmux_index_read8(gmux_data, port); + else + return gmux_pio_read8(gmux_data, port); +} + +static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val) +{ + if (gmux_data->indexed) + gmux_index_write8(gmux_data, port, val); + else + gmux_pio_write8(gmux_data, port, val); +} + +static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port) +{ + if (gmux_data->indexed) + return gmux_index_read32(gmux_data, port); + else + return gmux_pio_read32(gmux_data, port); +} + +static void gmux_write32(struct apple_gmux_data *gmux_data, int port, + u32 val) +{ + if (gmux_data->indexed) + gmux_index_write32(gmux_data, port, val); + else + gmux_pio_write32(gmux_data, port, val); +} + +static bool gmux_is_indexed(struct apple_gmux_data *gmux_data) +{ + u16 val; + + outb(0xaa, gmux_data->iostart + 0xcc); + outb(0x55, gmux_data->iostart + 0xcd); + outb(0x00, gmux_data->iostart + 0xce); + + val = inb(gmux_data->iostart + 0xcc) | + (inb(gmux_data->iostart + 0xcd) << 8); + + if (val == 0x55aa) + return true; + + return false; +} + +static int gmux_get_brightness(struct backlight_device *bd) +{ + struct apple_gmux_data *gmux_data = bl_get_data(bd); + return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) & + GMUX_BRIGHTNESS_MASK; +} + +static int gmux_update_status(struct backlight_device *bd) +{ + struct apple_gmux_data *gmux_data = bl_get_data(bd); + u32 brightness = bd->props.brightness; + + if (bd->props.state & BL_CORE_SUSPENDED) + return 0; + + gmux_write32(gmux_data, GMUX_PORT_BRIGHTNESS, brightness); + + return 0; +} + +static const struct backlight_ops gmux_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .get_brightness = gmux_get_brightness, + .update_status = gmux_update_status, +}; + +static int gmux_switchto(enum vga_switcheroo_client_id id) +{ + if (id == VGA_SWITCHEROO_IGD) { + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1); + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 2); + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 2); + } else { + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 2); + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 3); + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3); + } + + return 0; +} + +static int gmux_set_discrete_state(struct apple_gmux_data *gmux_data, + enum vga_switcheroo_state state) +{ + reinit_completion(&gmux_data->powerchange_done); + + if (state == VGA_SWITCHEROO_ON) { + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1); + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 3); + pr_debug("Discrete card powered up\n"); + } else { + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1); + gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 0); + pr_debug("Discrete card powered down\n"); + } + + gmux_data->power_state = state; + + if (gmux_data->gpe >= 0 && + !wait_for_completion_interruptible_timeout(&gmux_data->powerchange_done, + msecs_to_jiffies(200))) + pr_warn("Timeout waiting for gmux switch to complete\n"); + + return 0; +} + +static int gmux_set_power_state(enum vga_switcheroo_client_id id, + enum vga_switcheroo_state state) +{ + if (id == VGA_SWITCHEROO_IGD) + return 0; + + return gmux_set_discrete_state(apple_gmux_data, state); +} + +static int gmux_get_client_id(struct pci_dev *pdev) +{ + /* + * Early Macbook Pros with switchable graphics use nvidia + * integrated graphics. Hardcode that the 9400M is integrated. + */ + if (pdev->vendor == PCI_VENDOR_ID_INTEL) + return VGA_SWITCHEROO_IGD; + else if (pdev->vendor == PCI_VENDOR_ID_NVIDIA && + pdev->device == 0x0863) + return VGA_SWITCHEROO_IGD; + else + return VGA_SWITCHEROO_DIS; +} + +static enum vga_switcheroo_client_id +gmux_active_client(struct apple_gmux_data *gmux_data) +{ + if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DISPLAY) == 2) + return VGA_SWITCHEROO_IGD; + + return VGA_SWITCHEROO_DIS; +} + +static struct vga_switcheroo_handler gmux_handler = { + .switchto = gmux_switchto, + .power_state = gmux_set_power_state, + .get_client_id = gmux_get_client_id, +}; + +static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data) +{ + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE, + GMUX_INTERRUPT_DISABLE); +} + +static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data) +{ + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE, + GMUX_INTERRUPT_ENABLE); +} + +static inline u8 gmux_interrupt_get_status(struct apple_gmux_data *gmux_data) +{ + return gmux_read8(gmux_data, GMUX_PORT_INTERRUPT_STATUS); +} + +static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data) +{ + u8 status; + + /* to clear interrupts write back current status */ + status = gmux_interrupt_get_status(gmux_data); + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status); +} + +static void gmux_notify_handler(acpi_handle device, u32 value, void *context) +{ + u8 status; + struct pnp_dev *pnp = (struct pnp_dev *)context; + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); + + status = gmux_interrupt_get_status(gmux_data); + gmux_disable_interrupts(gmux_data); + pr_debug("Notify handler called: status %d\n", status); + + gmux_clear_interrupts(gmux_data); + gmux_enable_interrupts(gmux_data); + + if (status & GMUX_INTERRUPT_STATUS_POWER) + complete(&gmux_data->powerchange_done); +} + +static int gmux_suspend(struct device *dev) +{ + struct pnp_dev *pnp = to_pnp_dev(dev); + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); + + gmux_data->resume_client_id = gmux_active_client(gmux_data); + gmux_disable_interrupts(gmux_data); + return 0; +} + +static int gmux_resume(struct device *dev) +{ + struct pnp_dev *pnp = to_pnp_dev(dev); + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); + + gmux_enable_interrupts(gmux_data); + gmux_switchto(gmux_data->resume_client_id); + if (gmux_data->power_state == VGA_SWITCHEROO_OFF) + gmux_set_discrete_state(gmux_data, gmux_data->power_state); + return 0; +} + +static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) +{ + struct apple_gmux_data *gmux_data; + struct resource *res; + struct backlight_properties props; + struct backlight_device *bdev; + u8 ver_major, ver_minor, ver_release; + int ret = -ENXIO; + acpi_status status; + unsigned long long gpe; + + if (apple_gmux_data) + return -EBUSY; + + gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL); + if (!gmux_data) + return -ENOMEM; + pnp_set_drvdata(pnp, gmux_data); + + res = pnp_get_resource(pnp, IORESOURCE_IO, 0); + if (!res) { + pr_err("Failed to find gmux I/O resource\n"); + goto err_free; + } + + gmux_data->iostart = res->start; + gmux_data->iolen = res->end - res->start; + + if (gmux_data->iolen < GMUX_MIN_IO_LEN) { + pr_err("gmux I/O region too small (%lu < %u)\n", + gmux_data->iolen, GMUX_MIN_IO_LEN); + goto err_free; + } + + if (!request_region(gmux_data->iostart, gmux_data->iolen, + "Apple gmux")) { + pr_err("gmux I/O already in use\n"); + goto err_free; + } + + /* + * Invalid version information may indicate either that the gmux + * device isn't present or that it's a new one that uses indexed + * io + */ + + ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR); + ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR); + ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE); + if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) { + if (gmux_is_indexed(gmux_data)) { + u32 version; + mutex_init(&gmux_data->index_lock); + gmux_data->indexed = true; + version = gmux_read32(gmux_data, + GMUX_PORT_VERSION_MAJOR); + ver_major = (version >> 24) & 0xff; + ver_minor = (version >> 16) & 0xff; + ver_release = (version >> 8) & 0xff; + } else { + pr_info("gmux device not present\n"); + ret = -ENODEV; + goto err_release; + } + } + pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor, + ver_release, (gmux_data->indexed ? "indexed" : "classic")); + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS); + + /* + * Currently it's assumed that the maximum brightness is less than + * 2^24 for compatibility with old gmux versions. Cap the max + * brightness at this value, but print a warning if the hardware + * reports something higher so that it can be fixed. + */ + if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS)) + props.max_brightness = GMUX_MAX_BRIGHTNESS; + + bdev = backlight_device_register("gmux_backlight", &pnp->dev, + gmux_data, &gmux_bl_ops, &props); + if (IS_ERR(bdev)) { + ret = PTR_ERR(bdev); + goto err_release; + } + + gmux_data->bdev = bdev; + bdev->props.brightness = gmux_get_brightness(bdev); + backlight_update_status(bdev); + + /* + * The backlight situation on Macs is complicated. If the gmux is + * present it's the best choice, because it always works for + * backlight control and supports more levels than other options. + * Disable the other backlight choices. + */ + acpi_video_dmi_promote_vendor(); + acpi_video_unregister(); + apple_bl_unregister(); + + gmux_data->power_state = VGA_SWITCHEROO_ON; + + gmux_data->dhandle = ACPI_HANDLE(&pnp->dev); + if (!gmux_data->dhandle) { + pr_err("Cannot find acpi handle for pnp device %s\n", + dev_name(&pnp->dev)); + ret = -ENODEV; + goto err_notify; + } + + status = acpi_evaluate_integer(gmux_data->dhandle, "GMGP", NULL, &gpe); + if (ACPI_SUCCESS(status)) { + gmux_data->gpe = (int)gpe; + + status = acpi_install_notify_handler(gmux_data->dhandle, + ACPI_DEVICE_NOTIFY, + &gmux_notify_handler, pnp); + if (ACPI_FAILURE(status)) { + pr_err("Install notify handler failed: %s\n", + acpi_format_exception(status)); + ret = -ENODEV; + goto err_notify; + } + + status = acpi_enable_gpe(NULL, gmux_data->gpe); + if (ACPI_FAILURE(status)) { + pr_err("Cannot enable gpe: %s\n", + acpi_format_exception(status)); + goto err_enable_gpe; + } + } else { + pr_warn("No GPE found for gmux\n"); + gmux_data->gpe = -1; + } + + if (vga_switcheroo_register_handler(&gmux_handler)) { + ret = -ENODEV; + goto err_register_handler; + } + + init_completion(&gmux_data->powerchange_done); + apple_gmux_data = gmux_data; + gmux_enable_interrupts(gmux_data); + + return 0; + +err_register_handler: + if (gmux_data->gpe >= 0) + acpi_disable_gpe(NULL, gmux_data->gpe); +err_enable_gpe: + if (gmux_data->gpe >= 0) + acpi_remove_notify_handler(gmux_data->dhandle, + ACPI_DEVICE_NOTIFY, + &gmux_notify_handler); +err_notify: + backlight_device_unregister(bdev); +err_release: + release_region(gmux_data->iostart, gmux_data->iolen); +err_free: + kfree(gmux_data); + return ret; +} + +static void gmux_remove(struct pnp_dev *pnp) +{ + struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); + + vga_switcheroo_unregister_handler(); + gmux_disable_interrupts(gmux_data); + if (gmux_data->gpe >= 0) { + acpi_disable_gpe(NULL, gmux_data->gpe); + acpi_remove_notify_handler(gmux_data->dhandle, + ACPI_DEVICE_NOTIFY, + &gmux_notify_handler); + } + + backlight_device_unregister(gmux_data->bdev); + + release_region(gmux_data->iostart, gmux_data->iolen); + apple_gmux_data = NULL; + kfree(gmux_data); + + acpi_video_dmi_demote_vendor(); + acpi_video_register(); + apple_bl_register(); +} + +static const struct pnp_device_id gmux_device_ids[] = { + {"APP000B", 0}, + {"", 0} +}; + +static const struct dev_pm_ops gmux_dev_pm_ops = { + .suspend = gmux_suspend, + .resume = gmux_resume, +}; + +static struct pnp_driver gmux_pnp_driver = { + .name = "apple-gmux", + .probe = gmux_probe, + .remove = gmux_remove, + .id_table = gmux_device_ids, + .driver = { + .pm = &gmux_dev_pm_ops, + }, +}; + +static int __init apple_gmux_init(void) +{ + return pnp_register_driver(&gmux_pnp_driver); +} + +static void __exit apple_gmux_exit(void) +{ + pnp_unregister_driver(&gmux_pnp_driver); +} + +module_init(apple_gmux_init); +module_exit(apple_gmux_exit); + +MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>"); +MODULE_DESCRIPTION("Apple Gmux Driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pnp, gmux_device_ids); diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 61a1c750365..7f4dc6f51f8 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -4,6 +4,7 @@ * * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor * Copyright (C) 2006-2007 Corentin Chary + * Copyright (C) 2011 Wind River Systems * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,7 +30,7 @@ * John Belmonte - ACPI code for Toshiba laptop was a good starting point. * Eric Burghard - LED display support for W1N * Josh Green - Light Sens support - * Thomas Tuttle - His first patch for led support was very helpfull + * Thomas Tuttle - His first patch for led support was very helpful * Sam Lin - GPS support */ @@ -45,293 +46,325 @@ #include <linux/fb.h> #include <linux/leds.h> #include <linux/platform_device.h> -#include <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> #include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/input-polldev.h> +#include <linux/rfkill.h> +#include <linux/slab.h> +#include <linux/dmi.h> +#include <linux/acpi.h> -#define ASUS_LAPTOP_VERSION "0.42" +#define ASUS_LAPTOP_VERSION "0.42" -#define ASUS_HOTK_NAME "Asus Laptop Support" -#define ASUS_HOTK_CLASS "hotkey" -#define ASUS_HOTK_DEVICE_NAME "Hotkey" -#define ASUS_HOTK_FILE KBUILD_MODNAME -#define ASUS_HOTK_PREFIX "\\_SB.ATKD." +#define ASUS_LAPTOP_NAME "Asus Laptop Support" +#define ASUS_LAPTOP_CLASS "hotkey" +#define ASUS_LAPTOP_DEVICE_NAME "Hotkey" +#define ASUS_LAPTOP_FILE KBUILD_MODNAME +#define ASUS_LAPTOP_PREFIX "\\_SB.ATKD." +MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary"); +MODULE_DESCRIPTION(ASUS_LAPTOP_NAME); +MODULE_LICENSE("GPL"); + +/* + * WAPF defines the behavior of the Fn+Fx wlan key + * The significance of values is yet to be found, but + * most of the time: + * Bit | Bluetooth | WLAN + * 0 | Hardware | Hardware + * 1 | Hardware | Software + * 4 | Software | Software + */ +static uint wapf = 1; +module_param(wapf, uint, 0444); +MODULE_PARM_DESC(wapf, "WAPF value"); + +static char *wled_type = "unknown"; +static char *bled_type = "unknown"; + +module_param(wled_type, charp, 0444); +MODULE_PARM_DESC(wled_type, "Set the wled type on boot " + "(unknown, led or rfkill). " + "default is unknown"); + +module_param(bled_type, charp, 0444); +MODULE_PARM_DESC(bled_type, "Set the bled type on boot " + "(unknown, led or rfkill). " + "default is unknown"); + +static int wlan_status = 1; +static int bluetooth_status = 1; +static int wimax_status = -1; +static int wwan_status = -1; +static int als_status; + +module_param(wlan_status, int, 0444); +MODULE_PARM_DESC(wlan_status, "Set the wireless status on boot " + "(0 = disabled, 1 = enabled, -1 = don't do anything). " + "default is -1"); + +module_param(bluetooth_status, int, 0444); +MODULE_PARM_DESC(bluetooth_status, "Set the wireless status on boot " + "(0 = disabled, 1 = enabled, -1 = don't do anything). " + "default is -1"); + +module_param(wimax_status, int, 0444); +MODULE_PARM_DESC(wimax_status, "Set the wireless status on boot " + "(0 = disabled, 1 = enabled, -1 = don't do anything). " + "default is -1"); + +module_param(wwan_status, int, 0444); +MODULE_PARM_DESC(wwan_status, "Set the wireless status on boot " + "(0 = disabled, 1 = enabled, -1 = don't do anything). " + "default is -1"); + +module_param(als_status, int, 0444); +MODULE_PARM_DESC(als_status, "Set the ALS status on boot " + "(0 = disabled, 1 = enabled). " + "default is 0"); /* * Some events we use, same for all Asus */ -#define ATKD_BR_UP 0x10 -#define ATKD_BR_DOWN 0x20 -#define ATKD_LCD_ON 0x33 -#define ATKD_LCD_OFF 0x34 +#define ATKD_BRNUP_MIN 0x10 +#define ATKD_BRNUP_MAX 0x1f +#define ATKD_BRNDOWN_MIN 0x20 +#define ATKD_BRNDOWN_MAX 0x2f +#define ATKD_BRNDOWN 0x20 +#define ATKD_BRNUP 0x2f +#define ATKD_LCD_ON 0x33 +#define ATKD_LCD_OFF 0x34 /* * Known bits returned by \_SB.ATKD.HWRS */ -#define WL_HWRS 0x80 -#define BT_HWRS 0x100 +#define WL_HWRS 0x80 +#define BT_HWRS 0x100 /* * Flags for hotk status * WL_ON and BT_ON are also used for wireless_status() */ -#define WL_ON 0x01 /* internal Wifi */ -#define BT_ON 0x02 /* internal Bluetooth */ -#define MLED_ON 0x04 /* mail LED */ -#define TLED_ON 0x08 /* touchpad LED */ -#define RLED_ON 0x10 /* Record LED */ -#define PLED_ON 0x20 /* Phone LED */ -#define GLED_ON 0x40 /* Gaming LED */ -#define LCD_ON 0x80 /* LCD backlight */ -#define GPS_ON 0x100 /* GPS */ -#define KEY_ON 0x200 /* Keyboard backlight */ - -#define ASUS_LOG ASUS_HOTK_FILE ": " -#define ASUS_ERR KERN_ERR ASUS_LOG -#define ASUS_WARNING KERN_WARNING ASUS_LOG -#define ASUS_NOTICE KERN_NOTICE ASUS_LOG -#define ASUS_INFO KERN_INFO ASUS_LOG -#define ASUS_DEBUG KERN_DEBUG ASUS_LOG +#define WL_RSTS 0x01 /* internal Wifi */ +#define BT_RSTS 0x02 /* internal Bluetooth */ +#define WM_RSTS 0x08 /* internal wimax */ +#define WW_RSTS 0x20 /* internal wwan */ -MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary"); -MODULE_DESCRIPTION(ASUS_HOTK_NAME); -MODULE_LICENSE("GPL"); - -/* - * WAPF defines the behavior of the Fn+Fx wlan key - * The significance of values is yet to be found, but - * most of the time: - * 0x0 will do nothing - * 0x1 will allow to control the device with Fn+Fx key. - * 0x4 will send an ACPI event (0x88) while pressing the Fn+Fx key - * 0x5 like 0x1 or 0x4 - * So, if something doesn't work as you want, just try other values =) - */ -static uint wapf = 1; -module_param(wapf, uint, 0644); -MODULE_PARM_DESC(wapf, "WAPF value"); - -#define ASUS_HANDLE(object, paths...) \ - static acpi_handle object##_handle = NULL; \ - static char *object##_paths[] = { paths } +/* WLED and BLED type */ +#define TYPE_UNKNOWN 0 +#define TYPE_LED 1 +#define TYPE_RFKILL 2 /* LED */ -ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX "MLED"); -ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED"); -ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED"); /* W1JC */ -ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED"); /* A7J */ -ASUS_HANDLE(gled_set, ASUS_HOTK_PREFIX "GLED"); /* G1, G2 (probably) */ +#define METHOD_MLED "MLED" +#define METHOD_TLED "TLED" +#define METHOD_RLED "RLED" /* W1JC */ +#define METHOD_PLED "PLED" /* A7J */ +#define METHOD_GLED "GLED" /* G1, G2 (probably) */ /* LEDD */ -ASUS_HANDLE(ledd_set, ASUS_HOTK_PREFIX "SLCM"); +#define METHOD_LEDD "SLCM" /* * Bluetooth and WLAN * WLED and BLED are not handled like other XLED, because in some dsdt * they also control the WLAN/Bluetooth device. */ -ASUS_HANDLE(wl_switch, ASUS_HOTK_PREFIX "WLED"); -ASUS_HANDLE(bt_switch, ASUS_HOTK_PREFIX "BLED"); -ASUS_HANDLE(wireless_status, ASUS_HOTK_PREFIX "RSTS"); /* All new models */ +#define METHOD_WLAN "WLED" +#define METHOD_BLUETOOTH "BLED" + +/* WWAN and WIMAX */ +#define METHOD_WWAN "GSMC" +#define METHOD_WIMAX "WMXC" + +#define METHOD_WL_STATUS "RSTS" /* Brightness */ -ASUS_HANDLE(brightness_set, ASUS_HOTK_PREFIX "SPLV"); -ASUS_HANDLE(brightness_get, ASUS_HOTK_PREFIX "GPLV"); - -/* Backlight */ -ASUS_HANDLE(lcd_switch, "\\_SB.PCI0.SBRG.EC0._Q10", /* All new models */ - "\\_SB.PCI0.ISA.EC0._Q10", /* A1x */ - "\\_SB.PCI0.PX40.ECD0._Q10", /* L3C */ - "\\_SB.PCI0.PX40.EC0.Q10", /* M1A */ - "\\_SB.PCI0.LPCB.EC0._Q10", /* P30 */ - "\\_SB.PCI0.LPCB.EC0._Q0E", /* P30/P35 */ - "\\_SB.PCI0.PX40.Q10", /* S1x */ - "\\Q10"); /* A2x, L2D, L3D, M2E */ +#define METHOD_BRIGHTNESS_SET "SPLV" +#define METHOD_BRIGHTNESS_GET "GPLV" /* Display */ -ASUS_HANDLE(display_set, ASUS_HOTK_PREFIX "SDSP"); -ASUS_HANDLE(display_get, - /* A6B, A6K A6R A7D F3JM L4R M6R A3G M6A M6V VX-1 V6J V6V W3Z */ - "\\_SB.PCI0.P0P1.VGA.GETD", - /* A3E A4K, A4D A4L A6J A7J A8J Z71V M9V S5A M5A z33A W1Jc W2V G1 */ - "\\_SB.PCI0.P0P2.VGA.GETD", - /* A6V A6Q */ - "\\_SB.PCI0.P0P3.VGA.GETD", - /* A6T, A6M */ - "\\_SB.PCI0.P0PA.VGA.GETD", - /* L3C */ - "\\_SB.PCI0.PCI1.VGAC.NMAP", - /* Z96F */ - "\\_SB.PCI0.VGA.GETD", - /* A2D */ - "\\ACTD", - /* A4G Z71A W1N W5A W5F M2N M3N M5N M6N S1N S5N */ - "\\ADVG", - /* P30 */ - "\\DNXT", - /* A2H D1 L2D L3D L3H L2E L5D L5C M1A M2E L4L W3V */ - "\\INFB", - /* A3F A6F A3N A3L M6N W3N W6A */ - "\\SSTE"); - -ASUS_HANDLE(ls_switch, ASUS_HOTK_PREFIX "ALSC"); /* Z71A Z71V */ -ASUS_HANDLE(ls_level, ASUS_HOTK_PREFIX "ALSL"); /* Z71A Z71V */ +#define METHOD_SWITCH_DISPLAY "SDSP" + +#define METHOD_ALS_CONTROL "ALSC" /* Z71A Z71V */ +#define METHOD_ALS_LEVEL "ALSL" /* Z71A Z71V */ /* GPS */ /* R2H use different handle for GPS on/off */ -ASUS_HANDLE(gps_on, ASUS_HOTK_PREFIX "SDON"); /* R2H */ -ASUS_HANDLE(gps_off, ASUS_HOTK_PREFIX "SDOF"); /* R2H */ -ASUS_HANDLE(gps_status, ASUS_HOTK_PREFIX "GPST"); +#define METHOD_GPS_ON "SDON" +#define METHOD_GPS_OFF "SDOF" +#define METHOD_GPS_STATUS "GPST" /* Keyboard light */ -ASUS_HANDLE(kled_set, ASUS_HOTK_PREFIX "SLKB"); -ASUS_HANDLE(kled_get, ASUS_HOTK_PREFIX "GLKB"); +#define METHOD_KBD_LIGHT_SET "SLKB" +#define METHOD_KBD_LIGHT_GET "GLKB" + +/* For Pegatron Lucid tablet */ +#define DEVICE_NAME_PEGA "Lucid" + +#define METHOD_PEGA_ENABLE "ENPR" +#define METHOD_PEGA_DISABLE "DAPR" +#define PEGA_WLAN 0x00 +#define PEGA_BLUETOOTH 0x01 +#define PEGA_WWAN 0x02 +#define PEGA_ALS 0x04 +#define PEGA_ALS_POWER 0x05 + +#define METHOD_PEGA_READ "RDLN" +#define PEGA_READ_ALS_H 0x02 +#define PEGA_READ_ALS_L 0x03 + +#define PEGA_ACCEL_NAME "pega_accel" +#define PEGA_ACCEL_DESC "Pegatron Lucid Tablet Accelerometer" +#define METHOD_XLRX "XLRX" +#define METHOD_XLRY "XLRY" +#define METHOD_XLRZ "XLRZ" +#define PEGA_ACC_CLAMP 512 /* 1G accel is reported as ~256, so clamp to 2G */ +#define PEGA_ACC_RETRIES 3 /* - * This is the main structure, we can use it to store anything interesting - * about the hotk device + * Define a specific led structure to keep the main structure clean */ -struct asus_hotk { - char *name; /* laptop name */ - struct acpi_device *device; /* the device we are in */ - acpi_handle handle; /* the handle of the hotk device */ - char status; /* status of the hotk, for LEDs, ... */ - u32 ledd_status; /* status of the LED display */ - u8 light_level; /* light sensor level */ - u8 light_switch; /* light sensor switch value */ - u16 event_count[128]; /* count for each event TODO make this better */ - struct input_dev *inputdev; - u16 *keycode_map; +struct asus_led { + int wk; + struct work_struct work; + struct led_classdev led; + struct asus_laptop *asus; + const char *method; }; /* - * This header is made available to allow proper configuration given model, - * revision number , ... this info cannot go in struct asus_hotk because it is - * available before the hotk - */ -static struct acpi_table_header *asus_info; - -/* The actual device the driver binds to */ -static struct asus_hotk *hotk; - -/* - * The hotkey driver declaration + * Same thing for rfkill */ -static const struct acpi_device_id asus_device_ids[] = { - {"ATK0100", 0}, - {"ATK0101", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, asus_device_ids); - -static int asus_hotk_add(struct acpi_device *device); -static int asus_hotk_remove(struct acpi_device *device, int type); -static void asus_hotk_notify(struct acpi_device *device, u32 event); - -static struct acpi_driver asus_hotk_driver = { - .name = ASUS_HOTK_NAME, - .class = ASUS_HOTK_CLASS, - .owner = THIS_MODULE, - .ids = asus_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = asus_hotk_add, - .remove = asus_hotk_remove, - .notify = asus_hotk_notify, - }, +struct asus_rfkill { + /* type of control. Maps to PEGA_* values or *_RSTS */ + int control_id; + struct rfkill *rfkill; + struct asus_laptop *asus; }; -/* The backlight device /sys/class/backlight */ -static struct backlight_device *asus_backlight_device; - /* - * The backlight class declaration + * This is the main structure, we can use it to store anything interesting + * about the hotk device */ -static int read_brightness(struct backlight_device *bd); -static int update_bl_status(struct backlight_device *bd); -static struct backlight_ops asusbl_ops = { - .get_brightness = read_brightness, - .update_status = update_bl_status, -}; +struct asus_laptop { + char *name; /* laptop name */ -/* - * These functions actually update the LED's, and are called from a - * workqueue. By doing this as separate work rather than when the LED - * subsystem asks, we avoid messing with the Asus ACPI stuff during a - * potentially bad time, such as a timer interrupt. - */ -static struct workqueue_struct *led_workqueue; - -#define ASUS_LED(object, ledname, max) \ - static void object##_led_set(struct led_classdev *led_cdev, \ - enum led_brightness value); \ - static enum led_brightness object##_led_get( \ - struct led_classdev *led_cdev); \ - static void object##_led_update(struct work_struct *ignored); \ - static int object##_led_wk; \ - static DECLARE_WORK(object##_led_work, object##_led_update); \ - static struct led_classdev object##_led = { \ - .name = "asus::" ledname, \ - .brightness_set = object##_led_set, \ - .brightness_get = object##_led_get, \ - .max_brightness = max \ - } + struct acpi_table_header *dsdt_info; + struct platform_device *platform_device; + struct acpi_device *device; /* the device we are in */ + struct backlight_device *backlight_device; + + struct input_dev *inputdev; + struct key_entry *keymap; + struct input_polled_dev *pega_accel_poll; + + struct asus_led wled; + struct asus_led bled; + struct asus_led mled; + struct asus_led tled; + struct asus_led rled; + struct asus_led pled; + struct asus_led gled; + struct asus_led kled; + struct workqueue_struct *led_workqueue; + + int wled_type; + int bled_type; + int wireless_status; + bool have_rsts; + bool is_pega_lucid; + bool pega_acc_live; + int pega_acc_x; + int pega_acc_y; + int pega_acc_z; + + struct asus_rfkill wlan; + struct asus_rfkill bluetooth; + struct asus_rfkill wwan; + struct asus_rfkill wimax; + struct asus_rfkill gps; -ASUS_LED(mled, "mail", 1); -ASUS_LED(tled, "touchpad", 1); -ASUS_LED(rled, "record", 1); -ASUS_LED(pled, "phone", 1); -ASUS_LED(gled, "gaming", 1); -ASUS_LED(kled, "kbd_backlight", 3); - -struct key_entry { - char type; - u8 code; - u16 keycode; + acpi_handle handle; /* the handle of the hotk device */ + u32 ledd_status; /* status of the LED display */ + u8 light_level; /* light sensor level */ + u8 light_switch; /* light sensor switch value */ + u16 event_count[128]; /* count for each event TODO make this better */ }; -enum { KE_KEY, KE_END }; - -static struct key_entry asus_keymap[] = { - {KE_KEY, 0x02, KEY_SCREENLOCK}, - {KE_KEY, 0x05, KEY_WLAN}, - {KE_KEY, 0x08, KEY_F13}, - {KE_KEY, 0x17, KEY_ZOOM}, - {KE_KEY, 0x1f, KEY_BATTERY}, - {KE_KEY, 0x30, KEY_VOLUMEUP}, - {KE_KEY, 0x31, KEY_VOLUMEDOWN}, - {KE_KEY, 0x32, KEY_MUTE}, - {KE_KEY, 0x33, KEY_SWITCHVIDEOMODE}, - {KE_KEY, 0x34, KEY_SWITCHVIDEOMODE}, - {KE_KEY, 0x40, KEY_PREVIOUSSONG}, - {KE_KEY, 0x41, KEY_NEXTSONG}, - {KE_KEY, 0x43, KEY_STOPCD}, - {KE_KEY, 0x45, KEY_PLAYPAUSE}, - {KE_KEY, 0x4c, KEY_MEDIA}, - {KE_KEY, 0x50, KEY_EMAIL}, - {KE_KEY, 0x51, KEY_WWW}, - {KE_KEY, 0x55, KEY_CALC}, - {KE_KEY, 0x5C, KEY_SCREENLOCK}, /* Screenlock */ - {KE_KEY, 0x5D, KEY_WLAN}, - {KE_KEY, 0x5E, KEY_WLAN}, - {KE_KEY, 0x5F, KEY_WLAN}, - {KE_KEY, 0x60, KEY_SWITCHVIDEOMODE}, - {KE_KEY, 0x61, KEY_SWITCHVIDEOMODE}, - {KE_KEY, 0x62, KEY_SWITCHVIDEOMODE}, - {KE_KEY, 0x63, KEY_SWITCHVIDEOMODE}, - {KE_KEY, 0x6B, KEY_F13}, /* Lock Touchpad */ - {KE_KEY, 0x82, KEY_CAMERA}, - {KE_KEY, 0x88, KEY_WLAN }, - {KE_KEY, 0x8A, KEY_PROG1}, - {KE_KEY, 0x95, KEY_MEDIA}, - {KE_KEY, 0x99, KEY_PHONE}, - {KE_KEY, 0xc4, KEY_KBDILLUMUP}, - {KE_KEY, 0xc5, KEY_KBDILLUMDOWN}, +static const struct key_entry asus_keymap[] = { + /* Lenovo SL Specific keycodes */ + {KE_KEY, 0x02, { KEY_SCREENLOCK } }, + {KE_KEY, 0x05, { KEY_WLAN } }, + {KE_KEY, 0x08, { KEY_F13 } }, + {KE_KEY, 0x09, { KEY_PROG2 } }, /* Dock */ + {KE_KEY, 0x17, { KEY_ZOOM } }, + {KE_KEY, 0x1f, { KEY_BATTERY } }, + /* End of Lenovo SL Specific keycodes */ + {KE_KEY, ATKD_BRNDOWN, { KEY_BRIGHTNESSDOWN } }, + {KE_KEY, ATKD_BRNUP, { KEY_BRIGHTNESSUP } }, + {KE_KEY, 0x30, { KEY_VOLUMEUP } }, + {KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, + {KE_KEY, 0x32, { KEY_MUTE } }, + {KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */ + {KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */ + {KE_KEY, 0x40, { KEY_PREVIOUSSONG } }, + {KE_KEY, 0x41, { KEY_NEXTSONG } }, + {KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */ + {KE_KEY, 0x45, { KEY_PLAYPAUSE } }, + {KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */ + {KE_KEY, 0x50, { KEY_EMAIL } }, + {KE_KEY, 0x51, { KEY_WWW } }, + {KE_KEY, 0x55, { KEY_CALC } }, + {KE_IGNORE, 0x57, }, /* Battery mode */ + {KE_IGNORE, 0x58, }, /* AC mode */ + {KE_KEY, 0x5C, { KEY_SCREENLOCK } }, /* Screenlock */ + {KE_KEY, 0x5D, { KEY_WLAN } }, /* WLAN Toggle */ + {KE_KEY, 0x5E, { KEY_WLAN } }, /* WLAN Enable */ + {KE_KEY, 0x5F, { KEY_WLAN } }, /* WLAN Disable */ + {KE_KEY, 0x60, { KEY_TOUCHPAD_ON } }, + {KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */ + {KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */ + {KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */ + {KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */ + {KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */ + {KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */ + {KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */ + {KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, /* Lock Touchpad */ + {KE_KEY, 0x6C, { KEY_SLEEP } }, /* Suspend */ + {KE_KEY, 0x6D, { KEY_SLEEP } }, /* Hibernate */ + {KE_IGNORE, 0x6E, }, /* Low Battery notification */ + {KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */ + {KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */ + {KE_KEY, 0x82, { KEY_CAMERA } }, + {KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ + {KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ + {KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ + {KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ + {KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ + {KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */ + {KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */ + {KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */ + {KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */ + {KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */ + {KE_KEY, 0x95, { KEY_MEDIA } }, + {KE_KEY, 0x99, { KEY_PHONE } }, + {KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */ + {KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */ + {KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */ + {KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */ + {KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */ + {KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */ + {KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */ + {KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */ + {KE_KEY, 0xB5, { KEY_CALC } }, + {KE_KEY, 0xC4, { KEY_KBDILLUMUP } }, + {KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } }, {KE_END, 0}, }; + /* * This function evaluates an ACPI method, given an int as parameter, the * method is searched within the scope of the handle, can be NULL. The output @@ -339,15 +372,15 @@ static struct key_entry asus_keymap[] = { * * returns 0 if write is successful, -1 else. */ -static int write_acpi_int(acpi_handle handle, const char *method, int val, - struct acpi_buffer *output) +static int write_acpi_int_ret(acpi_handle handle, const char *method, int val, + struct acpi_buffer *output) { struct acpi_object_list params; /* list of input parameters (an int) */ union acpi_object in_obj; /* the only param we use */ acpi_status status; if (!handle) - return 0; + return -1; params.count = 1; params.pointer = &in_obj; @@ -361,102 +394,203 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val, return -1; } -static int read_wireless_status(int mask) +static int write_acpi_int(acpi_handle handle, const char *method, int val) { - unsigned long long status; - acpi_status rv = AE_OK; + return write_acpi_int_ret(handle, method, val, NULL); +} - if (!wireless_status_handle) - return (hotk->status & mask) ? 1 : 0; +static int acpi_check_handle(acpi_handle handle, const char *method, + acpi_handle *ret) +{ + acpi_status status; - rv = acpi_evaluate_integer(wireless_status_handle, NULL, NULL, &status); - if (ACPI_FAILURE(rv)) - pr_warning("Error reading Wireless status\n"); - else - return (status & mask) ? 1 : 0; + if (method == NULL) + return -ENODEV; + + if (ret) + status = acpi_get_handle(handle, (char *)method, + ret); + else { + acpi_handle dummy; - return (hotk->status & mask) ? 1 : 0; + status = acpi_get_handle(handle, (char *)method, + &dummy); + } + + if (status != AE_OK) { + if (ret) + pr_warn("Error finding %s\n", method); + return -ENODEV; + } + return 0; } -static int read_gps_status(void) +static bool asus_check_pega_lucid(struct asus_laptop *asus) { - unsigned long long status; - acpi_status rv = AE_OK; + return !strcmp(asus->name, DEVICE_NAME_PEGA) && + !acpi_check_handle(asus->handle, METHOD_PEGA_ENABLE, NULL) && + !acpi_check_handle(asus->handle, METHOD_PEGA_DISABLE, NULL) && + !acpi_check_handle(asus->handle, METHOD_PEGA_READ, NULL); +} - rv = acpi_evaluate_integer(gps_status_handle, NULL, NULL, &status); - if (ACPI_FAILURE(rv)) - pr_warning("Error reading GPS status\n"); - else - return status ? 1 : 0; +static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable) +{ + char *method = enable ? METHOD_PEGA_ENABLE : METHOD_PEGA_DISABLE; + return write_acpi_int(asus->handle, method, unit); +} - return (hotk->status & GPS_ON) ? 1 : 0; +static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method) +{ + int i, delta; + unsigned long long val; + for (i = 0; i < PEGA_ACC_RETRIES; i++) { + acpi_evaluate_integer(asus->handle, method, NULL, &val); + + /* The output is noisy. From reading the ASL + * dissassembly, timeout errors are returned with 1's + * in the high word, and the lack of locking around + * thei hi/lo byte reads means that a transition + * between (for example) -1 and 0 could be read as + * 0xff00 or 0x00ff. */ + delta = abs(curr - (short)val); + if (delta < 128 && !(val & ~0xffff)) + break; + } + return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP); } -/* Generic LED functions */ -static int read_status(int mask) +static void pega_accel_poll(struct input_polled_dev *ipd) { - /* There is a special method for both wireless devices */ - if (mask == BT_ON || mask == WL_ON) - return read_wireless_status(mask); - else if (mask == GPS_ON) - return read_gps_status(); + struct device *parent = ipd->input->dev.parent; + struct asus_laptop *asus = dev_get_drvdata(parent); + + /* In some cases, the very first call to poll causes a + * recursive fault under the polldev worker. This is + * apparently related to very early userspace access to the + * device, and perhaps a firmware bug. Fake the first report. */ + if (!asus->pega_acc_live) { + asus->pega_acc_live = true; + input_report_abs(ipd->input, ABS_X, 0); + input_report_abs(ipd->input, ABS_Y, 0); + input_report_abs(ipd->input, ABS_Z, 0); + input_sync(ipd->input); + return; + } - return (hotk->status & mask) ? 1 : 0; + asus->pega_acc_x = pega_acc_axis(asus, asus->pega_acc_x, METHOD_XLRX); + asus->pega_acc_y = pega_acc_axis(asus, asus->pega_acc_y, METHOD_XLRY); + asus->pega_acc_z = pega_acc_axis(asus, asus->pega_acc_z, METHOD_XLRZ); + + /* Note transform, convert to "right/up/out" in the native + * landscape orientation (i.e. the vector is the direction of + * "real up" in the device's cartiesian coordinates). */ + input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x); + input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y); + input_report_abs(ipd->input, ABS_Z, asus->pega_acc_z); + input_sync(ipd->input); } -static void write_status(acpi_handle handle, int out, int mask) +static void pega_accel_exit(struct asus_laptop *asus) { - hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask); - - switch (mask) { - case MLED_ON: - out = !(out & 0x1); - break; - case GLED_ON: - out = (out & 0x1) + 1; - break; - case GPS_ON: - handle = (out) ? gps_on_handle : gps_off_handle; - out = 0x02; - break; - default: - out &= 0x1; - break; + if (asus->pega_accel_poll) { + input_unregister_polled_device(asus->pega_accel_poll); + input_free_polled_device(asus->pega_accel_poll); } + asus->pega_accel_poll = NULL; +} + +static int pega_accel_init(struct asus_laptop *asus) +{ + int err; + struct input_polled_dev *ipd; + + if (!asus->is_pega_lucid) + return -ENODEV; + + if (acpi_check_handle(asus->handle, METHOD_XLRX, NULL) || + acpi_check_handle(asus->handle, METHOD_XLRY, NULL) || + acpi_check_handle(asus->handle, METHOD_XLRZ, NULL)) + return -ENODEV; - if (write_acpi_int(handle, NULL, out, NULL)) - pr_warning(" write failed %x\n", mask); + ipd = input_allocate_polled_device(); + if (!ipd) + return -ENOMEM; + + ipd->poll = pega_accel_poll; + ipd->poll_interval = 125; + ipd->poll_interval_min = 50; + ipd->poll_interval_max = 2000; + + ipd->input->name = PEGA_ACCEL_DESC; + ipd->input->phys = PEGA_ACCEL_NAME "/input0"; + ipd->input->dev.parent = &asus->platform_device->dev; + ipd->input->id.bustype = BUS_HOST; + + set_bit(EV_ABS, ipd->input->evbit); + input_set_abs_params(ipd->input, ABS_X, + -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); + input_set_abs_params(ipd->input, ABS_Y, + -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); + input_set_abs_params(ipd->input, ABS_Z, + -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); + + err = input_register_polled_device(ipd); + if (err) + goto exit; + + asus->pega_accel_poll = ipd; + return 0; + +exit: + input_free_polled_device(ipd); + return err; } +/* Generic LED function */ +static int asus_led_set(struct asus_laptop *asus, const char *method, + int value) +{ + if (!strcmp(method, METHOD_MLED)) + value = !value; + else if (!strcmp(method, METHOD_GLED)) + value = !value + 1; + else + value = !!value; + + return write_acpi_int(asus->handle, method, value); +} + +/* + * LEDs + */ /* /sys/class/led handlers */ -#define ASUS_LED_HANDLER(object, mask) \ - static void object##_led_set(struct led_classdev *led_cdev, \ - enum led_brightness value) \ - { \ - object##_led_wk = (value > 0) ? 1 : 0; \ - queue_work(led_workqueue, &object##_led_work); \ - } \ - static void object##_led_update(struct work_struct *ignored) \ - { \ - int value = object##_led_wk; \ - write_status(object##_set_handle, value, (mask)); \ - } \ - static enum led_brightness object##_led_get( \ - struct led_classdev *led_cdev) \ - { \ - return led_cdev->brightness; \ - } +static void asus_led_cdev_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct asus_led *led = container_of(led_cdev, struct asus_led, led); + struct asus_laptop *asus = led->asus; + + led->wk = !!value; + queue_work(asus->led_workqueue, &led->work); +} + +static void asus_led_cdev_update(struct work_struct *work) +{ + struct asus_led *led = container_of(work, struct asus_led, work); + struct asus_laptop *asus = led->asus; -ASUS_LED_HANDLER(mled, MLED_ON); -ASUS_LED_HANDLER(pled, PLED_ON); -ASUS_LED_HANDLER(rled, RLED_ON); -ASUS_LED_HANDLER(tled, TLED_ON); -ASUS_LED_HANDLER(gled, GLED_ON); + asus_led_set(asus, led->method, led->wk); +} + +static enum led_brightness asus_led_cdev_get(struct led_classdev *led_cdev) +{ + return led_cdev->brightness; +} /* - * Keyboard backlight + * Keyboard backlight (also a LED) */ -static int get_kled_lvl(void) +static int asus_kled_lvl(struct asus_laptop *asus) { unsigned long long kblv; struct acpi_object_list params; @@ -468,120 +602,250 @@ static int get_kled_lvl(void) in_obj.type = ACPI_TYPE_INTEGER; in_obj.integer.value = 2; - rv = acpi_evaluate_integer(kled_get_handle, NULL, ¶ms, &kblv); + rv = acpi_evaluate_integer(asus->handle, METHOD_KBD_LIGHT_GET, + ¶ms, &kblv); if (ACPI_FAILURE(rv)) { - pr_warning("Error reading kled level\n"); - return 0; + pr_warn("Error reading kled level\n"); + return -ENODEV; } return kblv; } -static int set_kled_lvl(int kblv) +static int asus_kled_set(struct asus_laptop *asus, int kblv) { if (kblv > 0) kblv = (1 << 7) | (kblv & 0x7F); else kblv = 0; - if (write_acpi_int(kled_set_handle, NULL, kblv, NULL)) { - pr_warning("Keyboard LED display write failed\n"); + if (write_acpi_int(asus->handle, METHOD_KBD_LIGHT_SET, kblv)) { + pr_warn("Keyboard LED display write failed\n"); return -EINVAL; } return 0; } -static void kled_led_set(struct led_classdev *led_cdev, - enum led_brightness value) +static void asus_kled_cdev_set(struct led_classdev *led_cdev, + enum led_brightness value) { - kled_led_wk = value; - queue_work(led_workqueue, &kled_led_work); + struct asus_led *led = container_of(led_cdev, struct asus_led, led); + struct asus_laptop *asus = led->asus; + + led->wk = value; + queue_work(asus->led_workqueue, &led->work); } -static void kled_led_update(struct work_struct *ignored) +static void asus_kled_cdev_update(struct work_struct *work) { - set_kled_lvl(kled_led_wk); + struct asus_led *led = container_of(work, struct asus_led, work); + struct asus_laptop *asus = led->asus; + + asus_kled_set(asus, led->wk); } -static enum led_brightness kled_led_get(struct led_classdev *led_cdev) +static enum led_brightness asus_kled_cdev_get(struct led_classdev *led_cdev) { - return get_kled_lvl(); + struct asus_led *led = container_of(led_cdev, struct asus_led, led); + struct asus_laptop *asus = led->asus; + + return asus_kled_lvl(asus); } -static int get_lcd_state(void) +static void asus_led_exit(struct asus_laptop *asus) { - return read_status(LCD_ON); + if (!IS_ERR_OR_NULL(asus->wled.led.dev)) + led_classdev_unregister(&asus->wled.led); + if (!IS_ERR_OR_NULL(asus->bled.led.dev)) + led_classdev_unregister(&asus->bled.led); + if (!IS_ERR_OR_NULL(asus->mled.led.dev)) + led_classdev_unregister(&asus->mled.led); + if (!IS_ERR_OR_NULL(asus->tled.led.dev)) + led_classdev_unregister(&asus->tled.led); + if (!IS_ERR_OR_NULL(asus->pled.led.dev)) + led_classdev_unregister(&asus->pled.led); + if (!IS_ERR_OR_NULL(asus->rled.led.dev)) + led_classdev_unregister(&asus->rled.led); + if (!IS_ERR_OR_NULL(asus->gled.led.dev)) + led_classdev_unregister(&asus->gled.led); + if (!IS_ERR_OR_NULL(asus->kled.led.dev)) + led_classdev_unregister(&asus->kled.led); + if (asus->led_workqueue) { + destroy_workqueue(asus->led_workqueue); + asus->led_workqueue = NULL; + } } -static int set_lcd_state(int value) +/* Ugly macro, need to fix that later */ +static int asus_led_register(struct asus_laptop *asus, + struct asus_led *led, + const char *name, const char *method) { - int lcd = 0; - acpi_status status = 0; - - lcd = value ? 1 : 0; - - if (lcd == get_lcd_state()) - return 0; + struct led_classdev *led_cdev = &led->led; - if (lcd_switch_handle) { - status = acpi_evaluate_object(lcd_switch_handle, - NULL, NULL, NULL); + if (!method || acpi_check_handle(asus->handle, method, NULL)) + return 0; /* Led not present */ - if (ACPI_FAILURE(status)) - pr_warning("Error switching LCD\n"); - } + led->asus = asus; + led->method = method; - write_status(NULL, lcd, LCD_ON); - return 0; + INIT_WORK(&led->work, asus_led_cdev_update); + led_cdev->name = name; + led_cdev->brightness_set = asus_led_cdev_set; + led_cdev->brightness_get = asus_led_cdev_get; + led_cdev->max_brightness = 1; + return led_classdev_register(&asus->platform_device->dev, led_cdev); } -static void lcd_blank(int blank) +static int asus_led_init(struct asus_laptop *asus) { - struct backlight_device *bd = asus_backlight_device; + int r = 0; + + /* + * The Pegatron Lucid has no physical leds, but all methods are + * available in the DSDT... + */ + if (asus->is_pega_lucid) + return 0; - if (bd) { - bd->props.power = blank; - backlight_update_status(bd); + /* + * Functions that actually update the LED's are called from a + * workqueue. By doing this as separate work rather than when the LED + * subsystem asks, we avoid messing with the Asus ACPI stuff during a + * potentially bad time, such as a timer interrupt. + */ + asus->led_workqueue = create_singlethread_workqueue("led_workqueue"); + if (!asus->led_workqueue) + return -ENOMEM; + + if (asus->wled_type == TYPE_LED) + r = asus_led_register(asus, &asus->wled, "asus::wlan", + METHOD_WLAN); + if (r) + goto error; + if (asus->bled_type == TYPE_LED) + r = asus_led_register(asus, &asus->bled, "asus::bluetooth", + METHOD_BLUETOOTH); + if (r) + goto error; + r = asus_led_register(asus, &asus->mled, "asus::mail", METHOD_MLED); + if (r) + goto error; + r = asus_led_register(asus, &asus->tled, "asus::touchpad", METHOD_TLED); + if (r) + goto error; + r = asus_led_register(asus, &asus->rled, "asus::record", METHOD_RLED); + if (r) + goto error; + r = asus_led_register(asus, &asus->pled, "asus::phone", METHOD_PLED); + if (r) + goto error; + r = asus_led_register(asus, &asus->gled, "asus::gaming", METHOD_GLED); + if (r) + goto error; + if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL) && + !acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_GET, NULL)) { + struct asus_led *led = &asus->kled; + struct led_classdev *cdev = &led->led; + + led->asus = asus; + + INIT_WORK(&led->work, asus_kled_cdev_update); + cdev->name = "asus::kbd_backlight"; + cdev->brightness_set = asus_kled_cdev_set; + cdev->brightness_get = asus_kled_cdev_get; + cdev->max_brightness = 3; + r = led_classdev_register(&asus->platform_device->dev, cdev); } +error: + if (r) + asus_led_exit(asus); + return r; } -static int read_brightness(struct backlight_device *bd) +/* + * Backlight device + */ +static int asus_read_brightness(struct backlight_device *bd) { + struct asus_laptop *asus = bl_get_data(bd); unsigned long long value; acpi_status rv = AE_OK; - rv = acpi_evaluate_integer(brightness_get_handle, NULL, NULL, &value); + rv = acpi_evaluate_integer(asus->handle, METHOD_BRIGHTNESS_GET, + NULL, &value); if (ACPI_FAILURE(rv)) - pr_warning("Error reading brightness\n"); + pr_warn("Error reading brightness\n"); return value; } -static int set_brightness(struct backlight_device *bd, int value) +static int asus_set_brightness(struct backlight_device *bd, int value) { - int ret = 0; - - value = (0 < value) ? ((15 < value) ? 15 : value) : 0; - /* 0 <= value <= 15 */ + struct asus_laptop *asus = bl_get_data(bd); - if (write_acpi_int(brightness_set_handle, NULL, value, NULL)) { - pr_warning("Error changing brightness\n"); - ret = -EIO; + if (write_acpi_int(asus->handle, METHOD_BRIGHTNESS_SET, value)) { + pr_warn("Error changing brightness\n"); + return -EIO; } - - return ret; + return 0; } static int update_bl_status(struct backlight_device *bd) { - int rv; int value = bd->props.brightness; - rv = set_brightness(bd, value); - if (rv) - return rv; + return asus_set_brightness(bd, value); +} + +static const struct backlight_ops asusbl_ops = { + .get_brightness = asus_read_brightness, + .update_status = update_bl_status, +}; - value = (bd->props.power == FB_BLANK_UNBLANK) ? 1 : 0; - return set_lcd_state(value); +static int asus_backlight_notify(struct asus_laptop *asus) +{ + struct backlight_device *bd = asus->backlight_device; + int old = bd->props.brightness; + + backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY); + + return old; +} + +static int asus_backlight_init(struct asus_laptop *asus) +{ + struct backlight_device *bd; + struct backlight_properties props; + + if (acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_GET, NULL) || + acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_SET, NULL)) + return 0; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.max_brightness = 15; + props.type = BACKLIGHT_PLATFORM; + + bd = backlight_device_register(ASUS_LAPTOP_FILE, + &asus->platform_device->dev, asus, + &asusbl_ops, &props); + if (IS_ERR(bd)) { + pr_err("Could not register asus backlight device\n"); + asus->backlight_device = NULL; + return PTR_ERR(bd); + } + + asus->backlight_device = bd; + bd->props.brightness = asus_read_brightness(bd); + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + return 0; +} + +static void asus_backlight_exit(struct asus_laptop *asus) +{ + if (asus->backlight_device) + backlight_device_unregister(asus->backlight_device); + asus->backlight_device = NULL; } /* @@ -596,37 +860,40 @@ static int update_bl_status(struct backlight_device *bd) static ssize_t show_infos(struct device *dev, struct device_attribute *attr, char *page) { + struct asus_laptop *asus = dev_get_drvdata(dev); int len = 0; unsigned long long temp; char buf[16]; /* enough for all info */ acpi_status rv = AE_OK; /* - * We use the easy way, we don't care of off and count, so we don't set eof - * to 1 + * We use the easy way, we don't care of off and count, + * so we don't set eof to 1 */ - len += sprintf(page, ASUS_HOTK_NAME " " ASUS_LAPTOP_VERSION "\n"); - len += sprintf(page + len, "Model reference : %s\n", hotk->name); + len += sprintf(page, ASUS_LAPTOP_NAME " " ASUS_LAPTOP_VERSION "\n"); + len += sprintf(page + len, "Model reference : %s\n", asus->name); /* * The SFUN method probably allows the original driver to get the list * of features supported by a given model. For now, 0x0100 or 0x0800 * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. * The significance of others is yet to be found. */ - rv = acpi_evaluate_integer(hotk->handle, "SFUN", NULL, &temp); + rv = acpi_evaluate_integer(asus->handle, "SFUN", NULL, &temp); if (!ACPI_FAILURE(rv)) len += sprintf(page + len, "SFUN value : %#x\n", (uint) temp); /* * The HWRS method return informations about the hardware. * 0x80 bit is for WLAN, 0x100 for Bluetooth. + * 0x40 for WWAN, 0x10 for WIMAX. * The significance of others is yet to be found. - * If we don't find the method, we assume the device are present. + * We don't currently use this for device detection, and it + * takes several seconds to run on some systems. */ - rv = acpi_evaluate_integer(hotk->handle, "HRWS", NULL, &temp); + rv = acpi_evaluate_integer(asus->handle, "HWRS", NULL, &temp); if (!ACPI_FAILURE(rv)) - len += sprintf(page + len, "HRWS value : %#x\n", + len += sprintf(page + len, "HWRS value : %#x\n", (uint) temp); /* * Another value for userspace: the ASYM method returns 0x02 for @@ -635,26 +902,26 @@ static ssize_t show_infos(struct device *dev, * Note: since not all the laptops provide this method, errors are * silently ignored. */ - rv = acpi_evaluate_integer(hotk->handle, "ASYM", NULL, &temp); + rv = acpi_evaluate_integer(asus->handle, "ASYM", NULL, &temp); if (!ACPI_FAILURE(rv)) len += sprintf(page + len, "ASYM value : %#x\n", (uint) temp); - if (asus_info) { - snprintf(buf, 16, "%d", asus_info->length); + if (asus->dsdt_info) { + snprintf(buf, 16, "%d", asus->dsdt_info->length); len += sprintf(page + len, "DSDT length : %s\n", buf); - snprintf(buf, 16, "%d", asus_info->checksum); + snprintf(buf, 16, "%d", asus->dsdt_info->checksum); len += sprintf(page + len, "DSDT checksum : %s\n", buf); - snprintf(buf, 16, "%d", asus_info->revision); + snprintf(buf, 16, "%d", asus->dsdt_info->revision); len += sprintf(page + len, "DSDT revision : %s\n", buf); - snprintf(buf, 7, "%s", asus_info->oem_id); + snprintf(buf, 7, "%s", asus->dsdt_info->oem_id); len += sprintf(page + len, "OEM id : %s\n", buf); - snprintf(buf, 9, "%s", asus_info->oem_table_id); + snprintf(buf, 9, "%s", asus->dsdt_info->oem_table_id); len += sprintf(page + len, "OEM table id : %s\n", buf); - snprintf(buf, 16, "%x", asus_info->oem_revision); + snprintf(buf, 16, "%x", asus->dsdt_info->oem_revision); len += sprintf(page + len, "OEM revision : 0x%s\n", buf); - snprintf(buf, 5, "%s", asus_info->asl_compiler_id); + snprintf(buf, 5, "%s", asus->dsdt_info->asl_compiler_id); len += sprintf(page + len, "ASL comp vendor id : %s\n", buf); - snprintf(buf, 16, "%x", asus_info->asl_compiler_revision); + snprintf(buf, 16, "%x", asus->dsdt_info->asl_compiler_revision); len += sprintf(page + len, "ASL comp revision : 0x%s\n", buf); } @@ -672,8 +939,9 @@ static int parse_arg(const char *buf, unsigned long count, int *val) return count; } -static ssize_t store_status(const char *buf, size_t count, - acpi_handle handle, int mask) +static ssize_t sysfs_acpi_set(struct asus_laptop *asus, + const char *buf, size_t count, + const char *method) { int rv, value; int out = 0; @@ -682,8 +950,8 @@ static ssize_t store_status(const char *buf, size_t count, if (rv > 0) out = value ? 1 : 0; - write_status(handle, out, mask); - + if (write_acpi_int(asus->handle, method, value)) + return -ENODEV; return rv; } @@ -693,95 +961,172 @@ static ssize_t store_status(const char *buf, size_t count, static ssize_t show_ledd(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "0x%08x\n", hotk->ledd_status); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sprintf(buf, "0x%08x\n", asus->ledd_status); } static ssize_t store_ledd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct asus_laptop *asus = dev_get_drvdata(dev); int rv, value; rv = parse_arg(buf, count, &value); if (rv > 0) { - if (write_acpi_int(ledd_set_handle, NULL, value, NULL)) - pr_warning("LED display write failed\n"); - else - hotk->ledd_status = (u32) value; + if (write_acpi_int(asus->handle, METHOD_LEDD, value)) { + pr_warn("LED display write failed\n"); + return -ENODEV; + } + asus->ledd_status = (u32) value; } return rv; } /* + * Wireless + */ +static int asus_wireless_status(struct asus_laptop *asus, int mask) +{ + unsigned long long status; + acpi_status rv = AE_OK; + + if (!asus->have_rsts) + return (asus->wireless_status & mask) ? 1 : 0; + + rv = acpi_evaluate_integer(asus->handle, METHOD_WL_STATUS, + NULL, &status); + if (ACPI_FAILURE(rv)) { + pr_warn("Error reading Wireless status\n"); + return -EINVAL; + } + return !!(status & mask); +} + +/* * WLAN */ +static int asus_wlan_set(struct asus_laptop *asus, int status) +{ + if (write_acpi_int(asus->handle, METHOD_WLAN, !!status)) { + pr_warn("Error setting wlan status to %d\n", status); + return -EIO; + } + return 0; +} + static ssize_t show_wlan(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", read_status(WL_ON)); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", asus_wireless_status(asus, WL_RSTS)); } static ssize_t store_wlan(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - return store_status(buf, count, wl_switch_handle, WL_ON); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sysfs_acpi_set(asus, buf, count, METHOD_WLAN); } -/* +/*e * Bluetooth */ +static int asus_bluetooth_set(struct asus_laptop *asus, int status) +{ + if (write_acpi_int(asus->handle, METHOD_BLUETOOTH, !!status)) { + pr_warn("Error setting bluetooth status to %d\n", status); + return -EIO; + } + return 0; +} + static ssize_t show_bluetooth(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", read_status(BT_ON)); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", asus_wireless_status(asus, BT_RSTS)); } static ssize_t store_bluetooth(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - return store_status(buf, count, bt_switch_handle, BT_ON); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sysfs_acpi_set(asus, buf, count, METHOD_BLUETOOTH); } /* - * Display + * Wimax */ -static void set_display(int value) +static int asus_wimax_set(struct asus_laptop *asus, int status) { - /* no sanity check needed for now */ - if (write_acpi_int(display_set_handle, NULL, value, NULL)) - pr_warning("Error setting display\n"); - return; + if (write_acpi_int(asus->handle, METHOD_WIMAX, !!status)) { + pr_warn("Error setting wimax status to %d\n", status); + return -EIO; + } + return 0; } -static int read_display(void) +static ssize_t show_wimax(struct device *dev, + struct device_attribute *attr, char *buf) { - unsigned long long value = 0; - acpi_status rv = AE_OK; + struct asus_laptop *asus = dev_get_drvdata(dev); - /* - * In most of the case, we know how to set the display, but sometime - * we can't read it - */ - if (display_get_handle) { - rv = acpi_evaluate_integer(display_get_handle, NULL, - NULL, &value); - if (ACPI_FAILURE(rv)) - pr_warning("Error reading display status\n"); + return sprintf(buf, "%d\n", asus_wireless_status(asus, WM_RSTS)); +} + +static ssize_t store_wimax(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sysfs_acpi_set(asus, buf, count, METHOD_WIMAX); +} + +/* + * Wwan + */ +static int asus_wwan_set(struct asus_laptop *asus, int status) +{ + if (write_acpi_int(asus->handle, METHOD_WWAN, !!status)) { + pr_warn("Error setting wwan status to %d\n", status); + return -EIO; } + return 0; +} - value &= 0x0F; /* needed for some models, shouldn't hurt others */ +static ssize_t show_wwan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_laptop *asus = dev_get_drvdata(dev); - return value; + return sprintf(buf, "%d\n", asus_wireless_status(asus, WW_RSTS)); +} + +static ssize_t store_wwan(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sysfs_acpi_set(asus, buf, count, METHOD_WWAN); } /* - * Now, *this* one could be more user-friendly, but so far, no-one has - * complained. The significance of bits is the same as in store_disp() + * Display */ -static ssize_t show_disp(struct device *dev, - struct device_attribute *attr, char *buf) +static void asus_set_display(struct asus_laptop *asus, int value) { - return sprintf(buf, "%d\n", read_display()); + /* no sanity check needed for now */ + if (write_acpi_int(asus->handle, METHOD_SWITCH_DISPLAY, value)) + pr_warn("Error setting display\n"); + return; } /* @@ -794,293 +1139,580 @@ static ssize_t show_disp(struct device *dev, static ssize_t store_disp(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct asus_laptop *asus = dev_get_drvdata(dev); int rv, value; rv = parse_arg(buf, count, &value); if (rv > 0) - set_display(value); + asus_set_display(asus, value); return rv; } /* * Light Sens */ -static void set_light_sens_switch(int value) +static void asus_als_switch(struct asus_laptop *asus, int value) { - if (write_acpi_int(ls_switch_handle, NULL, value, NULL)) + int ret; + + if (asus->is_pega_lucid) { + ret = asus_pega_lucid_set(asus, PEGA_ALS, value); + if (!ret) + ret = asus_pega_lucid_set(asus, PEGA_ALS_POWER, value); + } else { + ret = write_acpi_int(asus->handle, METHOD_ALS_CONTROL, value); + } + if (ret) pr_warning("Error setting light sensor switch\n"); - hotk->light_switch = value; + + asus->light_switch = value; } static ssize_t show_lssw(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", hotk->light_switch); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", asus->light_switch); } static ssize_t store_lssw(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct asus_laptop *asus = dev_get_drvdata(dev); int rv, value; rv = parse_arg(buf, count, &value); if (rv > 0) - set_light_sens_switch(value ? 1 : 0); + asus_als_switch(asus, value ? 1 : 0); return rv; } -static void set_light_sens_level(int value) +static void asus_als_level(struct asus_laptop *asus, int value) { - if (write_acpi_int(ls_level_handle, NULL, value, NULL)) - pr_warning("Error setting light sensor level\n"); - hotk->light_level = value; + if (write_acpi_int(asus->handle, METHOD_ALS_LEVEL, value)) + pr_warn("Error setting light sensor level\n"); + asus->light_level = value; } static ssize_t show_lslvl(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", hotk->light_level); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", asus->light_level); } static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct asus_laptop *asus = dev_get_drvdata(dev); int rv, value; rv = parse_arg(buf, count, &value); if (rv > 0) { value = (0 < value) ? ((15 < value) ? 15 : value) : 0; /* 0 <= value <= 15 */ - set_light_sens_level(value); + asus_als_level(asus, value); } return rv; } +static int pega_int_read(struct asus_laptop *asus, int arg, int *result) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + int err = write_acpi_int_ret(asus->handle, METHOD_PEGA_READ, arg, + &buffer); + if (!err) { + union acpi_object *obj = buffer.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + *result = obj->integer.value; + else + err = -EIO; + } + return err; +} + +static ssize_t show_lsvalue(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_laptop *asus = dev_get_drvdata(dev); + int err, hi, lo; + + err = pega_int_read(asus, PEGA_READ_ALS_H, &hi); + if (!err) + err = pega_int_read(asus, PEGA_READ_ALS_L, &lo); + if (!err) + return sprintf(buf, "%d\n", 10 * hi + lo); + return err; +} + /* * GPS */ +static int asus_gps_status(struct asus_laptop *asus) +{ + unsigned long long status; + acpi_status rv = AE_OK; + + rv = acpi_evaluate_integer(asus->handle, METHOD_GPS_STATUS, + NULL, &status); + if (ACPI_FAILURE(rv)) { + pr_warn("Error reading GPS status\n"); + return -ENODEV; + } + return !!status; +} + +static int asus_gps_switch(struct asus_laptop *asus, int status) +{ + const char *meth = status ? METHOD_GPS_ON : METHOD_GPS_OFF; + + if (write_acpi_int(asus->handle, meth, 0x02)) + return -ENODEV; + return 0; +} + static ssize_t show_gps(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", read_status(GPS_ON)); + struct asus_laptop *asus = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", asus_gps_status(asus)); } static ssize_t store_gps(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - return store_status(buf, count, NULL, GPS_ON); + struct asus_laptop *asus = dev_get_drvdata(dev); + int rv, value; + int ret; + + rv = parse_arg(buf, count, &value); + if (rv <= 0) + return -EINVAL; + ret = asus_gps_switch(asus, !!value); + if (ret) + return ret; + rfkill_set_sw_state(asus->gps.rfkill, !value); + return rv; } /* - * Hotkey functions + * rfkill */ -static struct key_entry *asus_get_entry_by_scancode(int code) +static int asus_gps_rfkill_set(void *data, bool blocked) { - struct key_entry *key; + struct asus_laptop *asus = data; - for (key = asus_keymap; key->type != KE_END; key++) - if (code == key->code) - return key; + return asus_gps_switch(asus, !blocked); +} - return NULL; +static const struct rfkill_ops asus_gps_rfkill_ops = { + .set_block = asus_gps_rfkill_set, +}; + +static int asus_rfkill_set(void *data, bool blocked) +{ + struct asus_rfkill *rfk = data; + struct asus_laptop *asus = rfk->asus; + + if (rfk->control_id == WL_RSTS) + return asus_wlan_set(asus, !blocked); + else if (rfk->control_id == BT_RSTS) + return asus_bluetooth_set(asus, !blocked); + else if (rfk->control_id == WM_RSTS) + return asus_wimax_set(asus, !blocked); + else if (rfk->control_id == WW_RSTS) + return asus_wwan_set(asus, !blocked); + + return -EINVAL; } -static struct key_entry *asus_get_entry_by_keycode(int code) +static const struct rfkill_ops asus_rfkill_ops = { + .set_block = asus_rfkill_set, +}; + +static void asus_rfkill_terminate(struct asus_rfkill *rfk) { - struct key_entry *key; + if (!rfk->rfkill) + return ; - for (key = asus_keymap; key->type != KE_END; key++) - if (code == key->keycode && key->type == KE_KEY) - return key; + rfkill_unregister(rfk->rfkill); + rfkill_destroy(rfk->rfkill); + rfk->rfkill = NULL; +} - return NULL; +static void asus_rfkill_exit(struct asus_laptop *asus) +{ + asus_rfkill_terminate(&asus->wwan); + asus_rfkill_terminate(&asus->bluetooth); + asus_rfkill_terminate(&asus->wlan); + asus_rfkill_terminate(&asus->gps); } -static int asus_getkeycode(struct input_dev *dev, int scancode, int *keycode) +static int asus_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk, + const char *name, int control_id, int type, + const struct rfkill_ops *ops) { - struct key_entry *key = asus_get_entry_by_scancode(scancode); + int result; - if (key && key->type == KE_KEY) { - *keycode = key->keycode; - return 0; + rfk->control_id = control_id; + rfk->asus = asus; + rfk->rfkill = rfkill_alloc(name, &asus->platform_device->dev, + type, ops, rfk); + if (!rfk->rfkill) + return -EINVAL; + + result = rfkill_register(rfk->rfkill); + if (result) { + rfkill_destroy(rfk->rfkill); + rfk->rfkill = NULL; } - return -EINVAL; + return result; } -static int asus_setkeycode(struct input_dev *dev, int scancode, int keycode) +static int asus_rfkill_init(struct asus_laptop *asus) { - struct key_entry *key; - int old_keycode; + int result = 0; - if (keycode < 0 || keycode > KEY_MAX) - return -EINVAL; + if (asus->is_pega_lucid) + return -ENODEV; - key = asus_get_entry_by_scancode(scancode); - if (key && key->type == KE_KEY) { - old_keycode = key->keycode; - key->keycode = keycode; - set_bit(keycode, dev->keybit); - if (!asus_get_entry_by_keycode(old_keycode)) - clear_bit(old_keycode, dev->keybit); - return 0; - } + if (!acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) && + !acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) && + !acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL)) + result = asus_rfkill_setup(asus, &asus->gps, "asus-gps", + -1, RFKILL_TYPE_GPS, + &asus_gps_rfkill_ops); + if (result) + goto exit; - return -EINVAL; + + if (!acpi_check_handle(asus->handle, METHOD_WLAN, NULL) && + asus->wled_type == TYPE_RFKILL) + result = asus_rfkill_setup(asus, &asus->wlan, "asus-wlan", + WL_RSTS, RFKILL_TYPE_WLAN, + &asus_rfkill_ops); + if (result) + goto exit; + + if (!acpi_check_handle(asus->handle, METHOD_BLUETOOTH, NULL) && + asus->bled_type == TYPE_RFKILL) + result = asus_rfkill_setup(asus, &asus->bluetooth, + "asus-bluetooth", BT_RSTS, + RFKILL_TYPE_BLUETOOTH, + &asus_rfkill_ops); + if (result) + goto exit; + + if (!acpi_check_handle(asus->handle, METHOD_WWAN, NULL)) + result = asus_rfkill_setup(asus, &asus->wwan, "asus-wwan", + WW_RSTS, RFKILL_TYPE_WWAN, + &asus_rfkill_ops); + if (result) + goto exit; + + if (!acpi_check_handle(asus->handle, METHOD_WIMAX, NULL)) + result = asus_rfkill_setup(asus, &asus->wimax, "asus-wimax", + WM_RSTS, RFKILL_TYPE_WIMAX, + &asus_rfkill_ops); + if (result) + goto exit; + +exit: + if (result) + asus_rfkill_exit(asus); + + return result; } -static void asus_hotk_notify(struct acpi_device *device, u32 event) +static int pega_rfkill_set(void *data, bool blocked) { - static struct key_entry *key; - u16 count; + struct asus_rfkill *rfk = data; - /* TODO Find a better way to handle events count. */ - if (!hotk) - return; + int ret = asus_pega_lucid_set(rfk->asus, rfk->control_id, !blocked); + return ret; +} - /* - * We need to tell the backlight device when the backlight power is - * switched - */ - if (event == ATKD_LCD_ON) { - write_status(NULL, 1, LCD_ON); - lcd_blank(FB_BLANK_UNBLANK); - } else if (event == ATKD_LCD_OFF) { - write_status(NULL, 0, LCD_ON); - lcd_blank(FB_BLANK_POWERDOWN); +static const struct rfkill_ops pega_rfkill_ops = { + .set_block = pega_rfkill_set, +}; + +static int pega_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk, + const char *name, int controlid, int rfkill_type) +{ + return asus_rfkill_setup(asus, rfk, name, controlid, rfkill_type, + &pega_rfkill_ops); +} + +static int pega_rfkill_init(struct asus_laptop *asus) +{ + int ret = 0; + + if(!asus->is_pega_lucid) + return -ENODEV; + + ret = pega_rfkill_setup(asus, &asus->wlan, "pega-wlan", + PEGA_WLAN, RFKILL_TYPE_WLAN); + if(ret) + goto exit; + + ret = pega_rfkill_setup(asus, &asus->bluetooth, "pega-bt", + PEGA_BLUETOOTH, RFKILL_TYPE_BLUETOOTH); + if(ret) + goto exit; + + ret = pega_rfkill_setup(asus, &asus->wwan, "pega-wwan", + PEGA_WWAN, RFKILL_TYPE_WWAN); + +exit: + if (ret) + asus_rfkill_exit(asus); + + return ret; +} + +/* + * Input device (i.e. hotkeys) + */ +static void asus_input_notify(struct asus_laptop *asus, int event) +{ + if (!asus->inputdev) + return ; + if (!sparse_keymap_report_event(asus->inputdev, event, 1, true)) + pr_info("Unknown key %x pressed\n", event); +} + +static int asus_input_init(struct asus_laptop *asus) +{ + struct input_dev *input; + int error; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + input->name = "Asus Laptop extra buttons"; + input->phys = ASUS_LAPTOP_FILE "/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &asus->platform_device->dev; + + error = sparse_keymap_setup(input, asus_keymap, NULL); + if (error) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; + } + error = input_register_device(input); + if (error) { + pr_warn("Unable to register input device\n"); + goto err_free_keymap; } - count = hotk->event_count[event % 128]++; - acpi_bus_generate_proc_event(hotk->device, event, count); - acpi_bus_generate_netlink_event(hotk->device->pnp.device_class, - dev_name(&hotk->device->dev), event, + asus->inputdev = input; + return 0; + +err_free_keymap: + sparse_keymap_free(input); +err_free_dev: + input_free_device(input); + return error; +} + +static void asus_input_exit(struct asus_laptop *asus) +{ + if (asus->inputdev) { + sparse_keymap_free(asus->inputdev); + input_unregister_device(asus->inputdev); + } + asus->inputdev = NULL; +} + +/* + * ACPI driver + */ +static void asus_acpi_notify(struct acpi_device *device, u32 event) +{ + struct asus_laptop *asus = acpi_driver_data(device); + u16 count; + + /* TODO Find a better way to handle events count. */ + count = asus->event_count[event % 128]++; + acpi_bus_generate_netlink_event(asus->device->pnp.device_class, + dev_name(&asus->device->dev), event, count); - if (hotk->inputdev) { - key = asus_get_entry_by_scancode(event); - if (!key) + if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX) + event = ATKD_BRNUP; + else if (event >= ATKD_BRNDOWN_MIN && + event <= ATKD_BRNDOWN_MAX) + event = ATKD_BRNDOWN; + + /* Brightness events are special */ + if (event == ATKD_BRNDOWN || event == ATKD_BRNUP) { + if (asus->backlight_device != NULL) { + /* Update the backlight device. */ + asus_backlight_notify(asus); return ; - - switch (key->type) { - case KE_KEY: - input_report_key(hotk->inputdev, key->keycode, 1); - input_sync(hotk->inputdev); - input_report_key(hotk->inputdev, key->keycode, 0); - input_sync(hotk->inputdev); - break; } } -} -#define ASUS_CREATE_DEVICE_ATTR(_name) \ - struct device_attribute dev_attr_##_name = { \ - .attr = { \ - .name = __stringify(_name), \ - .mode = 0 }, \ - .show = NULL, \ - .store = NULL, \ + /* Accelerometer "coarse orientation change" event */ + if (asus->pega_accel_poll && event == 0xEA) { + kobject_uevent(&asus->pega_accel_poll->input->dev.kobj, + KOBJ_CHANGE); + return ; } -#define ASUS_SET_DEVICE_ATTR(_name, _mode, _show, _store) \ - do { \ - dev_attr_##_name.attr.mode = _mode; \ - dev_attr_##_name.show = _show; \ - dev_attr_##_name.store = _store; \ - } while(0) - -static ASUS_CREATE_DEVICE_ATTR(infos); -static ASUS_CREATE_DEVICE_ATTR(wlan); -static ASUS_CREATE_DEVICE_ATTR(bluetooth); -static ASUS_CREATE_DEVICE_ATTR(display); -static ASUS_CREATE_DEVICE_ATTR(ledd); -static ASUS_CREATE_DEVICE_ATTR(ls_switch); -static ASUS_CREATE_DEVICE_ATTR(ls_level); -static ASUS_CREATE_DEVICE_ATTR(gps); - -static struct attribute *asuspf_attributes[] = { + asus_input_notify(asus, event); +} + +static DEVICE_ATTR(infos, S_IRUGO, show_infos, NULL); +static DEVICE_ATTR(wlan, S_IRUGO | S_IWUSR, show_wlan, store_wlan); +static DEVICE_ATTR(bluetooth, S_IRUGO | S_IWUSR, + show_bluetooth, store_bluetooth); +static DEVICE_ATTR(wimax, S_IRUGO | S_IWUSR, show_wimax, store_wimax); +static DEVICE_ATTR(wwan, S_IRUGO | S_IWUSR, show_wwan, store_wwan); +static DEVICE_ATTR(display, S_IWUSR, NULL, store_disp); +static DEVICE_ATTR(ledd, S_IRUGO | S_IWUSR, show_ledd, store_ledd); +static DEVICE_ATTR(ls_value, S_IRUGO, show_lsvalue, NULL); +static DEVICE_ATTR(ls_level, S_IRUGO | S_IWUSR, show_lslvl, store_lslvl); +static DEVICE_ATTR(ls_switch, S_IRUGO | S_IWUSR, show_lssw, store_lssw); +static DEVICE_ATTR(gps, S_IRUGO | S_IWUSR, show_gps, store_gps); + +static struct attribute *asus_attributes[] = { &dev_attr_infos.attr, &dev_attr_wlan.attr, &dev_attr_bluetooth.attr, + &dev_attr_wimax.attr, + &dev_attr_wwan.attr, &dev_attr_display.attr, &dev_attr_ledd.attr, - &dev_attr_ls_switch.attr, + &dev_attr_ls_value.attr, &dev_attr_ls_level.attr, + &dev_attr_ls_switch.attr, &dev_attr_gps.attr, NULL }; -static struct attribute_group asuspf_attribute_group = { - .attrs = asuspf_attributes -}; +static umode_t asus_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, + int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct platform_device *pdev = to_platform_device(dev); + struct asus_laptop *asus = platform_get_drvdata(pdev); + acpi_handle handle = asus->handle; + bool supported; + + if (asus->is_pega_lucid) { + /* no ls_level interface on the Lucid */ + if (attr == &dev_attr_ls_switch.attr) + supported = true; + else if (attr == &dev_attr_ls_level.attr) + supported = false; + else + goto normal; -static struct platform_driver asuspf_driver = { - .driver = { - .name = ASUS_HOTK_FILE, - .owner = THIS_MODULE, - } -}; + return supported; + } -static struct platform_device *asuspf_device; +normal: + if (attr == &dev_attr_wlan.attr) { + supported = !acpi_check_handle(handle, METHOD_WLAN, NULL); -static void asus_hotk_add_fs(void) -{ - ASUS_SET_DEVICE_ATTR(infos, 0444, show_infos, NULL); + } else if (attr == &dev_attr_bluetooth.attr) { + supported = !acpi_check_handle(handle, METHOD_BLUETOOTH, NULL); - if (wl_switch_handle) - ASUS_SET_DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan); + } else if (attr == &dev_attr_display.attr) { + supported = !acpi_check_handle(handle, METHOD_SWITCH_DISPLAY, NULL); - if (bt_switch_handle) - ASUS_SET_DEVICE_ATTR(bluetooth, 0644, - show_bluetooth, store_bluetooth); + } else if (attr == &dev_attr_wimax.attr) { + supported = + !acpi_check_handle(asus->handle, METHOD_WIMAX, NULL); - if (display_set_handle && display_get_handle) - ASUS_SET_DEVICE_ATTR(display, 0644, show_disp, store_disp); - else if (display_set_handle) - ASUS_SET_DEVICE_ATTR(display, 0200, NULL, store_disp); + } else if (attr == &dev_attr_wwan.attr) { + supported = !acpi_check_handle(asus->handle, METHOD_WWAN, NULL); - if (ledd_set_handle) - ASUS_SET_DEVICE_ATTR(ledd, 0644, show_ledd, store_ledd); + } else if (attr == &dev_attr_ledd.attr) { + supported = !acpi_check_handle(handle, METHOD_LEDD, NULL); - if (ls_switch_handle && ls_level_handle) { - ASUS_SET_DEVICE_ATTR(ls_level, 0644, show_lslvl, store_lslvl); - ASUS_SET_DEVICE_ATTR(ls_switch, 0644, show_lssw, store_lssw); + } else if (attr == &dev_attr_ls_switch.attr || + attr == &dev_attr_ls_level.attr) { + supported = !acpi_check_handle(handle, METHOD_ALS_CONTROL, NULL) && + !acpi_check_handle(handle, METHOD_ALS_LEVEL, NULL); + } else if (attr == &dev_attr_ls_value.attr) { + supported = asus->is_pega_lucid; + } else if (attr == &dev_attr_gps.attr) { + supported = !acpi_check_handle(handle, METHOD_GPS_ON, NULL) && + !acpi_check_handle(handle, METHOD_GPS_OFF, NULL) && + !acpi_check_handle(handle, METHOD_GPS_STATUS, NULL); + } else { + supported = true; } - if (gps_status_handle && gps_on_handle && gps_off_handle) - ASUS_SET_DEVICE_ATTR(gps, 0644, show_gps, store_gps); + return supported ? attr->mode : 0; } -static int asus_handle_init(char *name, acpi_handle * handle, - char **paths, int num_paths) + +static const struct attribute_group asus_attr_group = { + .is_visible = asus_sysfs_is_visible, + .attrs = asus_attributes, +}; + +static int asus_platform_init(struct asus_laptop *asus) { - int i; - acpi_status status; + int result; - for (i = 0; i < num_paths; i++) { - status = acpi_get_handle(NULL, paths[i], handle); - if (ACPI_SUCCESS(status)) - return 0; - } + asus->platform_device = platform_device_alloc(ASUS_LAPTOP_FILE, -1); + if (!asus->platform_device) + return -ENOMEM; + platform_set_drvdata(asus->platform_device, asus); - *handle = NULL; - return -ENODEV; + result = platform_device_add(asus->platform_device); + if (result) + goto fail_platform_device; + + result = sysfs_create_group(&asus->platform_device->dev.kobj, + &asus_attr_group); + if (result) + goto fail_sysfs; + + return 0; + +fail_sysfs: + platform_device_del(asus->platform_device); +fail_platform_device: + platform_device_put(asus->platform_device); + return result; } -#define ASUS_HANDLE_INIT(object) \ - asus_handle_init(#object, &object##_handle, object##_paths, \ - ARRAY_SIZE(object##_paths)) +static void asus_platform_exit(struct asus_laptop *asus) +{ + sysfs_remove_group(&asus->platform_device->dev.kobj, &asus_attr_group); + platform_device_unregister(asus->platform_device); +} + +static struct platform_driver platform_driver = { + .driver = { + .name = ASUS_LAPTOP_FILE, + .owner = THIS_MODULE, + }, +}; /* - * This function is used to initialize the hotk with right values. In this - * method, we can make all the detection we want, and modify the hotk struct + * This function is used to initialize the context with right values. In this + * method, we can make all the detection we want, and modify the asus_laptop + * struct */ -static int asus_hotk_get_info(void) +static int asus_laptop_get_info(struct asus_laptop *asus) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *model = NULL; - unsigned long long bsts_result, hwrs_result; + unsigned long long bsts_result; char *string = NULL; acpi_status status; @@ -1089,31 +1721,30 @@ static int asus_hotk_get_info(void) * models, but late enough to allow acpi_bus_register_driver() to fail * before doing anything ACPI-specific. Should we encounter a machine, * which needs special handling (i.e. its hotkey device has a different - * HID), this bit will be moved. A global variable asus_info contains - * the DSDT header. + * HID), this bit will be moved. */ - status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus_info); + status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus->dsdt_info); if (ACPI_FAILURE(status)) - pr_warning("Couldn't get the DSDT table header\n"); + pr_warn("Couldn't get the DSDT table header\n"); /* We have to write 0 on init this far for all ASUS models */ - if (write_acpi_int(hotk->handle, "INIT", 0, &buffer)) { + if (write_acpi_int_ret(asus->handle, "INIT", 0, &buffer)) { pr_err("Hotkey initialization failed\n"); return -ENODEV; } /* This needs to be called for some laptops to init properly */ status = - acpi_evaluate_integer(hotk->handle, "BSTS", NULL, &bsts_result); + acpi_evaluate_integer(asus->handle, "BSTS", NULL, &bsts_result); if (ACPI_FAILURE(status)) - pr_warning("Error calling BSTS\n"); + pr_warn("Error calling BSTS\n"); else if (bsts_result) pr_notice("BSTS called, 0x%02x returned\n", (uint) bsts_result); /* This too ... */ - write_acpi_int(hotk->handle, "CWAP", wapf, NULL); - + if (write_acpi_int(asus->handle, "CWAP", wapf)) + pr_err("Error calling CWAP(%d)\n", wapf); /* * Try to match the object returned by INIT to the specific model. * Handle every possible object (or the lack of thereof) the DSDT @@ -1134,397 +1765,243 @@ static int asus_hotk_get_info(void) break; } } - hotk->name = kstrdup(string, GFP_KERNEL); - if (!hotk->name) + asus->name = kstrdup(string, GFP_KERNEL); + if (!asus->name) { + kfree(buffer.pointer); return -ENOMEM; + } - if (*string) + if (string) pr_notice(" %s model detected\n", string); - ASUS_HANDLE_INIT(mled_set); - ASUS_HANDLE_INIT(tled_set); - ASUS_HANDLE_INIT(rled_set); - ASUS_HANDLE_INIT(pled_set); - ASUS_HANDLE_INIT(gled_set); - - ASUS_HANDLE_INIT(ledd_set); - - ASUS_HANDLE_INIT(kled_set); - ASUS_HANDLE_INIT(kled_get); - - /* - * The HWRS method return informations about the hardware. - * 0x80 bit is for WLAN, 0x100 for Bluetooth. - * The significance of others is yet to be found. - * If we don't find the method, we assume the device are present. - */ - status = - acpi_evaluate_integer(hotk->handle, "HRWS", NULL, &hwrs_result); - if (ACPI_FAILURE(status)) - hwrs_result = WL_HWRS | BT_HWRS; - - if (hwrs_result & WL_HWRS) - ASUS_HANDLE_INIT(wl_switch); - if (hwrs_result & BT_HWRS) - ASUS_HANDLE_INIT(bt_switch); - - ASUS_HANDLE_INIT(wireless_status); - - ASUS_HANDLE_INIT(brightness_set); - ASUS_HANDLE_INIT(brightness_get); - - ASUS_HANDLE_INIT(lcd_switch); - - ASUS_HANDLE_INIT(display_set); - ASUS_HANDLE_INIT(display_get); - - /* - * There is a lot of models with "ALSL", but a few get - * a real light sens, so we need to check it. - */ - if (!ASUS_HANDLE_INIT(ls_switch)) - ASUS_HANDLE_INIT(ls_level); - - ASUS_HANDLE_INIT(gps_on); - ASUS_HANDLE_INIT(gps_off); - ASUS_HANDLE_INIT(gps_status); + if (!acpi_check_handle(asus->handle, METHOD_WL_STATUS, NULL)) + asus->have_rsts = true; kfree(model); return AE_OK; } -static int asus_input_init(void) -{ - const struct key_entry *key; - int result; - - hotk->inputdev = input_allocate_device(); - if (!hotk->inputdev) { - pr_info("Unable to allocate input device\n"); - return 0; - } - hotk->inputdev->name = "Asus Laptop extra buttons"; - hotk->inputdev->phys = ASUS_HOTK_FILE "/input0"; - hotk->inputdev->id.bustype = BUS_HOST; - hotk->inputdev->getkeycode = asus_getkeycode; - hotk->inputdev->setkeycode = asus_setkeycode; - - for (key = asus_keymap; key->type != KE_END; key++) { - switch (key->type) { - case KE_KEY: - set_bit(EV_KEY, hotk->inputdev->evbit); - set_bit(key->keycode, hotk->inputdev->keybit); - break; - } - } - result = input_register_device(hotk->inputdev); - if (result) { - pr_info("Unable to register input device\n"); - input_free_device(hotk->inputdev); - } - return result; -} - -static int asus_hotk_check(void) +static int asus_acpi_init(struct asus_laptop *asus) { int result = 0; - result = acpi_bus_get_status(hotk->device); + result = acpi_bus_get_status(asus->device); if (result) return result; - - if (hotk->device->status.present) { - result = asus_hotk_get_info(); - } else { + if (!asus->device->status.present) { pr_err("Hotkey device not present, aborting\n"); - return -EINVAL; + return -ENODEV; } - return result; -} - -static int asus_hotk_found; - -static int asus_hotk_add(struct acpi_device *device) -{ - int result; - - pr_notice("Asus Laptop Support version %s\n", - ASUS_LAPTOP_VERSION); - - hotk = kzalloc(sizeof(struct asus_hotk), GFP_KERNEL); - if (!hotk) - return -ENOMEM; - - hotk->handle = device->handle; - strcpy(acpi_device_name(device), ASUS_HOTK_DEVICE_NAME); - strcpy(acpi_device_class(device), ASUS_HOTK_CLASS); - device->driver_data = hotk; - hotk->device = device; - - result = asus_hotk_check(); + result = asus_laptop_get_info(asus); if (result) - goto end; + return result; + + if (!strcmp(bled_type, "led")) + asus->bled_type = TYPE_LED; + else if (!strcmp(bled_type, "rfkill")) + asus->bled_type = TYPE_RFKILL; - asus_hotk_add_fs(); + if (!strcmp(wled_type, "led")) + asus->wled_type = TYPE_LED; + else if (!strcmp(wled_type, "rfkill")) + asus->wled_type = TYPE_RFKILL; - asus_hotk_found = 1; + if (bluetooth_status >= 0) + asus_bluetooth_set(asus, !!bluetooth_status); - /* WLED and BLED are on by default */ - write_status(bt_switch_handle, 1, BT_ON); - write_status(wl_switch_handle, 1, WL_ON); + if (wlan_status >= 0) + asus_wlan_set(asus, !!wlan_status); - /* If the h/w switch is off, we need to check the real status */ - write_status(NULL, read_status(BT_ON), BT_ON); - write_status(NULL, read_status(WL_ON), WL_ON); + if (wimax_status >= 0) + asus_wimax_set(asus, !!wimax_status); - /* LCD Backlight is on by default */ - write_status(NULL, 1, LCD_ON); + if (wwan_status >= 0) + asus_wwan_set(asus, !!wwan_status); /* Keyboard Backlight is on by default */ - if (kled_set_handle) - set_kled_lvl(1); + if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL)) + asus_kled_set(asus, 1); /* LED display is off by default */ - hotk->ledd_status = 0xFFF; + asus->ledd_status = 0xFFF; /* Set initial values of light sensor and level */ - hotk->light_switch = 0; /* Default to light sensor disabled */ - hotk->light_level = 5; /* level 5 for sensor sensitivity */ - - if (ls_switch_handle) - set_light_sens_switch(hotk->light_switch); - - if (ls_level_handle) - set_light_sens_level(hotk->light_level); - - /* GPS is on by default */ - write_status(NULL, 1, GPS_ON); - -end: - if (result) { - kfree(hotk->name); - kfree(hotk); + asus->light_switch = !!als_status; + asus->light_level = 5; /* level 5 for sensor sensitivity */ + + if (asus->is_pega_lucid) { + asus_als_switch(asus, asus->light_switch); + } else if (!acpi_check_handle(asus->handle, METHOD_ALS_CONTROL, NULL) && + !acpi_check_handle(asus->handle, METHOD_ALS_LEVEL, NULL)) { + asus_als_switch(asus, asus->light_switch); + asus_als_level(asus, asus->light_level); } return result; } -static int asus_hotk_remove(struct acpi_device *device, int type) +static void asus_dmi_check(void) { - kfree(hotk->name); - kfree(hotk); + const char *model; - return 0; -} - -static void asus_backlight_exit(void) -{ - if (asus_backlight_device) - backlight_device_unregister(asus_backlight_device); -} - -#define ASUS_LED_UNREGISTER(object) \ - if (object##_led.dev) \ - led_classdev_unregister(&object##_led) + model = dmi_get_system_info(DMI_PRODUCT_NAME); + if (!model) + return; -static void asus_led_exit(void) -{ - destroy_workqueue(led_workqueue); - ASUS_LED_UNREGISTER(mled); - ASUS_LED_UNREGISTER(tled); - ASUS_LED_UNREGISTER(pled); - ASUS_LED_UNREGISTER(rled); - ASUS_LED_UNREGISTER(gled); - ASUS_LED_UNREGISTER(kled); + /* On L1400B WLED control the sound card, don't mess with it ... */ + if (strncmp(model, "L1400B", 6) == 0) { + wlan_status = -1; + } } -static void asus_input_exit(void) -{ - if (hotk->inputdev) - input_unregister_device(hotk->inputdev); -} +static bool asus_device_present; -static void __exit asus_laptop_exit(void) +static int asus_acpi_add(struct acpi_device *device) { - asus_backlight_exit(); - asus_led_exit(); - asus_input_exit(); - - acpi_bus_unregister_driver(&asus_hotk_driver); - sysfs_remove_group(&asuspf_device->dev.kobj, &asuspf_attribute_group); - platform_device_unregister(asuspf_device); - platform_driver_unregister(&asuspf_driver); -} + struct asus_laptop *asus; + int result; -static int asus_backlight_init(struct device *dev) -{ - struct backlight_device *bd; + pr_notice("Asus Laptop Support version %s\n", + ASUS_LAPTOP_VERSION); + asus = kzalloc(sizeof(struct asus_laptop), GFP_KERNEL); + if (!asus) + return -ENOMEM; + asus->handle = device->handle; + strcpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME); + strcpy(acpi_device_class(device), ASUS_LAPTOP_CLASS); + device->driver_data = asus; + asus->device = device; - if (brightness_set_handle && lcd_switch_handle) { - bd = backlight_device_register(ASUS_HOTK_FILE, dev, - NULL, &asusbl_ops); - if (IS_ERR(bd)) { - pr_err("Could not register asus backlight device\n"); - asus_backlight_device = NULL; - return PTR_ERR(bd); - } + asus_dmi_check(); - asus_backlight_device = bd; + result = asus_acpi_init(asus); + if (result) + goto fail_platform; - bd->props.max_brightness = 15; - bd->props.brightness = read_brightness(NULL); - bd->props.power = FB_BLANK_UNBLANK; - backlight_update_status(bd); - } - return 0; -} + /* + * Need platform type detection first, then the platform + * device. It is used as a parent for the sub-devices below. + */ + asus->is_pega_lucid = asus_check_pega_lucid(asus); + result = asus_platform_init(asus); + if (result) + goto fail_platform; -static int asus_led_register(acpi_handle handle, - struct led_classdev *ldev, struct device *dev) -{ - if (!handle) - return 0; + if (!acpi_video_backlight_support()) { + result = asus_backlight_init(asus); + if (result) + goto fail_backlight; + } else + pr_info("Backlight controlled by ACPI video driver\n"); - return led_classdev_register(dev, ldev); -} + result = asus_input_init(asus); + if (result) + goto fail_input; -#define ASUS_LED_REGISTER(object, device) \ - asus_led_register(object##_set_handle, &object##_led, device) + result = asus_led_init(asus); + if (result) + goto fail_led; -static int asus_led_init(struct device *dev) -{ - int rv; + result = asus_rfkill_init(asus); + if (result && result != -ENODEV) + goto fail_rfkill; - rv = ASUS_LED_REGISTER(mled, dev); - if (rv) - goto out; + result = pega_accel_init(asus); + if (result && result != -ENODEV) + goto fail_pega_accel; - rv = ASUS_LED_REGISTER(tled, dev); - if (rv) - goto out1; + result = pega_rfkill_init(asus); + if (result && result != -ENODEV) + goto fail_pega_rfkill; - rv = ASUS_LED_REGISTER(rled, dev); - if (rv) - goto out2; + asus_device_present = true; + return 0; - rv = ASUS_LED_REGISTER(pled, dev); - if (rv) - goto out3; +fail_pega_rfkill: + pega_accel_exit(asus); +fail_pega_accel: + asus_rfkill_exit(asus); +fail_rfkill: + asus_led_exit(asus); +fail_led: + asus_input_exit(asus); +fail_input: + asus_backlight_exit(asus); +fail_backlight: + asus_platform_exit(asus); +fail_platform: + kfree(asus); - rv = ASUS_LED_REGISTER(gled, dev); - if (rv) - goto out4; + return result; +} - if (kled_set_handle && kled_get_handle) - rv = ASUS_LED_REGISTER(kled, dev); - if (rv) - goto out5; +static int asus_acpi_remove(struct acpi_device *device) +{ + struct asus_laptop *asus = acpi_driver_data(device); - led_workqueue = create_singlethread_workqueue("led_workqueue"); - if (!led_workqueue) - goto out6; + asus_backlight_exit(asus); + asus_rfkill_exit(asus); + asus_led_exit(asus); + asus_input_exit(asus); + pega_accel_exit(asus); + asus_platform_exit(asus); + kfree(asus->name); + kfree(asus); return 0; -out6: - rv = -ENOMEM; - ASUS_LED_UNREGISTER(kled); -out5: - ASUS_LED_UNREGISTER(gled); -out4: - ASUS_LED_UNREGISTER(pled); -out3: - ASUS_LED_UNREGISTER(rled); -out2: - ASUS_LED_UNREGISTER(tled); -out1: - ASUS_LED_UNREGISTER(mled); -out: - return rv; } +static const struct acpi_device_id asus_device_ids[] = { + {"ATK0100", 0}, + {"ATK0101", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, asus_device_ids); + +static struct acpi_driver asus_acpi_driver = { + .name = ASUS_LAPTOP_NAME, + .class = ASUS_LAPTOP_CLASS, + .owner = THIS_MODULE, + .ids = asus_device_ids, + .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, + .ops = { + .add = asus_acpi_add, + .remove = asus_acpi_remove, + .notify = asus_acpi_notify, + }, +}; + static int __init asus_laptop_init(void) { int result; - result = acpi_bus_register_driver(&asus_hotk_driver); + result = platform_driver_register(&platform_driver); if (result < 0) return result; - /* - * This is a bit of a kludge. We only want this module loaded - * for ASUS systems, but there's currently no way to probe the - * ACPI namespace for ASUS HIDs. So we just return failure if - * we didn't find one, which will cause the module to be - * unloaded. - */ - if (!asus_hotk_found) { - acpi_bus_unregister_driver(&asus_hotk_driver); - return -ENODEV; - } - - result = asus_input_init(); - if (result) - goto fail_input; - - /* Register platform stuff */ - result = platform_driver_register(&asuspf_driver); - if (result) - goto fail_platform_driver; - - asuspf_device = platform_device_alloc(ASUS_HOTK_FILE, -1); - if (!asuspf_device) { - result = -ENOMEM; - goto fail_platform_device1; + result = acpi_bus_register_driver(&asus_acpi_driver); + if (result < 0) + goto fail_acpi_driver; + if (!asus_device_present) { + result = -ENODEV; + goto fail_no_device; } - - result = platform_device_add(asuspf_device); - if (result) - goto fail_platform_device2; - - result = sysfs_create_group(&asuspf_device->dev.kobj, - &asuspf_attribute_group); - if (result) - goto fail_sysfs; - - result = asus_led_init(&asuspf_device->dev); - if (result) - goto fail_led; - - if (!acpi_video_backlight_support()) { - result = asus_backlight_init(&asuspf_device->dev); - if (result) - goto fail_backlight; - } else - pr_info("Brightness ignored, must be controlled by " - "ACPI video driver\n"); - return 0; -fail_backlight: - asus_led_exit(); - -fail_led: - sysfs_remove_group(&asuspf_device->dev.kobj, - &asuspf_attribute_group); - -fail_sysfs: - platform_device_del(asuspf_device); - -fail_platform_device2: - platform_device_put(asuspf_device); - -fail_platform_device1: - platform_driver_unregister(&asuspf_driver); - -fail_platform_driver: - asus_input_exit(); - -fail_input: - +fail_no_device: + acpi_bus_unregister_driver(&asus_acpi_driver); +fail_acpi_driver: + platform_driver_unregister(&platform_driver); return result; } +static void __exit asus_laptop_exit(void) +{ + acpi_bus_unregister_driver(&asus_acpi_driver); + platform_driver_unregister(&platform_driver); +} + module_init(asus_laptop_init); module_exit(asus_laptop_exit); diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c new file mode 100644 index 00000000000..ddf0eefd862 --- /dev/null +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -0,0 +1,312 @@ +/* + * Asus Notebooks WMI hotkey driver + * + * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/fb.h> +#include <linux/dmi.h> + +#include "asus-wmi.h" + +#define ASUS_NB_WMI_FILE "asus-nb-wmi" + +MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>"); +MODULE_DESCRIPTION("Asus Notebooks WMI Hotkey Driver"); +MODULE_LICENSE("GPL"); + +#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" + +MODULE_ALIAS("wmi:"ASUS_NB_WMI_EVENT_GUID); + +/* + * WAPF defines the behavior of the Fn+Fx wlan key + * The significance of values is yet to be found, but + * most of the time: + * Bit | Bluetooth | WLAN + * 0 | Hardware | Hardware + * 1 | Hardware | Software + * 4 | Software | Software + */ +static int wapf = -1; +module_param(wapf, uint, 0444); +MODULE_PARM_DESC(wapf, "WAPF value"); + +static struct quirk_entry *quirks; + +static struct quirk_entry quirk_asus_unknown = { + .wapf = 0, +}; + +/* + * For those machines that need software to control bt/wifi status + * and can't adjust brightness through ACPI interface + * and have duplicate events(ACPI and WMI) for display toggle + */ +static struct quirk_entry quirk_asus_x55u = { + .wapf = 4, + .wmi_backlight_power = true, + .no_display_toggle = true, +}; + +static struct quirk_entry quirk_asus_x401u = { + .wapf = 4, +}; + +static int dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +static struct dmi_system_id asus_quirks[] = { + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X401U", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X401U"), + }, + .driver_data = &quirk_asus_x55u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X401A", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X401A"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X401A1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X401A1"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X501U", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X501U"), + }, + .driver_data = &quirk_asus_x55u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X501A", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X501A"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X501A1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X501A1"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X550CA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X550CA"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X55A", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X55A"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X55C", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X55C"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X55U", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X55U"), + }, + .driver_data = &quirk_asus_x55u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X55VD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X55VD"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X75A", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X75A"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. 1015E", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "1015E"), + }, + .driver_data = &quirk_asus_x401u, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. 1015U", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "1015U"), + }, + .driver_data = &quirk_asus_x401u, + }, + {}, +}; + +static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver) +{ + quirks = &quirk_asus_unknown; + dmi_check_system(asus_quirks); + + driver->quirks = quirks; + driver->panel_power = FB_BLANK_UNBLANK; + + /* overwrite the wapf setting if the wapf paramater is specified */ + if (wapf != -1) + quirks->wapf = wapf; + else + wapf = quirks->wapf; +} + +static const struct key_entry asus_nb_wmi_keymap[] = { + { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x30, { KEY_VOLUMEUP } }, + { KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0x32, { KEY_MUTE } }, + { KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */ + { KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */ + { KE_KEY, 0x40, { KEY_PREVIOUSSONG } }, + { KE_KEY, 0x41, { KEY_NEXTSONG } }, + { KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */ + { KE_KEY, 0x45, { KEY_PLAYPAUSE } }, + { KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */ + { KE_KEY, 0x50, { KEY_EMAIL } }, + { KE_KEY, 0x51, { KEY_WWW } }, + { KE_KEY, 0x55, { KEY_CALC } }, + { KE_IGNORE, 0x57, }, /* Battery mode */ + { KE_IGNORE, 0x58, }, /* AC mode */ + { KE_KEY, 0x5C, { KEY_F15 } }, /* Power Gear key */ + { KE_KEY, 0x5D, { KEY_WLAN } }, /* Wireless console Toggle */ + { KE_KEY, 0x5E, { KEY_WLAN } }, /* Wireless console Enable */ + { KE_KEY, 0x5F, { KEY_WLAN } }, /* Wireless console Disable */ + { KE_KEY, 0x60, { KEY_TOUCHPAD_ON } }, + { KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */ + { KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */ + { KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */ + { KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */ + { KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */ + { KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */ + { KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */ + { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, + { KE_IGNORE, 0x6E, }, /* Low Battery notification */ + { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */ + { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */ + { KE_KEY, 0x82, { KEY_CAMERA } }, + { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ + { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ + { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ + { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ + { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ + { KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */ + { KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */ + { KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */ + { KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */ + { KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */ + { KE_KEY, 0x95, { KEY_MEDIA } }, + { KE_KEY, 0x99, { KEY_PHONE } }, + { KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */ + { KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */ + { KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */ + { KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */ + { KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */ + { KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */ + { KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */ + { KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */ + { KE_KEY, 0xB5, { KEY_CALC } }, + { KE_KEY, 0xC4, { KEY_KBDILLUMUP } }, + { KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } }, + { KE_IGNORE, 0xC6, }, /* Ambient Light Sensor notification */ + { KE_END, 0}, +}; + +static struct asus_wmi_driver asus_nb_wmi_driver = { + .name = ASUS_NB_WMI_FILE, + .owner = THIS_MODULE, + .event_guid = ASUS_NB_WMI_EVENT_GUID, + .keymap = asus_nb_wmi_keymap, + .input_name = "Asus WMI hotkeys", + .input_phys = ASUS_NB_WMI_FILE "/input0", + .detect_quirks = asus_nb_wmi_quirks, +}; + + +static int __init asus_nb_wmi_init(void) +{ + return asus_wmi_register_driver(&asus_nb_wmi_driver); +} + +static void __exit asus_nb_wmi_exit(void) +{ + asus_wmi_unregister_driver(&asus_nb_wmi_driver); +} + +module_init(asus_nb_wmi_init); +module_exit(asus_nb_wmi_exit); diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c new file mode 100644 index 00000000000..3c6ccedc82b --- /dev/null +++ b/drivers/platform/x86/asus-wmi.c @@ -0,0 +1,1975 @@ +/* + * Asus PC WMI hotkey driver + * + * Copyright(C) 2010 Intel Corporation. + * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com> + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/leds.h> +#include <linux/rfkill.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> +#include <linux/acpi.h> +#include <acpi/video.h> + +#include "asus-wmi.h" + +MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, " + "Yong Wang <yong.y.wang@intel.com>"); +MODULE_DESCRIPTION("Asus Generic WMI Driver"); +MODULE_LICENSE("GPL"); + +#define to_platform_driver(drv) \ + (container_of((drv), struct platform_driver, driver)) + +#define to_asus_wmi_driver(pdrv) \ + (container_of((pdrv), struct asus_wmi_driver, platform_driver)) + +#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" + +#define NOTIFY_BRNUP_MIN 0x11 +#define NOTIFY_BRNUP_MAX 0x1f +#define NOTIFY_BRNDOWN_MIN 0x20 +#define NOTIFY_BRNDOWN_MAX 0x2e +#define NOTIFY_KBD_BRTUP 0xc4 +#define NOTIFY_KBD_BRTDWN 0xc5 + +/* WMI Methods */ +#define ASUS_WMI_METHODID_SPEC 0x43455053 /* BIOS SPECification */ +#define ASUS_WMI_METHODID_SFBD 0x44424653 /* Set First Boot Device */ +#define ASUS_WMI_METHODID_GLCD 0x44434C47 /* Get LCD status */ +#define ASUS_WMI_METHODID_GPID 0x44495047 /* Get Panel ID?? (Resol) */ +#define ASUS_WMI_METHODID_QMOD 0x444F4D51 /* Quiet MODe */ +#define ASUS_WMI_METHODID_SPLV 0x4C425053 /* Set Panel Light Value */ +#define ASUS_WMI_METHODID_SFUN 0x4E554653 /* FUNCtionalities */ +#define ASUS_WMI_METHODID_SDSP 0x50534453 /* Set DiSPlay output */ +#define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */ +#define ASUS_WMI_METHODID_DEVP 0x50564544 /* DEVice Policy */ +#define ASUS_WMI_METHODID_OSVR 0x5256534F /* OS VeRsion */ +#define ASUS_WMI_METHODID_DSTS 0x53544344 /* Device STatuS */ +#define ASUS_WMI_METHODID_DSTS2 0x53545344 /* Device STatuS #2*/ +#define ASUS_WMI_METHODID_BSTS 0x53545342 /* Bios STatuS ? */ +#define ASUS_WMI_METHODID_DEVS 0x53564544 /* DEVice Set */ +#define ASUS_WMI_METHODID_CFVS 0x53564643 /* CPU Frequency Volt Set */ +#define ASUS_WMI_METHODID_KBFT 0x5446424B /* KeyBoard FilTer */ +#define ASUS_WMI_METHODID_INIT 0x54494E49 /* INITialize */ +#define ASUS_WMI_METHODID_HKEY 0x59454B48 /* Hot KEY ?? */ + +#define ASUS_WMI_UNSUPPORTED_METHOD 0xFFFFFFFE + +/* Wireless */ +#define ASUS_WMI_DEVID_HW_SWITCH 0x00010001 +#define ASUS_WMI_DEVID_WIRELESS_LED 0x00010002 +#define ASUS_WMI_DEVID_CWAP 0x00010003 +#define ASUS_WMI_DEVID_WLAN 0x00010011 +#define ASUS_WMI_DEVID_WLAN_LED 0x00010012 +#define ASUS_WMI_DEVID_BLUETOOTH 0x00010013 +#define ASUS_WMI_DEVID_GPS 0x00010015 +#define ASUS_WMI_DEVID_WIMAX 0x00010017 +#define ASUS_WMI_DEVID_WWAN3G 0x00010019 +#define ASUS_WMI_DEVID_UWB 0x00010021 + +/* Leds */ +/* 0x000200XX and 0x000400XX */ +#define ASUS_WMI_DEVID_LED1 0x00020011 +#define ASUS_WMI_DEVID_LED2 0x00020012 +#define ASUS_WMI_DEVID_LED3 0x00020013 +#define ASUS_WMI_DEVID_LED4 0x00020014 +#define ASUS_WMI_DEVID_LED5 0x00020015 +#define ASUS_WMI_DEVID_LED6 0x00020016 + +/* Backlight and Brightness */ +#define ASUS_WMI_DEVID_BACKLIGHT 0x00050011 +#define ASUS_WMI_DEVID_BRIGHTNESS 0x00050012 +#define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 +#define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* ?? */ + +/* Misc */ +#define ASUS_WMI_DEVID_CAMERA 0x00060013 + +/* Storage */ +#define ASUS_WMI_DEVID_CARDREADER 0x00080013 + +/* Input */ +#define ASUS_WMI_DEVID_TOUCHPAD 0x00100011 +#define ASUS_WMI_DEVID_TOUCHPAD_LED 0x00100012 + +/* Fan, Thermal */ +#define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011 +#define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 + +/* Power */ +#define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 + +/* Deep S3 / Resume on LID open */ +#define ASUS_WMI_DEVID_LID_RESUME 0x00120031 + +/* DSTS masks */ +#define ASUS_WMI_DSTS_STATUS_BIT 0x00000001 +#define ASUS_WMI_DSTS_UNKNOWN_BIT 0x00000002 +#define ASUS_WMI_DSTS_PRESENCE_BIT 0x00010000 +#define ASUS_WMI_DSTS_USER_BIT 0x00020000 +#define ASUS_WMI_DSTS_BIOS_BIT 0x00040000 +#define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF +#define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 + +struct bios_args { + u32 arg0; + u32 arg1; +} __packed; + +/* + * <platform>/ - debugfs root directory + * dev_id - current dev_id + * ctrl_param - current ctrl_param + * method_id - current method_id + * devs - call DEVS(dev_id, ctrl_param) and print result + * dsts - call DSTS(dev_id) and print result + * call - call method_id(dev_id, ctrl_param) and print result + */ +struct asus_wmi_debug { + struct dentry *root; + u32 method_id; + u32 dev_id; + u32 ctrl_param; +}; + +struct asus_rfkill { + struct asus_wmi *asus; + struct rfkill *rfkill; + u32 dev_id; +}; + +struct asus_wmi { + int dsts_id; + int spec; + int sfun; + + struct input_dev *inputdev; + struct backlight_device *backlight_device; + struct platform_device *platform_device; + + struct led_classdev wlan_led; + int wlan_led_wk; + struct led_classdev tpd_led; + int tpd_led_wk; + struct led_classdev kbd_led; + int kbd_led_wk; + struct workqueue_struct *led_workqueue; + struct work_struct tpd_led_work; + struct work_struct kbd_led_work; + struct work_struct wlan_led_work; + + struct asus_rfkill wlan; + struct asus_rfkill bluetooth; + struct asus_rfkill wimax; + struct asus_rfkill wwan3g; + struct asus_rfkill gps; + struct asus_rfkill uwb; + + struct hotplug_slot *hotplug_slot; + struct mutex hotplug_lock; + struct mutex wmi_lock; + struct workqueue_struct *hotplug_workqueue; + struct work_struct hotplug_work; + + struct asus_wmi_debug debug; + + struct asus_wmi_driver *driver; +}; + +static int asus_wmi_input_init(struct asus_wmi *asus) +{ + int err; + + asus->inputdev = input_allocate_device(); + if (!asus->inputdev) + return -ENOMEM; + + asus->inputdev->name = asus->driver->input_name; + asus->inputdev->phys = asus->driver->input_phys; + asus->inputdev->id.bustype = BUS_HOST; + asus->inputdev->dev.parent = &asus->platform_device->dev; + set_bit(EV_REP, asus->inputdev->evbit); + + err = sparse_keymap_setup(asus->inputdev, asus->driver->keymap, NULL); + if (err) + goto err_free_dev; + + err = input_register_device(asus->inputdev); + if (err) + goto err_free_keymap; + + return 0; + +err_free_keymap: + sparse_keymap_free(asus->inputdev); +err_free_dev: + input_free_device(asus->inputdev); + return err; +} + +static void asus_wmi_input_exit(struct asus_wmi *asus) +{ + if (asus->inputdev) { + sparse_keymap_free(asus->inputdev); + input_unregister_device(asus->inputdev); + } + + asus->inputdev = NULL; +} + +static int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, + u32 *retval) +{ + struct bios_args args = { + .arg0 = arg0, + .arg1 = arg1, + }; + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + union acpi_object *obj; + u32 tmp = 0; + + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 1, method_id, + &input, &output); + + if (ACPI_FAILURE(status)) + goto exit; + + obj = (union acpi_object *)output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + tmp = (u32) obj->integer.value; + + if (retval) + *retval = tmp; + + kfree(obj); + +exit: + if (ACPI_FAILURE(status)) + return -EIO; + + if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) + return -ENODEV; + + return 0; +} + +static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval) +{ + return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval); +} + +static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, + u32 *retval) +{ + return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id, + ctrl_param, retval); +} + +/* Helper for special devices with magic return codes */ +static int asus_wmi_get_devstate_bits(struct asus_wmi *asus, + u32 dev_id, u32 mask) +{ + u32 retval = 0; + int err; + + err = asus_wmi_get_devstate(asus, dev_id, &retval); + + if (err < 0) + return err; + + if (!(retval & ASUS_WMI_DSTS_PRESENCE_BIT)) + return -ENODEV; + + if (mask == ASUS_WMI_DSTS_STATUS_BIT) { + if (retval & ASUS_WMI_DSTS_UNKNOWN_BIT) + return -ENODEV; + } + + return retval & mask; +} + +static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id) +{ + return asus_wmi_get_devstate_bits(asus, dev_id, + ASUS_WMI_DSTS_STATUS_BIT); +} + +/* + * LEDs + */ +/* + * These functions actually update the LED's, and are called from a + * workqueue. By doing this as separate work rather than when the LED + * subsystem asks, we avoid messing with the Asus ACPI stuff during a + * potentially bad time, such as a timer interrupt. + */ +static void tpd_led_update(struct work_struct *work) +{ + int ctrl_param; + struct asus_wmi *asus; + + asus = container_of(work, struct asus_wmi, tpd_led_work); + + ctrl_param = asus->tpd_led_wk; + asus_wmi_set_devstate(ASUS_WMI_DEVID_TOUCHPAD_LED, ctrl_param, NULL); +} + +static void tpd_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct asus_wmi *asus; + + asus = container_of(led_cdev, struct asus_wmi, tpd_led); + + asus->tpd_led_wk = !!value; + queue_work(asus->led_workqueue, &asus->tpd_led_work); +} + +static int read_tpd_led_state(struct asus_wmi *asus) +{ + return asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_TOUCHPAD_LED); +} + +static enum led_brightness tpd_led_get(struct led_classdev *led_cdev) +{ + struct asus_wmi *asus; + + asus = container_of(led_cdev, struct asus_wmi, tpd_led); + + return read_tpd_led_state(asus); +} + +static void kbd_led_update(struct work_struct *work) +{ + int ctrl_param = 0; + struct asus_wmi *asus; + + asus = container_of(work, struct asus_wmi, kbd_led_work); + + /* + * bits 0-2: level + * bit 7: light on/off + */ + if (asus->kbd_led_wk > 0) + ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F); + + asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL); +} + +static int kbd_led_read(struct asus_wmi *asus, int *level, int *env) +{ + int retval; + + /* + * bits 0-2: level + * bit 7: light on/off + * bit 8-10: environment (0: dark, 1: normal, 2: light) + * bit 17: status unknown + */ + retval = asus_wmi_get_devstate_bits(asus, ASUS_WMI_DEVID_KBD_BACKLIGHT, + 0xFFFF); + + /* Unknown status is considered as off */ + if (retval == 0x8000) + retval = 0; + + if (retval >= 0) { + if (level) + *level = retval & 0x7F; + if (env) + *env = (retval >> 8) & 0x7F; + retval = 0; + } + + return retval; +} + +static void kbd_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct asus_wmi *asus; + + asus = container_of(led_cdev, struct asus_wmi, kbd_led); + + if (value > asus->kbd_led.max_brightness) + value = asus->kbd_led.max_brightness; + else if (value < 0) + value = 0; + + asus->kbd_led_wk = value; + queue_work(asus->led_workqueue, &asus->kbd_led_work); +} + +static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) +{ + struct asus_wmi *asus; + int retval, value; + + asus = container_of(led_cdev, struct asus_wmi, kbd_led); + + retval = kbd_led_read(asus, &value, NULL); + + if (retval < 0) + return retval; + + return value; +} + +static int wlan_led_unknown_state(struct asus_wmi *asus) +{ + u32 result; + + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result); + + return result & ASUS_WMI_DSTS_UNKNOWN_BIT; +} + +static int wlan_led_presence(struct asus_wmi *asus) +{ + u32 result; + + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result); + + return result & ASUS_WMI_DSTS_PRESENCE_BIT; +} + +static void wlan_led_update(struct work_struct *work) +{ + int ctrl_param; + struct asus_wmi *asus; + + asus = container_of(work, struct asus_wmi, wlan_led_work); + + ctrl_param = asus->wlan_led_wk; + asus_wmi_set_devstate(ASUS_WMI_DEVID_WIRELESS_LED, ctrl_param, NULL); +} + +static void wlan_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct asus_wmi *asus; + + asus = container_of(led_cdev, struct asus_wmi, wlan_led); + + asus->wlan_led_wk = !!value; + queue_work(asus->led_workqueue, &asus->wlan_led_work); +} + +static enum led_brightness wlan_led_get(struct led_classdev *led_cdev) +{ + struct asus_wmi *asus; + u32 result; + + asus = container_of(led_cdev, struct asus_wmi, wlan_led); + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result); + + return result & ASUS_WMI_DSTS_BRIGHTNESS_MASK; +} + +static void asus_wmi_led_exit(struct asus_wmi *asus) +{ + if (!IS_ERR_OR_NULL(asus->kbd_led.dev)) + led_classdev_unregister(&asus->kbd_led); + if (!IS_ERR_OR_NULL(asus->tpd_led.dev)) + led_classdev_unregister(&asus->tpd_led); + if (!IS_ERR_OR_NULL(asus->wlan_led.dev)) + led_classdev_unregister(&asus->wlan_led); + if (asus->led_workqueue) + destroy_workqueue(asus->led_workqueue); +} + +static int asus_wmi_led_init(struct asus_wmi *asus) +{ + int rv = 0; + + asus->led_workqueue = create_singlethread_workqueue("led_workqueue"); + if (!asus->led_workqueue) + return -ENOMEM; + + if (read_tpd_led_state(asus) >= 0) { + INIT_WORK(&asus->tpd_led_work, tpd_led_update); + + asus->tpd_led.name = "asus::touchpad"; + asus->tpd_led.brightness_set = tpd_led_set; + asus->tpd_led.brightness_get = tpd_led_get; + asus->tpd_led.max_brightness = 1; + + rv = led_classdev_register(&asus->platform_device->dev, + &asus->tpd_led); + if (rv) + goto error; + } + + if (kbd_led_read(asus, NULL, NULL) >= 0) { + INIT_WORK(&asus->kbd_led_work, kbd_led_update); + + asus->kbd_led.name = "asus::kbd_backlight"; + asus->kbd_led.brightness_set = kbd_led_set; + asus->kbd_led.brightness_get = kbd_led_get; + asus->kbd_led.max_brightness = 3; + + rv = led_classdev_register(&asus->platform_device->dev, + &asus->kbd_led); + if (rv) + goto error; + } + + if (wlan_led_presence(asus) && (asus->driver->quirks->wapf == 4)) { + INIT_WORK(&asus->wlan_led_work, wlan_led_update); + + asus->wlan_led.name = "asus::wlan"; + asus->wlan_led.brightness_set = wlan_led_set; + if (!wlan_led_unknown_state(asus)) + asus->wlan_led.brightness_get = wlan_led_get; + asus->wlan_led.flags = LED_CORE_SUSPENDRESUME; + asus->wlan_led.max_brightness = 1; + asus->wlan_led.default_trigger = "asus-wlan"; + + rv = led_classdev_register(&asus->platform_device->dev, + &asus->wlan_led); + } + +error: + if (rv) + asus_wmi_led_exit(asus); + + return rv; +} + + +/* + * PCI hotplug (for wlan rfkill) + */ +static bool asus_wlan_rfkill_blocked(struct asus_wmi *asus) +{ + int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN); + + if (result < 0) + return false; + return !result; +} + +static void asus_rfkill_hotplug(struct asus_wmi *asus) +{ + struct pci_dev *dev; + struct pci_bus *bus; + bool blocked; + bool absent; + u32 l; + + mutex_lock(&asus->wmi_lock); + blocked = asus_wlan_rfkill_blocked(asus); + mutex_unlock(&asus->wmi_lock); + + mutex_lock(&asus->hotplug_lock); + pci_lock_rescan_remove(); + + if (asus->wlan.rfkill) + rfkill_set_sw_state(asus->wlan.rfkill, blocked); + + if (asus->hotplug_slot) { + bus = pci_find_bus(0, 1); + if (!bus) { + pr_warn("Unable to find PCI bus 1?\n"); + goto out_unlock; + } + + if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) { + pr_err("Unable to read PCI config space?\n"); + goto out_unlock; + } + absent = (l == 0xffffffff); + + if (blocked != absent) { + pr_warn("BIOS says wireless lan is %s, " + "but the pci device is %s\n", + blocked ? "blocked" : "unblocked", + absent ? "absent" : "present"); + pr_warn("skipped wireless hotplug as probably " + "inappropriate for this model\n"); + goto out_unlock; + } + + if (!blocked) { + dev = pci_get_slot(bus, 0); + if (dev) { + /* Device already present */ + pci_dev_put(dev); + goto out_unlock; + } + dev = pci_scan_single_device(bus, 0); + if (dev) { + pci_bus_assign_resources(bus); + pci_bus_add_device(dev); + } + } else { + dev = pci_get_slot(bus, 0); + if (dev) { + pci_stop_and_remove_bus_device(dev); + pci_dev_put(dev); + } + } + } + +out_unlock: + pci_unlock_rescan_remove(); + mutex_unlock(&asus->hotplug_lock); +} + +static void asus_rfkill_notify(acpi_handle handle, u32 event, void *data) +{ + struct asus_wmi *asus = data; + + if (event != ACPI_NOTIFY_BUS_CHECK) + return; + + /* + * We can't call directly asus_rfkill_hotplug because most + * of the time WMBC is still being executed and not reetrant. + * There is currently no way to tell ACPICA that we want this + * method to be serialized, we schedule a asus_rfkill_hotplug + * call later, in a safer context. + */ + queue_work(asus->hotplug_workqueue, &asus->hotplug_work); +} + +static int asus_register_rfkill_notifier(struct asus_wmi *asus, char *node) +{ + acpi_status status; + acpi_handle handle; + + status = acpi_get_handle(NULL, node, &handle); + + if (ACPI_SUCCESS(status)) { + status = acpi_install_notify_handler(handle, + ACPI_SYSTEM_NOTIFY, + asus_rfkill_notify, asus); + if (ACPI_FAILURE(status)) + pr_warn("Failed to register notify on %s\n", node); + } else + return -ENODEV; + + return 0; +} + +static void asus_unregister_rfkill_notifier(struct asus_wmi *asus, char *node) +{ + acpi_status status = AE_OK; + acpi_handle handle; + + status = acpi_get_handle(NULL, node, &handle); + + if (ACPI_SUCCESS(status)) { + status = acpi_remove_notify_handler(handle, + ACPI_SYSTEM_NOTIFY, + asus_rfkill_notify); + if (ACPI_FAILURE(status)) + pr_err("Error removing rfkill notify handler %s\n", + node); + } +} + +static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot, + u8 *value) +{ + struct asus_wmi *asus = hotplug_slot->private; + int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN); + + if (result < 0) + return result; + + *value = !!result; + return 0; +} + +static void asus_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot) +{ + kfree(hotplug_slot->info); + kfree(hotplug_slot); +} + +static struct hotplug_slot_ops asus_hotplug_slot_ops = { + .owner = THIS_MODULE, + .get_adapter_status = asus_get_adapter_status, + .get_power_status = asus_get_adapter_status, +}; + +static void asus_hotplug_work(struct work_struct *work) +{ + struct asus_wmi *asus; + + asus = container_of(work, struct asus_wmi, hotplug_work); + asus_rfkill_hotplug(asus); +} + +static int asus_setup_pci_hotplug(struct asus_wmi *asus) +{ + int ret = -ENOMEM; + struct pci_bus *bus = pci_find_bus(0, 1); + + if (!bus) { + pr_err("Unable to find wifi PCI bus\n"); + return -ENODEV; + } + + asus->hotplug_workqueue = + create_singlethread_workqueue("hotplug_workqueue"); + if (!asus->hotplug_workqueue) + goto error_workqueue; + + INIT_WORK(&asus->hotplug_work, asus_hotplug_work); + + asus->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); + if (!asus->hotplug_slot) + goto error_slot; + + asus->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info), + GFP_KERNEL); + if (!asus->hotplug_slot->info) + goto error_info; + + asus->hotplug_slot->private = asus; + asus->hotplug_slot->release = &asus_cleanup_pci_hotplug; + asus->hotplug_slot->ops = &asus_hotplug_slot_ops; + asus_get_adapter_status(asus->hotplug_slot, + &asus->hotplug_slot->info->adapter_status); + + ret = pci_hp_register(asus->hotplug_slot, bus, 0, "asus-wifi"); + if (ret) { + pr_err("Unable to register hotplug slot - %d\n", ret); + goto error_register; + } + + return 0; + +error_register: + kfree(asus->hotplug_slot->info); +error_info: + kfree(asus->hotplug_slot); + asus->hotplug_slot = NULL; +error_slot: + destroy_workqueue(asus->hotplug_workqueue); +error_workqueue: + return ret; +} + +/* + * Rfkill devices + */ +static int asus_rfkill_set(void *data, bool blocked) +{ + struct asus_rfkill *priv = data; + u32 ctrl_param = !blocked; + u32 dev_id = priv->dev_id; + + /* + * If the user bit is set, BIOS can't set and record the wlan status, + * it will report the value read from id ASUS_WMI_DEVID_WLAN_LED + * while we query the wlan status through WMI(ASUS_WMI_DEVID_WLAN). + * So, we have to record wlan status in id ASUS_WMI_DEVID_WLAN_LED + * while setting the wlan status through WMI. + * This is also the behavior that windows app will do. + */ + if ((dev_id == ASUS_WMI_DEVID_WLAN) && + priv->asus->driver->wlan_ctrl_by_user) + dev_id = ASUS_WMI_DEVID_WLAN_LED; + + return asus_wmi_set_devstate(dev_id, ctrl_param, NULL); +} + +static void asus_rfkill_query(struct rfkill *rfkill, void *data) +{ + struct asus_rfkill *priv = data; + int result; + + result = asus_wmi_get_devstate_simple(priv->asus, priv->dev_id); + + if (result < 0) + return; + + rfkill_set_sw_state(priv->rfkill, !result); +} + +static int asus_rfkill_wlan_set(void *data, bool blocked) +{ + struct asus_rfkill *priv = data; + struct asus_wmi *asus = priv->asus; + int ret; + + /* + * This handler is enabled only if hotplug is enabled. + * In this case, the asus_wmi_set_devstate() will + * trigger a wmi notification and we need to wait + * this call to finish before being able to call + * any wmi method + */ + mutex_lock(&asus->wmi_lock); + ret = asus_rfkill_set(data, blocked); + mutex_unlock(&asus->wmi_lock); + return ret; +} + +static const struct rfkill_ops asus_rfkill_wlan_ops = { + .set_block = asus_rfkill_wlan_set, + .query = asus_rfkill_query, +}; + +static const struct rfkill_ops asus_rfkill_ops = { + .set_block = asus_rfkill_set, + .query = asus_rfkill_query, +}; + +static int asus_new_rfkill(struct asus_wmi *asus, + struct asus_rfkill *arfkill, + const char *name, enum rfkill_type type, int dev_id) +{ + int result = asus_wmi_get_devstate_simple(asus, dev_id); + struct rfkill **rfkill = &arfkill->rfkill; + + if (result < 0) + return result; + + arfkill->dev_id = dev_id; + arfkill->asus = asus; + + if (dev_id == ASUS_WMI_DEVID_WLAN && + asus->driver->quirks->hotplug_wireless) + *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, + &asus_rfkill_wlan_ops, arfkill); + else + *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, + &asus_rfkill_ops, arfkill); + + if (!*rfkill) + return -EINVAL; + + if ((dev_id == ASUS_WMI_DEVID_WLAN) && + (asus->driver->quirks->wapf == 4)) + rfkill_set_led_trigger_name(*rfkill, "asus-wlan"); + + rfkill_init_sw_state(*rfkill, !result); + result = rfkill_register(*rfkill); + if (result) { + rfkill_destroy(*rfkill); + *rfkill = NULL; + return result; + } + return 0; +} + +static void asus_wmi_rfkill_exit(struct asus_wmi *asus) +{ + asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P5"); + asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P6"); + asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P7"); + if (asus->wlan.rfkill) { + rfkill_unregister(asus->wlan.rfkill); + rfkill_destroy(asus->wlan.rfkill); + asus->wlan.rfkill = NULL; + } + /* + * Refresh pci hotplug in case the rfkill state was changed after + * asus_unregister_rfkill_notifier() + */ + asus_rfkill_hotplug(asus); + if (asus->hotplug_slot) + pci_hp_deregister(asus->hotplug_slot); + if (asus->hotplug_workqueue) + destroy_workqueue(asus->hotplug_workqueue); + + if (asus->bluetooth.rfkill) { + rfkill_unregister(asus->bluetooth.rfkill); + rfkill_destroy(asus->bluetooth.rfkill); + asus->bluetooth.rfkill = NULL; + } + if (asus->wimax.rfkill) { + rfkill_unregister(asus->wimax.rfkill); + rfkill_destroy(asus->wimax.rfkill); + asus->wimax.rfkill = NULL; + } + if (asus->wwan3g.rfkill) { + rfkill_unregister(asus->wwan3g.rfkill); + rfkill_destroy(asus->wwan3g.rfkill); + asus->wwan3g.rfkill = NULL; + } + if (asus->gps.rfkill) { + rfkill_unregister(asus->gps.rfkill); + rfkill_destroy(asus->gps.rfkill); + asus->gps.rfkill = NULL; + } + if (asus->uwb.rfkill) { + rfkill_unregister(asus->uwb.rfkill); + rfkill_destroy(asus->uwb.rfkill); + asus->uwb.rfkill = NULL; + } +} + +static int asus_wmi_rfkill_init(struct asus_wmi *asus) +{ + int result = 0; + + mutex_init(&asus->hotplug_lock); + mutex_init(&asus->wmi_lock); + + result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan", + RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN); + + if (result && result != -ENODEV) + goto exit; + + result = asus_new_rfkill(asus, &asus->bluetooth, + "asus-bluetooth", RFKILL_TYPE_BLUETOOTH, + ASUS_WMI_DEVID_BLUETOOTH); + + if (result && result != -ENODEV) + goto exit; + + result = asus_new_rfkill(asus, &asus->wimax, "asus-wimax", + RFKILL_TYPE_WIMAX, ASUS_WMI_DEVID_WIMAX); + + if (result && result != -ENODEV) + goto exit; + + result = asus_new_rfkill(asus, &asus->wwan3g, "asus-wwan3g", + RFKILL_TYPE_WWAN, ASUS_WMI_DEVID_WWAN3G); + + if (result && result != -ENODEV) + goto exit; + + result = asus_new_rfkill(asus, &asus->gps, "asus-gps", + RFKILL_TYPE_GPS, ASUS_WMI_DEVID_GPS); + + if (result && result != -ENODEV) + goto exit; + + result = asus_new_rfkill(asus, &asus->uwb, "asus-uwb", + RFKILL_TYPE_UWB, ASUS_WMI_DEVID_UWB); + + if (result && result != -ENODEV) + goto exit; + + if (!asus->driver->quirks->hotplug_wireless) + goto exit; + + result = asus_setup_pci_hotplug(asus); + /* + * If we get -EBUSY then something else is handling the PCI hotplug - + * don't fail in this case + */ + if (result == -EBUSY) + result = 0; + + asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P5"); + asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P6"); + asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P7"); + /* + * Refresh pci hotplug in case the rfkill state was changed during + * setup. + */ + asus_rfkill_hotplug(asus); + +exit: + if (result && result != -ENODEV) + asus_wmi_rfkill_exit(asus); + + if (result == -ENODEV) + result = 0; + + return result; +} + +/* + * Hwmon device + */ +static ssize_t asus_hwmon_pwm1(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + u32 value; + int err; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value); + + if (err < 0) + return err; + + value &= 0xFF; + + if (value == 1) /* Low Speed */ + value = 85; + else if (value == 2) + value = 170; + else if (value == 3) + value = 255; + else if (value != 0) { + pr_err("Unknown fan speed %#x\n", value); + value = -1; + } + + return sprintf(buf, "%d\n", value); +} + +static ssize_t asus_hwmon_temp1(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + u32 value; + int err; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_THERMAL_CTRL, &value); + + if (err < 0) + return err; + + value = KELVIN_TO_CELSIUS((value & 0xFFFF)) * 1000; + + return sprintf(buf, "%d\n", value); +} + +static DEVICE_ATTR(pwm1, S_IRUGO, asus_hwmon_pwm1, NULL); +static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL); + +static struct attribute *hwmon_attributes[] = { + &dev_attr_pwm1.attr, + &dev_attr_temp1_input.attr, + NULL +}; + +static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct platform_device *pdev = to_platform_device(dev->parent); + struct asus_wmi *asus = platform_get_drvdata(pdev); + bool ok = true; + int dev_id = -1; + u32 value = ASUS_WMI_UNSUPPORTED_METHOD; + + if (attr == &dev_attr_pwm1.attr) + dev_id = ASUS_WMI_DEVID_FAN_CTRL; + else if (attr == &dev_attr_temp1_input.attr) + dev_id = ASUS_WMI_DEVID_THERMAL_CTRL; + + if (dev_id != -1) { + int err = asus_wmi_get_devstate(asus, dev_id, &value); + + if (err < 0) + return 0; /* can't return negative here */ + } + + if (dev_id == ASUS_WMI_DEVID_FAN_CTRL) { + /* + * We need to find a better way, probably using sfun, + * bits or spec ... + * Currently we disable it if: + * - ASUS_WMI_UNSUPPORTED_METHOD is returned + * - reverved bits are non-zero + * - sfun and presence bit are not set + */ + if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000 + || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT))) + ok = false; + } else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) { + /* If value is zero, something is clearly wrong */ + if (value == 0) + ok = false; + } + + return ok ? attr->mode : 0; +} + +static struct attribute_group hwmon_attribute_group = { + .is_visible = asus_hwmon_sysfs_is_visible, + .attrs = hwmon_attributes +}; +__ATTRIBUTE_GROUPS(hwmon_attribute); + +static int asus_wmi_hwmon_init(struct asus_wmi *asus) +{ + struct device *hwmon; + + hwmon = hwmon_device_register_with_groups(&asus->platform_device->dev, + "asus", asus, + hwmon_attribute_groups); + if (IS_ERR(hwmon)) { + pr_err("Could not register asus hwmon device\n"); + return PTR_ERR(hwmon); + } + return 0; +} + +/* + * Backlight + */ +static int read_backlight_power(struct asus_wmi *asus) +{ + int ret; + if (asus->driver->quirks->store_backlight_power) + ret = !asus->driver->panel_power; + else + ret = asus_wmi_get_devstate_simple(asus, + ASUS_WMI_DEVID_BACKLIGHT); + + if (ret < 0) + return ret; + + return ret ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; +} + +static int read_brightness_max(struct asus_wmi *asus) +{ + u32 retval; + int err; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval); + + if (err < 0) + return err; + + retval = retval & ASUS_WMI_DSTS_MAX_BRIGTH_MASK; + retval >>= 8; + + if (!retval) + return -ENODEV; + + return retval; +} + +static int read_brightness(struct backlight_device *bd) +{ + struct asus_wmi *asus = bl_get_data(bd); + u32 retval; + int err; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval); + + if (err < 0) + return err; + + return retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK; +} + +static u32 get_scalar_command(struct backlight_device *bd) +{ + struct asus_wmi *asus = bl_get_data(bd); + u32 ctrl_param = 0; + + if ((asus->driver->brightness < bd->props.brightness) || + bd->props.brightness == bd->props.max_brightness) + ctrl_param = 0x00008001; + else if ((asus->driver->brightness > bd->props.brightness) || + bd->props.brightness == 0) + ctrl_param = 0x00008000; + + asus->driver->brightness = bd->props.brightness; + + return ctrl_param; +} + +static int update_bl_status(struct backlight_device *bd) +{ + struct asus_wmi *asus = bl_get_data(bd); + u32 ctrl_param; + int power, err = 0; + + power = read_backlight_power(asus); + if (power != -ENODEV && bd->props.power != power) { + ctrl_param = !!(bd->props.power == FB_BLANK_UNBLANK); + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, + ctrl_param, NULL); + if (asus->driver->quirks->store_backlight_power) + asus->driver->panel_power = bd->props.power; + + /* When using scalar brightness, updating the brightness + * will mess with the backlight power */ + if (asus->driver->quirks->scalar_panel_brightness) + return err; + } + + if (asus->driver->quirks->scalar_panel_brightness) + ctrl_param = get_scalar_command(bd); + else + ctrl_param = bd->props.brightness; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BRIGHTNESS, + ctrl_param, NULL); + + return err; +} + +static const struct backlight_ops asus_wmi_bl_ops = { + .get_brightness = read_brightness, + .update_status = update_bl_status, +}; + +static int asus_wmi_backlight_notify(struct asus_wmi *asus, int code) +{ + struct backlight_device *bd = asus->backlight_device; + int old = bd->props.brightness; + int new = old; + + if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) + new = code - NOTIFY_BRNUP_MIN + 1; + else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX) + new = code - NOTIFY_BRNDOWN_MIN; + + bd->props.brightness = new; + backlight_update_status(bd); + backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY); + + return old; +} + +static int asus_wmi_backlight_init(struct asus_wmi *asus) +{ + struct backlight_device *bd; + struct backlight_properties props; + int max; + int power; + + max = read_brightness_max(asus); + + if (max == -ENODEV) + max = 0; + else if (max < 0) + return max; + + power = read_backlight_power(asus); + + if (power == -ENODEV) + power = FB_BLANK_UNBLANK; + else if (power < 0) + return power; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = max; + bd = backlight_device_register(asus->driver->name, + &asus->platform_device->dev, asus, + &asus_wmi_bl_ops, &props); + if (IS_ERR(bd)) { + pr_err("Could not register backlight device\n"); + return PTR_ERR(bd); + } + + asus->backlight_device = bd; + + if (asus->driver->quirks->store_backlight_power) + asus->driver->panel_power = power; + + bd->props.brightness = read_brightness(bd); + bd->props.power = power; + backlight_update_status(bd); + + asus->driver->brightness = bd->props.brightness; + + return 0; +} + +static void asus_wmi_backlight_exit(struct asus_wmi *asus) +{ + if (asus->backlight_device) + backlight_device_unregister(asus->backlight_device); + + asus->backlight_device = NULL; +} + +static int is_display_toggle(int code) +{ + /* display toggle keys */ + if ((code >= 0x61 && code <= 0x67) || + (code >= 0x8c && code <= 0x93) || + (code >= 0xa0 && code <= 0xa7) || + (code >= 0xd0 && code <= 0xd5)) + return 1; + + return 0; +} + +static void asus_wmi_notify(u32 value, void *context) +{ + struct asus_wmi *asus = context; + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + int code; + int orig_code; + unsigned int key_value = 1; + bool autorelease = 1; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_err("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + + if (!obj || obj->type != ACPI_TYPE_INTEGER) + goto exit; + + code = obj->integer.value; + orig_code = code; + + if (asus->driver->key_filter) { + asus->driver->key_filter(asus->driver, &code, &key_value, + &autorelease); + if (code == ASUS_WMI_KEY_IGNORE) + goto exit; + } + + if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) + code = ASUS_WMI_BRN_UP; + else if (code >= NOTIFY_BRNDOWN_MIN && + code <= NOTIFY_BRNDOWN_MAX) + code = ASUS_WMI_BRN_DOWN; + + if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) { + if (!acpi_video_backlight_support()) { + asus_wmi_backlight_notify(asus, orig_code); + goto exit; + } + } + + if (is_display_toggle(code) && + asus->driver->quirks->no_display_toggle) + goto exit; + + if (!sparse_keymap_report_event(asus->inputdev, code, + key_value, autorelease)) + pr_info("Unknown key %x pressed\n", code); + +exit: + kfree(obj); +} + +/* + * Sys helpers + */ +static int parse_arg(const char *buf, unsigned long count, int *val) +{ + if (!count) + return 0; + if (sscanf(buf, "%i", val) != 1) + return -EINVAL; + return count; +} + +static ssize_t store_sys_wmi(struct asus_wmi *asus, int devid, + const char *buf, size_t count) +{ + u32 retval; + int rv, err, value; + + value = asus_wmi_get_devstate_simple(asus, devid); + if (value == -ENODEV) /* Check device presence */ + return value; + + rv = parse_arg(buf, count, &value); + err = asus_wmi_set_devstate(devid, value, &retval); + + if (err < 0) + return err; + + return rv; +} + +static ssize_t show_sys_wmi(struct asus_wmi *asus, int devid, char *buf) +{ + int value = asus_wmi_get_devstate_simple(asus, devid); + + if (value < 0) + return value; + + return sprintf(buf, "%d\n", value); +} + +#define ASUS_WMI_CREATE_DEVICE_ATTR(_name, _mode, _cm) \ + static ssize_t show_##_name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ + { \ + struct asus_wmi *asus = dev_get_drvdata(dev); \ + \ + return show_sys_wmi(asus, _cm, buf); \ + } \ + static ssize_t store_##_name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct asus_wmi *asus = dev_get_drvdata(dev); \ + \ + return store_sys_wmi(asus, _cm, buf, count); \ + } \ + static struct device_attribute dev_attr_##_name = { \ + .attr = { \ + .name = __stringify(_name), \ + .mode = _mode }, \ + .show = show_##_name, \ + .store = store_##_name, \ + } + +ASUS_WMI_CREATE_DEVICE_ATTR(touchpad, 0644, ASUS_WMI_DEVID_TOUCHPAD); +ASUS_WMI_CREATE_DEVICE_ATTR(camera, 0644, ASUS_WMI_DEVID_CAMERA); +ASUS_WMI_CREATE_DEVICE_ATTR(cardr, 0644, ASUS_WMI_DEVID_CARDREADER); +ASUS_WMI_CREATE_DEVICE_ATTR(lid_resume, 0644, ASUS_WMI_DEVID_LID_RESUME); + +static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int value, rv; + + if (!count || sscanf(buf, "%i", &value) != 1) + return -EINVAL; + if (value < 0 || value > 2) + return -EINVAL; + + rv = asus_wmi_evaluate_method(ASUS_WMI_METHODID_CFVS, value, 0, NULL); + if (rv < 0) + return rv; + + return count; +} + +static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv); + +static struct attribute *platform_attributes[] = { + &dev_attr_cpufv.attr, + &dev_attr_camera.attr, + &dev_attr_cardr.attr, + &dev_attr_touchpad.attr, + &dev_attr_lid_resume.attr, + NULL +}; + +static umode_t asus_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct platform_device *pdev = to_platform_device(dev); + struct asus_wmi *asus = platform_get_drvdata(pdev); + bool ok = true; + int devid = -1; + + if (attr == &dev_attr_camera.attr) + devid = ASUS_WMI_DEVID_CAMERA; + else if (attr == &dev_attr_cardr.attr) + devid = ASUS_WMI_DEVID_CARDREADER; + else if (attr == &dev_attr_touchpad.attr) + devid = ASUS_WMI_DEVID_TOUCHPAD; + else if (attr == &dev_attr_lid_resume.attr) + devid = ASUS_WMI_DEVID_LID_RESUME; + + if (devid != -1) + ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); + + return ok ? attr->mode : 0; +} + +static struct attribute_group platform_attribute_group = { + .is_visible = asus_sysfs_is_visible, + .attrs = platform_attributes +}; + +static void asus_wmi_sysfs_exit(struct platform_device *device) +{ + sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); +} + +static int asus_wmi_sysfs_init(struct platform_device *device) +{ + return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); +} + +/* + * Platform device + */ +static int asus_wmi_platform_init(struct asus_wmi *asus) +{ + int rv; + + /* INIT enable hotkeys on some models */ + if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_INIT, 0, 0, &rv)) + pr_info("Initialization: %#x\n", rv); + + /* We don't know yet what to do with this version... */ + if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SPEC, 0, 0x9, &rv)) { + pr_info("BIOS WMI version: %d.%d\n", rv >> 16, rv & 0xFF); + asus->spec = rv; + } + + /* + * The SFUN method probably allows the original driver to get the list + * of features supported by a given model. For now, 0x0100 or 0x0800 + * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. + * The significance of others is yet to be found. + */ + if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SFUN, 0, 0, &rv)) { + pr_info("SFUN value: %#x\n", rv); + asus->sfun = rv; + } + + /* + * Eee PC and Notebooks seems to have different method_id for DSTS, + * but it may also be related to the BIOS's SPEC. + * Note, on most Eeepc, there is no way to check if a method exist + * or note, while on notebooks, they returns 0xFFFFFFFE on failure, + * but once again, SPEC may probably be used for that kind of things. + */ + if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL)) + asus->dsts_id = ASUS_WMI_METHODID_DSTS; + else + asus->dsts_id = ASUS_WMI_METHODID_DSTS2; + + /* CWAP allow to define the behavior of the Fn+F2 key, + * this method doesn't seems to be present on Eee PCs */ + if (asus->driver->quirks->wapf >= 0) + asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP, + asus->driver->quirks->wapf, NULL); + + return asus_wmi_sysfs_init(asus->platform_device); +} + +static void asus_wmi_platform_exit(struct asus_wmi *asus) +{ + asus_wmi_sysfs_exit(asus->platform_device); +} + +/* + * debugfs + */ +struct asus_wmi_debugfs_node { + struct asus_wmi *asus; + char *name; + int (*show) (struct seq_file *m, void *data); +}; + +static int show_dsts(struct seq_file *m, void *data) +{ + struct asus_wmi *asus = m->private; + int err; + u32 retval = -1; + + err = asus_wmi_get_devstate(asus, asus->debug.dev_id, &retval); + + if (err < 0) + return err; + + seq_printf(m, "DSTS(%#x) = %#x\n", asus->debug.dev_id, retval); + + return 0; +} + +static int show_devs(struct seq_file *m, void *data) +{ + struct asus_wmi *asus = m->private; + int err; + u32 retval = -1; + + err = asus_wmi_set_devstate(asus->debug.dev_id, asus->debug.ctrl_param, + &retval); + + if (err < 0) + return err; + + seq_printf(m, "DEVS(%#x, %#x) = %#x\n", asus->debug.dev_id, + asus->debug.ctrl_param, retval); + + return 0; +} + +static int show_call(struct seq_file *m, void *data) +{ + struct asus_wmi *asus = m->private; + struct bios_args args = { + .arg0 = asus->debug.dev_id, + .arg1 = asus->debug.ctrl_param, + }; + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, + 1, asus->debug.method_id, + &input, &output); + + if (ACPI_FAILURE(status)) + return -EIO; + + obj = (union acpi_object *)output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + seq_printf(m, "%#x(%#x, %#x) = %#x\n", asus->debug.method_id, + asus->debug.dev_id, asus->debug.ctrl_param, + (u32) obj->integer.value); + else + seq_printf(m, "%#x(%#x, %#x) = t:%d\n", asus->debug.method_id, + asus->debug.dev_id, asus->debug.ctrl_param, + obj ? obj->type : -1); + + kfree(obj); + + return 0; +} + +static struct asus_wmi_debugfs_node asus_wmi_debug_files[] = { + {NULL, "devs", show_devs}, + {NULL, "dsts", show_dsts}, + {NULL, "call", show_call}, +}; + +static int asus_wmi_debugfs_open(struct inode *inode, struct file *file) +{ + struct asus_wmi_debugfs_node *node = inode->i_private; + + return single_open(file, node->show, node->asus); +} + +static const struct file_operations asus_wmi_debugfs_io_ops = { + .owner = THIS_MODULE, + .open = asus_wmi_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void asus_wmi_debugfs_exit(struct asus_wmi *asus) +{ + debugfs_remove_recursive(asus->debug.root); +} + +static int asus_wmi_debugfs_init(struct asus_wmi *asus) +{ + struct dentry *dent; + int i; + + asus->debug.root = debugfs_create_dir(asus->driver->name, NULL); + if (!asus->debug.root) { + pr_err("failed to create debugfs directory\n"); + goto error_debugfs; + } + + dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, + asus->debug.root, &asus->debug.method_id); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, + asus->debug.root, &asus->debug.dev_id); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, + asus->debug.root, &asus->debug.ctrl_param); + if (!dent) + goto error_debugfs; + + for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) { + struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i]; + + node->asus = asus; + dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO, + asus->debug.root, node, + &asus_wmi_debugfs_io_ops); + if (!dent) { + pr_err("failed to create debug file: %s\n", node->name); + goto error_debugfs; + } + } + + return 0; + +error_debugfs: + asus_wmi_debugfs_exit(asus); + return -ENOMEM; +} + +/* + * WMI Driver + */ +static int asus_wmi_add(struct platform_device *pdev) +{ + struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); + struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv); + struct asus_wmi *asus; + acpi_status status; + int err; + u32 result; + + asus = kzalloc(sizeof(struct asus_wmi), GFP_KERNEL); + if (!asus) + return -ENOMEM; + + asus->driver = wdrv; + asus->platform_device = pdev; + wdrv->platform_device = pdev; + platform_set_drvdata(asus->platform_device, asus); + + if (wdrv->detect_quirks) + wdrv->detect_quirks(asus->driver); + + err = asus_wmi_platform_init(asus); + if (err) + goto fail_platform; + + err = asus_wmi_input_init(asus); + if (err) + goto fail_input; + + err = asus_wmi_hwmon_init(asus); + if (err) + goto fail_hwmon; + + err = asus_wmi_led_init(asus); + if (err) + goto fail_leds; + + err = asus_wmi_rfkill_init(asus); + if (err) + goto fail_rfkill; + + if (asus->driver->quirks->wmi_backlight_power) + acpi_video_dmi_promote_vendor(); + if (!acpi_video_backlight_support()) { + pr_info("Disabling ACPI video driver\n"); + acpi_video_unregister(); + err = asus_wmi_backlight_init(asus); + if (err && err != -ENODEV) + goto fail_backlight; + } else + pr_info("Backlight controlled by ACPI video driver\n"); + + status = wmi_install_notify_handler(asus->driver->event_guid, + asus_wmi_notify, asus); + if (ACPI_FAILURE(status)) { + pr_err("Unable to register notify handler - %d\n", status); + err = -ENODEV; + goto fail_wmi_handler; + } + + err = asus_wmi_debugfs_init(asus); + if (err) + goto fail_debugfs; + + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WLAN, &result); + if (result & (ASUS_WMI_DSTS_PRESENCE_BIT | ASUS_WMI_DSTS_USER_BIT)) + asus->driver->wlan_ctrl_by_user = 1; + + return 0; + +fail_debugfs: + wmi_remove_notify_handler(asus->driver->event_guid); +fail_wmi_handler: + asus_wmi_backlight_exit(asus); +fail_backlight: + asus_wmi_rfkill_exit(asus); +fail_rfkill: + asus_wmi_led_exit(asus); +fail_leds: +fail_hwmon: + asus_wmi_input_exit(asus); +fail_input: + asus_wmi_platform_exit(asus); +fail_platform: + kfree(asus); + return err; +} + +static int asus_wmi_remove(struct platform_device *device) +{ + struct asus_wmi *asus; + + asus = platform_get_drvdata(device); + wmi_remove_notify_handler(asus->driver->event_guid); + asus_wmi_backlight_exit(asus); + asus_wmi_input_exit(asus); + asus_wmi_led_exit(asus); + asus_wmi_rfkill_exit(asus); + asus_wmi_debugfs_exit(asus); + asus_wmi_platform_exit(asus); + + kfree(asus); + return 0; +} + +/* + * Platform driver - hibernate/resume callbacks + */ +static int asus_hotk_thaw(struct device *device) +{ + struct asus_wmi *asus = dev_get_drvdata(device); + + if (asus->wlan.rfkill) { + bool wlan; + + /* + * Work around bios bug - acpi _PTS turns off the wireless led + * during suspend. Normally it restores it on resume, but + * we should kick it ourselves in case hibernation is aborted. + */ + wlan = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN); + asus_wmi_set_devstate(ASUS_WMI_DEVID_WLAN, wlan, NULL); + } + + return 0; +} + +static int asus_hotk_restore(struct device *device) +{ + struct asus_wmi *asus = dev_get_drvdata(device); + int bl; + + /* Refresh both wlan rfkill state and pci hotplug */ + if (asus->wlan.rfkill) + asus_rfkill_hotplug(asus); + + if (asus->bluetooth.rfkill) { + bl = !asus_wmi_get_devstate_simple(asus, + ASUS_WMI_DEVID_BLUETOOTH); + rfkill_set_sw_state(asus->bluetooth.rfkill, bl); + } + if (asus->wimax.rfkill) { + bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WIMAX); + rfkill_set_sw_state(asus->wimax.rfkill, bl); + } + if (asus->wwan3g.rfkill) { + bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WWAN3G); + rfkill_set_sw_state(asus->wwan3g.rfkill, bl); + } + if (asus->gps.rfkill) { + bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPS); + rfkill_set_sw_state(asus->gps.rfkill, bl); + } + if (asus->uwb.rfkill) { + bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_UWB); + rfkill_set_sw_state(asus->uwb.rfkill, bl); + } + + return 0; +} + +static const struct dev_pm_ops asus_pm_ops = { + .thaw = asus_hotk_thaw, + .restore = asus_hotk_restore, +}; + +static int asus_wmi_probe(struct platform_device *pdev) +{ + struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); + struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv); + int ret; + + if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) { + pr_warn("Management GUID not found\n"); + return -ENODEV; + } + + if (wdrv->event_guid && !wmi_has_guid(wdrv->event_guid)) { + pr_warn("Event GUID not found\n"); + return -ENODEV; + } + + if (wdrv->probe) { + ret = wdrv->probe(pdev); + if (ret) + return ret; + } + + return asus_wmi_add(pdev); +} + +static bool used; + +int __init_or_module asus_wmi_register_driver(struct asus_wmi_driver *driver) +{ + struct platform_driver *platform_driver; + struct platform_device *platform_device; + + if (used) + return -EBUSY; + + platform_driver = &driver->platform_driver; + platform_driver->remove = asus_wmi_remove; + platform_driver->driver.owner = driver->owner; + platform_driver->driver.name = driver->name; + platform_driver->driver.pm = &asus_pm_ops; + + platform_device = platform_create_bundle(platform_driver, + asus_wmi_probe, + NULL, 0, NULL, 0); + if (IS_ERR(platform_device)) + return PTR_ERR(platform_device); + + used = true; + return 0; +} +EXPORT_SYMBOL_GPL(asus_wmi_register_driver); + +void asus_wmi_unregister_driver(struct asus_wmi_driver *driver) +{ + platform_device_unregister(driver->platform_device); + platform_driver_unregister(&driver->platform_driver); + used = false; +} +EXPORT_SYMBOL_GPL(asus_wmi_unregister_driver); + +static int __init asus_wmi_init(void) +{ + if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) { + pr_info("Asus Management GUID not found\n"); + return -ENODEV; + } + + pr_info("ASUS WMI generic driver loaded\n"); + return 0; +} + +static void __exit asus_wmi_exit(void) +{ + pr_info("ASUS WMI generic driver unloaded\n"); +} + +module_init(asus_wmi_init); +module_exit(asus_wmi_exit); diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h new file mode 100644 index 00000000000..4da4c8bafe7 --- /dev/null +++ b/drivers/platform/x86/asus-wmi.h @@ -0,0 +1,84 @@ +/* + * Asus PC WMI hotkey driver + * + * Copyright(C) 2010 Intel Corporation. + * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com> + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ASUS_WMI_H_ +#define _ASUS_WMI_H_ + +#include <linux/platform_device.h> + +#define ASUS_WMI_KEY_IGNORE (-1) +#define ASUS_WMI_BRN_DOWN 0x20 +#define ASUS_WMI_BRN_UP 0x2f + +struct module; +struct key_entry; +struct asus_wmi; + +struct quirk_entry { + bool hotplug_wireless; + bool scalar_panel_brightness; + bool store_backlight_power; + bool wmi_backlight_power; + int wapf; + /* + * For machines with AMD graphic chips, it will send out WMI event + * and ACPI interrupt at the same time while hitting the hotkey. + * To simplify the problem, we just have to ignore the WMI event, + * and let the ACPI interrupt to send out the key event. + */ + int no_display_toggle; +}; + +struct asus_wmi_driver { + int brightness; + int panel_power; + int wlan_ctrl_by_user; + + const char *name; + struct module *owner; + + const char *event_guid; + + const struct key_entry *keymap; + const char *input_name; + const char *input_phys; + struct quirk_entry *quirks; + /* Returns new code, value, and autorelease values in arguments. + * Return ASUS_WMI_KEY_IGNORE in code if event should be ignored. */ + void (*key_filter) (struct asus_wmi_driver *driver, int *code, + unsigned int *value, bool *autorelease); + + int (*probe) (struct platform_device *device); + void (*detect_quirks) (struct asus_wmi_driver *driver); + + struct platform_driver platform_driver; + struct platform_device *platform_device; +}; + +int asus_wmi_register_driver(struct asus_wmi_driver *driver); +void asus_wmi_unregister_driver(struct asus_wmi_driver *driver); + +#endif /* !_ASUS_WMI_H_ */ diff --git a/drivers/platform/x86/asus_acpi.c b/drivers/platform/x86/asus_acpi.c deleted file mode 100644 index c1d2aeeea94..00000000000 --- a/drivers/platform/x86/asus_acpi.c +++ /dev/null @@ -1,1525 +0,0 @@ -/* - * asus_acpi.c - Asus Laptop ACPI Extras - * - * - * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * - * The development page for this driver is located at - * http://sourceforge.net/projects/acpi4asus/ - * - * Credits: - * Pontus Fuchs - Helper functions, cleanup - * Johann Wiesner - Small compile fixes - * John Belmonte - ACPI code for Toshiba laptop was a good starting point. - * �ic Burghard - LED display support for W1N - * - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/proc_fs.h> -#include <linux/seq_file.h> -#include <linux/backlight.h> -#include <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> -#include <asm/uaccess.h> - -#define ASUS_ACPI_VERSION "0.30" - -#define PROC_ASUS "asus" /* The directory */ -#define PROC_MLED "mled" -#define PROC_WLED "wled" -#define PROC_TLED "tled" -#define PROC_BT "bluetooth" -#define PROC_LEDD "ledd" -#define PROC_INFO "info" -#define PROC_LCD "lcd" -#define PROC_BRN "brn" -#define PROC_DISP "disp" - -#define ACPI_HOTK_NAME "Asus Laptop ACPI Extras Driver" -#define ACPI_HOTK_CLASS "hotkey" -#define ACPI_HOTK_DEVICE_NAME "Hotkey" - -/* - * Some events we use, same for all Asus - */ -#define BR_UP 0x10 -#define BR_DOWN 0x20 - -/* - * Flags for hotk status - */ -#define MLED_ON 0x01 /* Mail LED */ -#define WLED_ON 0x02 /* Wireless LED */ -#define TLED_ON 0x04 /* Touchpad LED */ -#define BT_ON 0x08 /* Internal Bluetooth */ - -MODULE_AUTHOR("Julien Lerouge, Karol Kozimor"); -MODULE_DESCRIPTION(ACPI_HOTK_NAME); -MODULE_LICENSE("GPL"); - -static uid_t asus_uid; -static gid_t asus_gid; -module_param(asus_uid, uint, 0); -MODULE_PARM_DESC(asus_uid, "UID for entries in /proc/acpi/asus"); -module_param(asus_gid, uint, 0); -MODULE_PARM_DESC(asus_gid, "GID for entries in /proc/acpi/asus"); - -/* For each model, all features implemented, - * those marked with R are relative to HOTK, A for absolute */ -struct model_data { - char *name; /* name of the laptop________________A */ - char *mt_mled; /* method to handle mled_____________R */ - char *mled_status; /* node to handle mled reading_______A */ - char *mt_wled; /* method to handle wled_____________R */ - char *wled_status; /* node to handle wled reading_______A */ - char *mt_tled; /* method to handle tled_____________R */ - char *tled_status; /* node to handle tled reading_______A */ - char *mt_ledd; /* method to handle LED display______R */ - char *mt_bt_switch; /* method to switch Bluetooth on/off_R */ - char *bt_status; /* no model currently supports this__? */ - char *mt_lcd_switch; /* method to turn LCD on/off_________A */ - char *lcd_status; /* node to read LCD panel state______A */ - char *brightness_up; /* method to set brightness up_______A */ - char *brightness_down; /* method to set brightness down ____A */ - char *brightness_set; /* method to set absolute brightness_R */ - char *brightness_get; /* method to get absolute brightness_R */ - char *brightness_status;/* node to get brightness____________A */ - char *display_set; /* method to set video output________R */ - char *display_get; /* method to get video output________R */ -}; - -/* - * This is the main structure, we can use it to store anything interesting - * about the hotk device - */ -struct asus_hotk { - struct acpi_device *device; /* the device we are in */ - acpi_handle handle; /* the handle of the hotk device */ - char status; /* status of the hotk, for LEDs */ - u32 ledd_status; /* status of the LED display */ - struct model_data *methods; /* methods available on the laptop */ - u8 brightness; /* brightness level */ - enum { - A1x = 0, /* A1340D, A1300F */ - A2x, /* A2500H */ - A4G, /* A4700G */ - D1x, /* D1 */ - L2D, /* L2000D */ - L3C, /* L3800C */ - L3D, /* L3400D */ - L3H, /* L3H, L2000E, L5D */ - L4R, /* L4500R */ - L5x, /* L5800C */ - L8L, /* L8400L */ - M1A, /* M1300A */ - M2E, /* M2400E, L4400L */ - M6N, /* M6800N, W3400N */ - M6R, /* M6700R, A3000G */ - P30, /* Samsung P30 */ - S1x, /* S1300A, but also L1400B and M2400A (L84F) */ - S2x, /* S200 (J1 reported), Victor MP-XP7210 */ - W1N, /* W1000N */ - W5A, /* W5A */ - W3V, /* W3030V */ - xxN, /* M2400N, M3700N, M5200N, M6800N, - S1300N, S5200N*/ - A4S, /* Z81sp */ - F3Sa, /* (Centrino) */ - R1F, - END_MODEL - } model; /* Models currently supported */ - u16 event_count[128]; /* Count for each event TODO make this better */ -}; - -/* Here we go */ -#define A1x_PREFIX "\\_SB.PCI0.ISA.EC0." -#define L3C_PREFIX "\\_SB.PCI0.PX40.ECD0." -#define M1A_PREFIX "\\_SB.PCI0.PX40.EC0." -#define P30_PREFIX "\\_SB.PCI0.LPCB.EC0." -#define S1x_PREFIX "\\_SB.PCI0.PX40." -#define S2x_PREFIX A1x_PREFIX -#define xxN_PREFIX "\\_SB.PCI0.SBRG.EC0." - -static struct model_data model_conf[END_MODEL] = { - /* - * TODO I have seen a SWBX and AIBX method on some models, like L1400B, - * it seems to be a kind of switch, but what for ? - */ - - { - .name = "A1x", - .mt_mled = "MLED", - .mled_status = "\\MAIL", - .mt_lcd_switch = A1x_PREFIX "_Q10", - .lcd_status = "\\BKLI", - .brightness_up = A1x_PREFIX "_Q0E", - .brightness_down = A1x_PREFIX "_Q0F"}, - - { - .name = "A2x", - .mt_mled = "MLED", - .mt_wled = "WLED", - .wled_status = "\\SG66", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\BAOF", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "A4G", - .mt_mled = "MLED", -/* WLED present, but not controlled by ACPI */ - .mt_lcd_switch = xxN_PREFIX "_Q10", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "D1x", - .mt_mled = "MLED", - .mt_lcd_switch = "\\Q0D", - .lcd_status = "\\GP11", - .brightness_up = "\\Q0C", - .brightness_down = "\\Q0B", - .brightness_status = "\\BLVL", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L2D", - .mt_mled = "MLED", - .mled_status = "\\SGP6", - .mt_wled = "WLED", - .wled_status = "\\RCP3", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\SGP0", - .brightness_up = "\\Q0E", - .brightness_down = "\\Q0F", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L3C", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = L3C_PREFIX "_Q10", - .lcd_status = "\\GL32", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\_SB.PCI0.PCI1.VGAC.NMAP"}, - - { - .name = "L3D", - .mt_mled = "MLED", - .mled_status = "\\MALD", - .mt_wled = "WLED", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\BKLG", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L3H", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = "EHK", - .lcd_status = "\\_SB.PCI0.PM.PBC", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L4R", - .mt_mled = "MLED", - .mt_wled = "WLED", - .wled_status = "\\_SB.PCI0.SBRG.SG13", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\_SB.PCI0.SBSM.SEO4", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"}, - - { - .name = "L5x", - .mt_mled = "MLED", -/* WLED present, but not controlled by ACPI */ - .mt_tled = "TLED", - .mt_lcd_switch = "\\Q0D", - .lcd_status = "\\BAOF", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "L8L" -/* No features, but at least support the hotkeys */ - }, - - { - .name = "M1A", - .mt_mled = "MLED", - .mt_lcd_switch = M1A_PREFIX "Q10", - .lcd_status = "\\PNOF", - .brightness_up = M1A_PREFIX "Q0E", - .brightness_down = M1A_PREFIX "Q0F", - .brightness_status = "\\BRIT", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "M2E", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\GP06", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "M6N", - .mt_mled = "MLED", - .mt_wled = "WLED", - .wled_status = "\\_SB.PCI0.SBRG.SG13", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\_SB.BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\SSTE"}, - - { - .name = "M6R", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\_SB.PCI0.SBSM.SEO4", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"}, - - { - .name = "P30", - .mt_wled = "WLED", - .mt_lcd_switch = P30_PREFIX "_Q0E", - .lcd_status = "\\BKLT", - .brightness_up = P30_PREFIX "_Q68", - .brightness_down = P30_PREFIX "_Q69", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\DNXT"}, - - { - .name = "S1x", - .mt_mled = "MLED", - .mled_status = "\\EMLE", - .mt_wled = "WLED", - .mt_lcd_switch = S1x_PREFIX "Q10", - .lcd_status = "\\PNOF", - .brightness_set = "SPLV", - .brightness_get = "GPLV"}, - - { - .name = "S2x", - .mt_mled = "MLED", - .mled_status = "\\MAIL", - .mt_lcd_switch = S2x_PREFIX "_Q10", - .lcd_status = "\\BKLI", - .brightness_up = S2x_PREFIX "_Q0B", - .brightness_down = S2x_PREFIX "_Q0A"}, - - { - .name = "W1N", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_ledd = "SLCM", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "W5A", - .mt_bt_switch = "BLED", - .mt_wled = "WLED", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "W3V", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB"}, - - { - .name = "xxN", - .mt_mled = "MLED", -/* WLED present, but not controlled by ACPI */ - .mt_lcd_switch = xxN_PREFIX "_Q10", - .lcd_status = "\\BKLT", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\ADVG"}, - - { - .name = "A4S", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .mt_bt_switch = "BLED", - .mt_wled = "WLED" - }, - - { - .name = "F3Sa", - .mt_bt_switch = "BLED", - .mt_wled = "WLED", - .mt_mled = "MLED", - .brightness_get = "GPLV", - .brightness_set = "SPLV", - .mt_lcd_switch = "\\_SB.PCI0.SBRG.EC0._Q10", - .lcd_status = "\\_SB.PCI0.SBRG.EC0.RPIN", - .display_get = "\\ADVG", - .display_set = "SDSP", - }, - { - .name = "R1F", - .mt_bt_switch = "BLED", - .mt_mled = "MLED", - .mt_wled = "WLED", - .mt_lcd_switch = "\\Q10", - .lcd_status = "\\GP06", - .brightness_set = "SPLV", - .brightness_get = "GPLV", - .display_set = "SDSP", - .display_get = "\\INFB" - } -}; - -/* procdir we use */ -static struct proc_dir_entry *asus_proc_dir; - -static struct backlight_device *asus_backlight_device; - -/* - * This header is made available to allow proper configuration given model, - * revision number , ... this info cannot go in struct asus_hotk because it is - * available before the hotk - */ -static struct acpi_table_header *asus_info; - -/* The actual device the driver binds to */ -static struct asus_hotk *hotk; - -/* - * The hotkey driver and autoloading declaration - */ -static int asus_hotk_add(struct acpi_device *device); -static int asus_hotk_remove(struct acpi_device *device, int type); -static void asus_hotk_notify(struct acpi_device *device, u32 event); - -static const struct acpi_device_id asus_device_ids[] = { - {"ATK0100", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, asus_device_ids); - -static struct acpi_driver asus_hotk_driver = { - .name = "asus_acpi", - .class = ACPI_HOTK_CLASS, - .owner = THIS_MODULE, - .ids = asus_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = asus_hotk_add, - .remove = asus_hotk_remove, - .notify = asus_hotk_notify, - }, -}; - -/* - * This function evaluates an ACPI method, given an int as parameter, the - * method is searched within the scope of the handle, can be NULL. The output - * of the method is written is output, which can also be NULL - * - * returns 1 if write is successful, 0 else. - */ -static int write_acpi_int(acpi_handle handle, const char *method, int val, - struct acpi_buffer *output) -{ - struct acpi_object_list params; /* list of input parameters (int) */ - union acpi_object in_obj; /* the only param we use */ - acpi_status status; - - params.count = 1; - params.pointer = &in_obj; - in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = val; - - status = acpi_evaluate_object(handle, (char *)method, ¶ms, output); - return (status == AE_OK); -} - -static int read_acpi_int(acpi_handle handle, const char *method, int *val) -{ - struct acpi_buffer output; - union acpi_object out_obj; - acpi_status status; - - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = acpi_evaluate_object(handle, (char *)method, NULL, &output); - *val = out_obj.integer.value; - return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); -} - -static int asus_info_proc_show(struct seq_file *m, void *v) -{ - int temp; - - seq_printf(m, ACPI_HOTK_NAME " " ASUS_ACPI_VERSION "\n"); - seq_printf(m, "Model reference : %s\n", hotk->methods->name); - /* - * The SFUN method probably allows the original driver to get the list - * of features supported by a given model. For now, 0x0100 or 0x0800 - * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. - * The significance of others is yet to be found. - */ - if (read_acpi_int(hotk->handle, "SFUN", &temp)) - seq_printf(m, "SFUN value : 0x%04x\n", temp); - /* - * Another value for userspace: the ASYM method returns 0x02 for - * battery low and 0x04 for battery critical, its readings tend to be - * more accurate than those provided by _BST. - * Note: since not all the laptops provide this method, errors are - * silently ignored. - */ - if (read_acpi_int(hotk->handle, "ASYM", &temp)) - seq_printf(m, "ASYM value : 0x%04x\n", temp); - if (asus_info) { - seq_printf(m, "DSDT length : %d\n", asus_info->length); - seq_printf(m, "DSDT checksum : %d\n", asus_info->checksum); - seq_printf(m, "DSDT revision : %d\n", asus_info->revision); - seq_printf(m, "OEM id : %.*s\n", ACPI_OEM_ID_SIZE, asus_info->oem_id); - seq_printf(m, "OEM table id : %.*s\n", ACPI_OEM_TABLE_ID_SIZE, asus_info->oem_table_id); - seq_printf(m, "OEM revision : 0x%x\n", asus_info->oem_revision); - seq_printf(m, "ASL comp vendor id : %.*s\n", ACPI_NAME_SIZE, asus_info->asl_compiler_id); - seq_printf(m, "ASL comp revision : 0x%x\n", asus_info->asl_compiler_revision); - } - - return 0; -} - -static int asus_info_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, asus_info_proc_show, NULL); -} - -static const struct file_operations asus_info_proc_fops = { - .owner = THIS_MODULE, - .open = asus_info_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -/* - * /proc handlers - * We write our info in page, we begin at offset off and cannot write more - * than count bytes. We set eof to 1 if we handle those 2 values. We return the - * number of bytes written in page - */ - -/* Generic LED functions */ -static int read_led(const char *ledname, int ledmask) -{ - if (ledname) { - int led_status; - - if (read_acpi_int(NULL, ledname, &led_status)) - return led_status; - else - printk(KERN_WARNING "Asus ACPI: Error reading LED " - "status\n"); - } - return (hotk->status & ledmask) ? 1 : 0; -} - -static int parse_arg(const char __user *buf, unsigned long count, int *val) -{ - char s[32]; - if (!count) - return 0; - if (count > 31) - return -EINVAL; - if (copy_from_user(s, buf, count)) - return -EFAULT; - s[count] = 0; - if (sscanf(s, "%i", val) != 1) - return -EINVAL; - return count; -} - -/* FIXME: kill extraneous args so it can be called independently */ -static int -write_led(const char __user *buffer, unsigned long count, - char *ledname, int ledmask, int invert) -{ - int rv, value; - int led_out = 0; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) - led_out = value ? 1 : 0; - - hotk->status = - (led_out) ? (hotk->status | ledmask) : (hotk->status & ~ledmask); - - if (invert) /* invert target value */ - led_out = !led_out; - - if (!write_acpi_int(hotk->handle, ledname, led_out, NULL)) - printk(KERN_WARNING "Asus ACPI: LED (%s) write failed\n", - ledname); - - return rv; -} - -/* - * Proc handlers for MLED - */ -static int mled_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->mled_status, MLED_ON)); - return 0; -} - -static int mled_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, mled_proc_show, NULL); -} - -static ssize_t mled_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - return write_led(buffer, count, hotk->methods->mt_mled, MLED_ON, 1); -} - -static const struct file_operations mled_proc_fops = { - .owner = THIS_MODULE, - .open = mled_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = mled_proc_write, -}; - -/* - * Proc handlers for LED display - */ -static int ledd_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "0x%08x\n", hotk->ledd_status); - return 0; -} - -static int ledd_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, ledd_proc_show, NULL); -} - -static ssize_t ledd_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) { - if (!write_acpi_int - (hotk->handle, hotk->methods->mt_ledd, value, NULL)) - printk(KERN_WARNING - "Asus ACPI: LED display write failed\n"); - else - hotk->ledd_status = (u32) value; - } - return rv; -} - -static const struct file_operations ledd_proc_fops = { - .owner = THIS_MODULE, - .open = ledd_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = ledd_proc_write, -}; - -/* - * Proc handlers for WLED - */ -static int wled_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->wled_status, WLED_ON)); - return 0; -} - -static int wled_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, wled_proc_show, NULL); -} - -static ssize_t wled_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - return write_led(buffer, count, hotk->methods->mt_wled, WLED_ON, 0); -} - -static const struct file_operations wled_proc_fops = { - .owner = THIS_MODULE, - .open = wled_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = wled_proc_write, -}; - -/* - * Proc handlers for Bluetooth - */ -static int bluetooth_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->bt_status, BT_ON)); - return 0; -} - -static int bluetooth_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, bluetooth_proc_show, NULL); -} - -static ssize_t bluetooth_proc_write(struct file *file, - const char __user *buffer, size_t count, loff_t *pos) -{ - /* Note: mt_bt_switch controls both internal Bluetooth adapter's - presence and its LED */ - return write_led(buffer, count, hotk->methods->mt_bt_switch, BT_ON, 0); -} - -static const struct file_operations bluetooth_proc_fops = { - .owner = THIS_MODULE, - .open = bluetooth_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = bluetooth_proc_write, -}; - -/* - * Proc handlers for TLED - */ -static int tled_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_led(hotk->methods->tled_status, TLED_ON)); - return 0; -} - -static int tled_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, tled_proc_show, NULL); -} - -static ssize_t tled_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - return write_led(buffer, count, hotk->methods->mt_tled, TLED_ON, 0); -} - -static const struct file_operations tled_proc_fops = { - .owner = THIS_MODULE, - .open = tled_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = tled_proc_write, -}; - -static int get_lcd_state(void) -{ - int lcd = 0; - - if (hotk->model == L3H) { - /* L3H and the like have to be handled differently */ - acpi_status status = 0; - struct acpi_object_list input; - union acpi_object mt_params[2]; - struct acpi_buffer output; - union acpi_object out_obj; - - input.count = 2; - input.pointer = mt_params; - /* Note: the following values are partly guessed up, but - otherwise they seem to work */ - mt_params[0].type = ACPI_TYPE_INTEGER; - mt_params[0].integer.value = 0x02; - mt_params[1].type = ACPI_TYPE_INTEGER; - mt_params[1].integer.value = 0x02; - - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = - acpi_evaluate_object(NULL, hotk->methods->lcd_status, - &input, &output); - if (status != AE_OK) - return -1; - if (out_obj.type == ACPI_TYPE_INTEGER) - /* That's what the AML code does */ - lcd = out_obj.integer.value >> 8; - } else if (hotk->model == F3Sa) { - unsigned long long tmp; - union acpi_object param; - struct acpi_object_list input; - acpi_status status; - - /* Read pin 11 */ - param.type = ACPI_TYPE_INTEGER; - param.integer.value = 0x11; - input.count = 1; - input.pointer = ¶m; - - status = acpi_evaluate_integer(NULL, hotk->methods->lcd_status, - &input, &tmp); - if (status != AE_OK) - return -1; - - lcd = tmp; - } else { - /* We don't have to check anything if we are here */ - if (!read_acpi_int(NULL, hotk->methods->lcd_status, &lcd)) - printk(KERN_WARNING - "Asus ACPI: Error reading LCD status\n"); - - if (hotk->model == L2D) - lcd = ~lcd; - } - - return (lcd & 1); -} - -static int set_lcd_state(int value) -{ - int lcd = 0; - acpi_status status = 0; - - lcd = value ? 1 : 0; - if (lcd != get_lcd_state()) { - /* switch */ - if (hotk->model != L3H) { - status = - acpi_evaluate_object(NULL, - hotk->methods->mt_lcd_switch, - NULL, NULL); - } else { - /* L3H and the like must be handled differently */ - if (!write_acpi_int - (hotk->handle, hotk->methods->mt_lcd_switch, 0x07, - NULL)) - status = AE_ERROR; - /* L3H's AML executes EHK (0x07) upon Fn+F7 keypress, - the exact behaviour is simulated here */ - } - if (ACPI_FAILURE(status)) - printk(KERN_WARNING "Asus ACPI: Error switching LCD\n"); - } - return 0; - -} - -static int lcd_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", get_lcd_state()); - return 0; -} - -static int lcd_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, lcd_proc_show, NULL); -} - -static ssize_t lcd_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) - set_lcd_state(value); - return rv; -} - -static const struct file_operations lcd_proc_fops = { - .owner = THIS_MODULE, - .open = lcd_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = lcd_proc_write, -}; - -static int read_brightness(struct backlight_device *bd) -{ - int value; - - if (hotk->methods->brightness_get) { /* SPLV/GPLV laptop */ - if (!read_acpi_int(hotk->handle, hotk->methods->brightness_get, - &value)) - printk(KERN_WARNING - "Asus ACPI: Error reading brightness\n"); - } else if (hotk->methods->brightness_status) { /* For D1 for example */ - if (!read_acpi_int(NULL, hotk->methods->brightness_status, - &value)) - printk(KERN_WARNING - "Asus ACPI: Error reading brightness\n"); - } else /* No GPLV method */ - value = hotk->brightness; - return value; -} - -/* - * Change the brightness level - */ -static int set_brightness(int value) -{ - acpi_status status = 0; - int ret = 0; - - /* SPLV laptop */ - if (hotk->methods->brightness_set) { - if (!write_acpi_int(hotk->handle, hotk->methods->brightness_set, - value, NULL)) - printk(KERN_WARNING - "Asus ACPI: Error changing brightness\n"); - ret = -EIO; - goto out; - } - - /* No SPLV method if we are here, act as appropriate */ - value -= read_brightness(NULL); - while (value != 0) { - status = acpi_evaluate_object(NULL, (value > 0) ? - hotk->methods->brightness_up : - hotk->methods->brightness_down, - NULL, NULL); - (value > 0) ? value-- : value++; - if (ACPI_FAILURE(status)) - printk(KERN_WARNING - "Asus ACPI: Error changing brightness\n"); - ret = -EIO; - } -out: - return ret; -} - -static int set_brightness_status(struct backlight_device *bd) -{ - return set_brightness(bd->props.brightness); -} - -static int brn_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%d\n", read_brightness(NULL)); - return 0; -} - -static int brn_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, brn_proc_show, NULL); -} - -static ssize_t brn_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) { - value = (0 < value) ? ((15 < value) ? 15 : value) : 0; - /* 0 <= value <= 15 */ - set_brightness(value); - } - return rv; -} - -static const struct file_operations brn_proc_fops = { - .owner = THIS_MODULE, - .open = brn_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = brn_proc_write, -}; - -static void set_display(int value) -{ - /* no sanity check needed for now */ - if (!write_acpi_int(hotk->handle, hotk->methods->display_set, - value, NULL)) - printk(KERN_WARNING "Asus ACPI: Error setting display\n"); - return; -} - -/* - * Now, *this* one could be more user-friendly, but so far, no-one has - * complained. The significance of bits is the same as in proc_write_disp() - */ -static int disp_proc_show(struct seq_file *m, void *v) -{ - int value = 0; - - if (!read_acpi_int(hotk->handle, hotk->methods->display_get, &value)) - printk(KERN_WARNING - "Asus ACPI: Error reading display status\n"); - value &= 0x07; /* needed for some models, shouldn't hurt others */ - seq_printf(m, "%d\n", value); - return 0; -} - -static int disp_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, disp_proc_show, NULL); -} - -/* - * Experimental support for display switching. As of now: 1 should activate - * the LCD output, 2 should do for CRT, and 4 for TV-Out. Any combination - * (bitwise) of these will suffice. I never actually tested 3 displays hooked - * up simultaneously, so be warned. See the acpi4asus README for more info. - */ -static ssize_t disp_proc_write(struct file *file, const char __user *buffer, - size_t count, loff_t *pos) -{ - int rv, value; - - rv = parse_arg(buffer, count, &value); - if (rv > 0) - set_display(value); - return rv; -} - -static const struct file_operations disp_proc_fops = { - .owner = THIS_MODULE, - .open = disp_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .write = disp_proc_write, -}; - -static int -asus_proc_add(char *name, const struct file_operations *proc_fops, mode_t mode, - struct acpi_device *device) -{ - struct proc_dir_entry *proc; - - proc = proc_create_data(name, mode, acpi_device_dir(device), - proc_fops, acpi_driver_data(device)); - if (!proc) { - printk(KERN_WARNING " Unable to create %s fs entry\n", name); - return -1; - } - proc->uid = asus_uid; - proc->gid = asus_gid; - return 0; -} - -static int asus_hotk_add_fs(struct acpi_device *device) -{ - struct proc_dir_entry *proc; - mode_t mode; - - /* - * If parameter uid or gid is not changed, keep the default setting for - * our proc entries (-rw-rw-rw-) else, it means we care about security, - * and then set to -rw-rw---- - */ - - if ((asus_uid == 0) && (asus_gid == 0)) { - mode = S_IFREG | S_IRUGO | S_IWUGO; - } else { - mode = S_IFREG | S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP; - printk(KERN_WARNING " asus_uid and asus_gid parameters are " - "deprecated, use chown and chmod instead!\n"); - } - - acpi_device_dir(device) = asus_proc_dir; - if (!acpi_device_dir(device)) - return -ENODEV; - - proc = proc_create(PROC_INFO, mode, acpi_device_dir(device), - &asus_info_proc_fops); - if (proc) { - proc->uid = asus_uid; - proc->gid = asus_gid; - } else { - printk(KERN_WARNING " Unable to create " PROC_INFO - " fs entry\n"); - } - - if (hotk->methods->mt_wled) { - asus_proc_add(PROC_WLED, &wled_proc_fops, mode, device); - } - - if (hotk->methods->mt_ledd) { - asus_proc_add(PROC_LEDD, &ledd_proc_fops, mode, device); - } - - if (hotk->methods->mt_mled) { - asus_proc_add(PROC_MLED, &mled_proc_fops, mode, device); - } - - if (hotk->methods->mt_tled) { - asus_proc_add(PROC_TLED, &tled_proc_fops, mode, device); - } - - if (hotk->methods->mt_bt_switch) { - asus_proc_add(PROC_BT, &bluetooth_proc_fops, mode, device); - } - - /* - * We need both read node and write method as LCD switch is also - * accessible from the keyboard - */ - if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) { - asus_proc_add(PROC_LCD, &lcd_proc_fops, mode, device); - } - - if ((hotk->methods->brightness_up && hotk->methods->brightness_down) || - (hotk->methods->brightness_get && hotk->methods->brightness_set)) { - asus_proc_add(PROC_BRN, &brn_proc_fops, mode, device); - } - - if (hotk->methods->display_set) { - asus_proc_add(PROC_DISP, &disp_proc_fops, mode, device); - } - - return 0; -} - -static int asus_hotk_remove_fs(struct acpi_device *device) -{ - if (acpi_device_dir(device)) { - remove_proc_entry(PROC_INFO, acpi_device_dir(device)); - if (hotk->methods->mt_wled) - remove_proc_entry(PROC_WLED, acpi_device_dir(device)); - if (hotk->methods->mt_mled) - remove_proc_entry(PROC_MLED, acpi_device_dir(device)); - if (hotk->methods->mt_tled) - remove_proc_entry(PROC_TLED, acpi_device_dir(device)); - if (hotk->methods->mt_ledd) - remove_proc_entry(PROC_LEDD, acpi_device_dir(device)); - if (hotk->methods->mt_bt_switch) - remove_proc_entry(PROC_BT, acpi_device_dir(device)); - if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) - remove_proc_entry(PROC_LCD, acpi_device_dir(device)); - if ((hotk->methods->brightness_up - && hotk->methods->brightness_down) - || (hotk->methods->brightness_get - && hotk->methods->brightness_set)) - remove_proc_entry(PROC_BRN, acpi_device_dir(device)); - if (hotk->methods->display_set) - remove_proc_entry(PROC_DISP, acpi_device_dir(device)); - } - return 0; -} - -static void asus_hotk_notify(struct acpi_device *device, u32 event) -{ - /* TODO Find a better way to handle events count. */ - if (!hotk) - return; - - /* - * The BIOS *should* be sending us device events, but apparently - * Asus uses system events instead, so just ignore any device - * events we get. - */ - if (event > ACPI_MAX_SYS_NOTIFY) - return; - - if ((event & ~((u32) BR_UP)) < 16) - hotk->brightness = (event & ~((u32) BR_UP)); - else if ((event & ~((u32) BR_DOWN)) < 16) - hotk->brightness = (event & ~((u32) BR_DOWN)); - - acpi_bus_generate_proc_event(hotk->device, event, - hotk->event_count[event % 128]++); - - return; -} - -/* - * Match the model string to the list of supported models. Return END_MODEL if - * no match or model is NULL. - */ -static int asus_model_match(char *model) -{ - if (model == NULL) - return END_MODEL; - - if (strncmp(model, "L3D", 3) == 0) - return L3D; - else if (strncmp(model, "L2E", 3) == 0 || - strncmp(model, "L3H", 3) == 0 || strncmp(model, "L5D", 3) == 0) - return L3H; - else if (strncmp(model, "L3", 2) == 0 || strncmp(model, "L2B", 3) == 0) - return L3C; - else if (strncmp(model, "L8L", 3) == 0) - return L8L; - else if (strncmp(model, "L4R", 3) == 0) - return L4R; - else if (strncmp(model, "M6N", 3) == 0 || strncmp(model, "W3N", 3) == 0) - return M6N; - else if (strncmp(model, "M6R", 3) == 0 || strncmp(model, "A3G", 3) == 0) - return M6R; - else if (strncmp(model, "M2N", 3) == 0 || - strncmp(model, "M3N", 3) == 0 || - strncmp(model, "M5N", 3) == 0 || - strncmp(model, "M6N", 3) == 0 || - strncmp(model, "S1N", 3) == 0 || - strncmp(model, "S5N", 3) == 0 || strncmp(model, "W1N", 3) == 0) - return xxN; - else if (strncmp(model, "M1", 2) == 0) - return M1A; - else if (strncmp(model, "M2", 2) == 0 || strncmp(model, "L4E", 3) == 0) - return M2E; - else if (strncmp(model, "L2", 2) == 0) - return L2D; - else if (strncmp(model, "L8", 2) == 0) - return S1x; - else if (strncmp(model, "D1", 2) == 0) - return D1x; - else if (strncmp(model, "A1", 2) == 0) - return A1x; - else if (strncmp(model, "A2", 2) == 0) - return A2x; - else if (strncmp(model, "J1", 2) == 0) - return S2x; - else if (strncmp(model, "L5", 2) == 0) - return L5x; - else if (strncmp(model, "A4G", 3) == 0) - return A4G; - else if (strncmp(model, "W1N", 3) == 0) - return W1N; - else if (strncmp(model, "W3V", 3) == 0) - return W3V; - else if (strncmp(model, "W5A", 3) == 0) - return W5A; - else if (strncmp(model, "R1F", 3) == 0) - return R1F; - else if (strncmp(model, "A4S", 3) == 0) - return A4S; - else if (strncmp(model, "F3Sa", 4) == 0) - return F3Sa; - else - return END_MODEL; -} - -/* - * This function is used to initialize the hotk with right values. In this - * method, we can make all the detection we want, and modify the hotk struct - */ -static int asus_hotk_get_info(void) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *model = NULL; - int bsts_result; - char *string = NULL; - acpi_status status; - - /* - * Get DSDT headers early enough to allow for differentiating between - * models, but late enough to allow acpi_bus_register_driver() to fail - * before doing anything ACPI-specific. Should we encounter a machine, - * which needs special handling (i.e. its hotkey device has a different - * HID), this bit will be moved. A global variable asus_info contains - * the DSDT header. - */ - status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus_info); - if (ACPI_FAILURE(status)) - printk(KERN_WARNING " Couldn't get the DSDT table header\n"); - - /* We have to write 0 on init this far for all ASUS models */ - if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) { - printk(KERN_ERR " Hotkey initialization failed\n"); - return -ENODEV; - } - - /* This needs to be called for some laptops to init properly */ - if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result)) - printk(KERN_WARNING " Error calling BSTS\n"); - else if (bsts_result) - printk(KERN_NOTICE " BSTS called, 0x%02x returned\n", - bsts_result); - - /* - * Try to match the object returned by INIT to the specific model. - * Handle every possible object (or the lack of thereof) the DSDT - * writers might throw at us. When in trouble, we pass NULL to - * asus_model_match() and try something completely different. - */ - if (buffer.pointer) { - model = buffer.pointer; - switch (model->type) { - case ACPI_TYPE_STRING: - string = model->string.pointer; - break; - case ACPI_TYPE_BUFFER: - string = model->buffer.pointer; - break; - default: - kfree(model); - model = NULL; - break; - } - } - hotk->model = asus_model_match(string); - if (hotk->model == END_MODEL) { /* match failed */ - if (asus_info && - strncmp(asus_info->oem_table_id, "ODEM", 4) == 0) { - hotk->model = P30; - printk(KERN_NOTICE - " Samsung P30 detected, supported\n"); - } else { - hotk->model = M2E; - printk(KERN_NOTICE " unsupported model %s, trying " - "default values\n", string); - printk(KERN_NOTICE - " send /proc/acpi/dsdt to the developers\n"); - kfree(model); - return -ENODEV; - } - hotk->methods = &model_conf[hotk->model]; - return AE_OK; - } - hotk->methods = &model_conf[hotk->model]; - printk(KERN_NOTICE " %s model detected, supported\n", string); - - /* Sort of per-model blacklist */ - if (strncmp(string, "L2B", 3) == 0) - hotk->methods->lcd_status = NULL; - /* L2B is similar enough to L3C to use its settings, with this only - exception */ - else if (strncmp(string, "A3G", 3) == 0) - hotk->methods->lcd_status = "\\BLFG"; - /* A3G is like M6R */ - else if (strncmp(string, "S5N", 3) == 0 || - strncmp(string, "M5N", 3) == 0 || - strncmp(string, "W3N", 3) == 0) - hotk->methods->mt_mled = NULL; - /* S5N, M5N and W3N have no MLED */ - else if (strncmp(string, "L5D", 3) == 0) - hotk->methods->mt_wled = NULL; - /* L5D's WLED is not controlled by ACPI */ - else if (strncmp(string, "M2N", 3) == 0 || - strncmp(string, "W3V", 3) == 0 || - strncmp(string, "S1N", 3) == 0) - hotk->methods->mt_wled = "WLED"; - /* M2N, S1N and W3V have a usable WLED */ - else if (asus_info) { - if (strncmp(asus_info->oem_table_id, "L1", 2) == 0) - hotk->methods->mled_status = NULL; - /* S1300A reports L84F, but L1400B too, account for that */ - } - - kfree(model); - - return AE_OK; -} - -static int asus_hotk_check(void) -{ - int result = 0; - - result = acpi_bus_get_status(hotk->device); - if (result) - return result; - - if (hotk->device->status.present) { - result = asus_hotk_get_info(); - } else { - printk(KERN_ERR " Hotkey device not present, aborting\n"); - return -EINVAL; - } - - return result; -} - -static int asus_hotk_found; - -static int asus_hotk_add(struct acpi_device *device) -{ - acpi_status status = AE_OK; - int result; - - printk(KERN_NOTICE "Asus Laptop ACPI Extras version %s\n", - ASUS_ACPI_VERSION); - - hotk = kzalloc(sizeof(struct asus_hotk), GFP_KERNEL); - if (!hotk) - return -ENOMEM; - - hotk->handle = device->handle; - strcpy(acpi_device_name(device), ACPI_HOTK_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_HOTK_CLASS); - device->driver_data = hotk; - hotk->device = device; - - result = asus_hotk_check(); - if (result) - goto end; - - result = asus_hotk_add_fs(device); - if (result) - goto end; - - /* For laptops without GPLV: init the hotk->brightness value */ - if ((!hotk->methods->brightness_get) - && (!hotk->methods->brightness_status) - && (hotk->methods->brightness_up && hotk->methods->brightness_down)) { - status = - acpi_evaluate_object(NULL, hotk->methods->brightness_down, - NULL, NULL); - if (ACPI_FAILURE(status)) - printk(KERN_WARNING " Error changing brightness\n"); - else { - status = - acpi_evaluate_object(NULL, - hotk->methods->brightness_up, - NULL, NULL); - if (ACPI_FAILURE(status)) - printk(KERN_WARNING " Strange, error changing" - " brightness\n"); - } - } - - asus_hotk_found = 1; - - /* LED display is off by default */ - hotk->ledd_status = 0xFFF; - -end: - if (result) - kfree(hotk); - - return result; -} - -static int asus_hotk_remove(struct acpi_device *device, int type) -{ - asus_hotk_remove_fs(device); - - kfree(hotk); - - return 0; -} - -static struct backlight_ops asus_backlight_data = { - .get_brightness = read_brightness, - .update_status = set_brightness_status, -}; - -static void asus_acpi_exit(void) -{ - if (asus_backlight_device) - backlight_device_unregister(asus_backlight_device); - - acpi_bus_unregister_driver(&asus_hotk_driver); - remove_proc_entry(PROC_ASUS, acpi_root_dir); - - return; -} - -static int __init asus_acpi_init(void) -{ - int result; - - result = acpi_bus_register_driver(&asus_hotk_driver); - if (result < 0) - return result; - - asus_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir); - if (!asus_proc_dir) { - printk(KERN_ERR "Asus ACPI: Unable to create /proc entry\n"); - acpi_bus_unregister_driver(&asus_hotk_driver); - return -ENODEV; - } - - /* - * This is a bit of a kludge. We only want this module loaded - * for ASUS systems, but there's currently no way to probe the - * ACPI namespace for ASUS HIDs. So we just return failure if - * we didn't find one, which will cause the module to be - * unloaded. - */ - if (!asus_hotk_found) { - acpi_bus_unregister_driver(&asus_hotk_driver); - remove_proc_entry(PROC_ASUS, acpi_root_dir); - return -ENODEV; - } - - asus_backlight_device = backlight_device_register("asus", NULL, NULL, - &asus_backlight_data); - if (IS_ERR(asus_backlight_device)) { - printk(KERN_ERR "Could not register asus backlight device\n"); - asus_backlight_device = NULL; - asus_acpi_exit(); - return -ENODEV; - } - asus_backlight_device->props.max_brightness = 15; - - return 0; -} - -module_init(asus_acpi_init); -module_exit(asus_acpi_exit); diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index ed90082cdf1..70d355a9ae2 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -19,20 +19,32 @@ #include <linux/init.h> #include <linux/module.h> +#include <linux/slab.h> #include <linux/workqueue.h> -#include <acpi/acpi_drivers.h> +#include <linux/acpi.h> #include <linux/backlight.h> #include <linux/input.h> +#include <linux/rfkill.h> MODULE_LICENSE("GPL"); - struct cmpc_accel { int sensitivity; + int g_select; + int inputdev_state; }; +#define CMPC_ACCEL_DEV_STATE_CLOSED 0 +#define CMPC_ACCEL_DEV_STATE_OPEN 1 + #define CMPC_ACCEL_SENSITIVITY_DEFAULT 5 +#define CMPC_ACCEL_G_SELECT_DEFAULT 0 +#define CMPC_ACCEL_HID "ACCE0000" +#define CMPC_ACCEL_HID_V4 "ACCE0001" +#define CMPC_TABLET_HID "TBLT0000" +#define CMPC_IPML_HID "IPML200" +#define CMPC_KEYS_HID "FNBT0000" /* * Generic input device code. @@ -69,7 +81,393 @@ static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) } /* - * Accelerometer code. + * Accelerometer code for Classmate V4 + */ +static acpi_status cmpc_start_accel_v4(acpi_handle handle) +{ + union acpi_object param[4]; + struct acpi_object_list input; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x3; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = 0; + param[2].type = ACPI_TYPE_INTEGER; + param[2].integer.value = 0; + param[3].type = ACPI_TYPE_INTEGER; + param[3].integer.value = 0; + input.count = 4; + input.pointer = param; + status = acpi_evaluate_object(handle, "ACMD", &input, NULL); + return status; +} + +static acpi_status cmpc_stop_accel_v4(acpi_handle handle) +{ + union acpi_object param[4]; + struct acpi_object_list input; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x4; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = 0; + param[2].type = ACPI_TYPE_INTEGER; + param[2].integer.value = 0; + param[3].type = ACPI_TYPE_INTEGER; + param[3].integer.value = 0; + input.count = 4; + input.pointer = param; + status = acpi_evaluate_object(handle, "ACMD", &input, NULL); + return status; +} + +static acpi_status cmpc_accel_set_sensitivity_v4(acpi_handle handle, int val) +{ + union acpi_object param[4]; + struct acpi_object_list input; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x02; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = val; + param[2].type = ACPI_TYPE_INTEGER; + param[2].integer.value = 0; + param[3].type = ACPI_TYPE_INTEGER; + param[3].integer.value = 0; + input.count = 4; + input.pointer = param; + return acpi_evaluate_object(handle, "ACMD", &input, NULL); +} + +static acpi_status cmpc_accel_set_g_select_v4(acpi_handle handle, int val) +{ + union acpi_object param[4]; + struct acpi_object_list input; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x05; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = val; + param[2].type = ACPI_TYPE_INTEGER; + param[2].integer.value = 0; + param[3].type = ACPI_TYPE_INTEGER; + param[3].integer.value = 0; + input.count = 4; + input.pointer = param; + return acpi_evaluate_object(handle, "ACMD", &input, NULL); +} + +static acpi_status cmpc_get_accel_v4(acpi_handle handle, + int16_t *x, + int16_t *y, + int16_t *z) +{ + union acpi_object param[4]; + struct acpi_object_list input; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + int16_t *locs; + acpi_status status; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0x01; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = 0; + param[2].type = ACPI_TYPE_INTEGER; + param[2].integer.value = 0; + param[3].type = ACPI_TYPE_INTEGER; + param[3].integer.value = 0; + input.count = 4; + input.pointer = param; + status = acpi_evaluate_object(handle, "ACMD", &input, &output); + if (ACPI_SUCCESS(status)) { + union acpi_object *obj; + obj = output.pointer; + locs = (int16_t *) obj->buffer.pointer; + *x = locs[0]; + *y = locs[1]; + *z = locs[2]; + kfree(output.pointer); + } + return status; +} + +static void cmpc_accel_handler_v4(struct acpi_device *dev, u32 event) +{ + if (event == 0x81) { + int16_t x, y, z; + acpi_status status; + + status = cmpc_get_accel_v4(dev->handle, &x, &y, &z); + if (ACPI_SUCCESS(status)) { + struct input_dev *inputdev = dev_get_drvdata(&dev->dev); + + input_report_abs(inputdev, ABS_X, x); + input_report_abs(inputdev, ABS_Y, y); + input_report_abs(inputdev, ABS_Z, z); + input_sync(inputdev); + } + } +} + +static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi; + struct input_dev *inputdev; + struct cmpc_accel *accel; + + acpi = to_acpi_device(dev); + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + return sprintf(buf, "%d\n", accel->sensitivity); +} + +static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi; + struct input_dev *inputdev; + struct cmpc_accel *accel; + unsigned long sensitivity; + int r; + + acpi = to_acpi_device(dev); + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + r = kstrtoul(buf, 0, &sensitivity); + if (r) + return r; + + /* sensitivity must be between 1 and 127 */ + if (sensitivity < 1 || sensitivity > 127) + return -EINVAL; + + accel->sensitivity = sensitivity; + cmpc_accel_set_sensitivity_v4(acpi->handle, sensitivity); + + return strnlen(buf, count); +} + +static struct device_attribute cmpc_accel_sensitivity_attr_v4 = { + .attr = { .name = "sensitivity", .mode = 0660 }, + .show = cmpc_accel_sensitivity_show_v4, + .store = cmpc_accel_sensitivity_store_v4 +}; + +static ssize_t cmpc_accel_g_select_show_v4(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi; + struct input_dev *inputdev; + struct cmpc_accel *accel; + + acpi = to_acpi_device(dev); + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + return sprintf(buf, "%d\n", accel->g_select); +} + +static ssize_t cmpc_accel_g_select_store_v4(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi; + struct input_dev *inputdev; + struct cmpc_accel *accel; + unsigned long g_select; + int r; + + acpi = to_acpi_device(dev); + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + r = kstrtoul(buf, 0, &g_select); + if (r) + return r; + + /* 0 means 1.5g, 1 means 6g, everything else is wrong */ + if (g_select != 0 && g_select != 1) + return -EINVAL; + + accel->g_select = g_select; + cmpc_accel_set_g_select_v4(acpi->handle, g_select); + + return strnlen(buf, count); +} + +static struct device_attribute cmpc_accel_g_select_attr_v4 = { + .attr = { .name = "g_select", .mode = 0660 }, + .show = cmpc_accel_g_select_show_v4, + .store = cmpc_accel_g_select_store_v4 +}; + +static int cmpc_accel_open_v4(struct input_dev *input) +{ + struct acpi_device *acpi; + struct cmpc_accel *accel; + + acpi = to_acpi_device(input->dev.parent); + accel = dev_get_drvdata(&input->dev); + + cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); + cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select); + + if (ACPI_SUCCESS(cmpc_start_accel_v4(acpi->handle))) { + accel->inputdev_state = CMPC_ACCEL_DEV_STATE_OPEN; + return 0; + } + return -EIO; +} + +static void cmpc_accel_close_v4(struct input_dev *input) +{ + struct acpi_device *acpi; + struct cmpc_accel *accel; + + acpi = to_acpi_device(input->dev.parent); + accel = dev_get_drvdata(&input->dev); + + cmpc_stop_accel_v4(acpi->handle); + accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; +} + +static void cmpc_accel_idev_init_v4(struct input_dev *inputdev) +{ + set_bit(EV_ABS, inputdev->evbit); + input_set_abs_params(inputdev, ABS_X, -255, 255, 16, 0); + input_set_abs_params(inputdev, ABS_Y, -255, 255, 16, 0); + input_set_abs_params(inputdev, ABS_Z, -255, 255, 16, 0); + inputdev->open = cmpc_accel_open_v4; + inputdev->close = cmpc_accel_close_v4; +} + +#ifdef CONFIG_PM_SLEEP +static int cmpc_accel_suspend_v4(struct device *dev) +{ + struct input_dev *inputdev; + struct cmpc_accel *accel; + + inputdev = dev_get_drvdata(dev); + accel = dev_get_drvdata(&inputdev->dev); + + if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) + return cmpc_stop_accel_v4(to_acpi_device(dev)->handle); + + return 0; +} + +static int cmpc_accel_resume_v4(struct device *dev) +{ + struct input_dev *inputdev; + struct cmpc_accel *accel; + + inputdev = dev_get_drvdata(dev); + accel = dev_get_drvdata(&inputdev->dev); + + if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) { + cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle, + accel->sensitivity); + cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle, + accel->g_select); + + if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle))) + return -EIO; + } + + return 0; +} +#endif + +static int cmpc_accel_add_v4(struct acpi_device *acpi) +{ + int error; + struct input_dev *inputdev; + struct cmpc_accel *accel; + + accel = kmalloc(sizeof(*accel), GFP_KERNEL); + if (!accel) + return -ENOMEM; + + accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; + + accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; + cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); + + error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); + if (error) + goto failed_sensitivity; + + accel->g_select = CMPC_ACCEL_G_SELECT_DEFAULT; + cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select); + + error = device_create_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); + if (error) + goto failed_g_select; + + error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel_v4", + cmpc_accel_idev_init_v4); + if (error) + goto failed_input; + + inputdev = dev_get_drvdata(&acpi->dev); + dev_set_drvdata(&inputdev->dev, accel); + + return 0; + +failed_input: + device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); +failed_g_select: + device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); +failed_sensitivity: + kfree(accel); + return error; +} + +static int cmpc_accel_remove_v4(struct acpi_device *acpi) +{ + struct input_dev *inputdev; + struct cmpc_accel *accel; + + inputdev = dev_get_drvdata(&acpi->dev); + accel = dev_get_drvdata(&inputdev->dev); + + device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); + device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); + return cmpc_remove_acpi_notify_device(acpi); +} + +static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4, + cmpc_accel_resume_v4); + +static const struct acpi_device_id cmpc_accel_device_ids_v4[] = { + {CMPC_ACCEL_HID_V4, 0}, + {"", 0} +}; + +static struct acpi_driver cmpc_accel_acpi_driver_v4 = { + .owner = THIS_MODULE, + .name = "cmpc_accel_v4", + .class = "cmpc_accel_v4", + .ids = cmpc_accel_device_ids_v4, + .ops = { + .add = cmpc_accel_add_v4, + .remove = cmpc_accel_remove_v4, + .notify = cmpc_accel_handler_v4, + }, + .drv.pm = &cmpc_accel_pm, +}; + + +/* + * Accelerometer code for Classmate versions prior to V4 */ static acpi_status cmpc_start_accel(acpi_handle handle) { @@ -191,7 +589,7 @@ static ssize_t cmpc_accel_sensitivity_store(struct device *dev, inputdev = dev_get_drvdata(&acpi->dev); accel = dev_get_drvdata(&inputdev->dev); - r = strict_strtoul(buf, 0, &sensitivity); + r = kstrtoul(buf, 0, &sensitivity); if (r) return r; @@ -201,7 +599,7 @@ static ssize_t cmpc_accel_sensitivity_store(struct device *dev, return strnlen(buf, count); } -struct device_attribute cmpc_accel_sensitivity_attr = { +static struct device_attribute cmpc_accel_sensitivity_attr = { .attr = { .name = "sensitivity", .mode = 0660 }, .show = cmpc_accel_sensitivity_show, .store = cmpc_accel_sensitivity_store @@ -269,7 +667,7 @@ failed_file: return error; } -static int cmpc_accel_remove(struct acpi_device *acpi, int type) +static int cmpc_accel_remove(struct acpi_device *acpi) { struct input_dev *inputdev; struct cmpc_accel *accel; @@ -282,10 +680,9 @@ static int cmpc_accel_remove(struct acpi_device *acpi, int type) } static const struct acpi_device_id cmpc_accel_device_ids[] = { - {"ACCE0000", 0}, + {CMPC_ACCEL_HID, 0}, {"", 0} }; -MODULE_DEVICE_TABLE(acpi, cmpc_accel_device_ids); static struct acpi_driver cmpc_accel_acpi_driver = { .owner = THIS_MODULE, @@ -327,8 +724,10 @@ static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) struct input_dev *inputdev = dev_get_drvdata(&dev->dev); if (event == 0x81) { - if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) + if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) { input_report_switch(inputdev, SW_TABLET_MODE, !val); + input_sync(inputdev); + } } } @@ -341,8 +740,10 @@ static void cmpc_tablet_idev_init(struct input_dev *inputdev) set_bit(SW_TABLET_MODE, inputdev->swbit); acpi = to_acpi_device(inputdev->dev.parent); - if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) + if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) { input_report_switch(inputdev, SW_TABLET_MODE, !val); + input_sync(inputdev); + } } static int cmpc_tablet_add(struct acpi_device *acpi) @@ -351,25 +752,31 @@ static int cmpc_tablet_add(struct acpi_device *acpi) cmpc_tablet_idev_init); } -static int cmpc_tablet_remove(struct acpi_device *acpi, int type) +static int cmpc_tablet_remove(struct acpi_device *acpi) { return cmpc_remove_acpi_notify_device(acpi); } -static int cmpc_tablet_resume(struct acpi_device *acpi) +#ifdef CONFIG_PM_SLEEP +static int cmpc_tablet_resume(struct device *dev) { - struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); + struct input_dev *inputdev = dev_get_drvdata(dev); + unsigned long long val = 0; - if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) + if (ACPI_SUCCESS(cmpc_get_tablet(to_acpi_device(dev)->handle, &val))) { input_report_switch(inputdev, SW_TABLET_MODE, !val); + input_sync(inputdev); + } return 0; } +#endif + +static SIMPLE_DEV_PM_OPS(cmpc_tablet_pm, NULL, cmpc_tablet_resume); static const struct acpi_device_id cmpc_tablet_device_ids[] = { - {"TBLT0000", 0}, + {CMPC_TABLET_HID, 0}, {"", 0} }; -MODULE_DEVICE_TABLE(acpi, cmpc_tablet_device_ids); static struct acpi_driver cmpc_tablet_acpi_driver = { .owner = THIS_MODULE, @@ -379,9 +786,9 @@ static struct acpi_driver cmpc_tablet_acpi_driver = { .ops = { .add = cmpc_tablet_add, .remove = cmpc_tablet_remove, - .resume = cmpc_tablet_resume, .notify = cmpc_tablet_handler, - } + }, + .drv.pm = &cmpc_tablet_pm, }; @@ -452,45 +859,177 @@ static int cmpc_bl_update_status(struct backlight_device *bd) return -1; } -static struct backlight_ops cmpc_bl_ops = { +static const struct backlight_ops cmpc_bl_ops = { .get_brightness = cmpc_bl_get_brightness, .update_status = cmpc_bl_update_status }; -static int cmpc_bl_add(struct acpi_device *acpi) +/* + * RFKILL code. + */ + +static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle, + unsigned long long *value) +{ + union acpi_object param; + struct acpi_object_list input; + unsigned long long output; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = 0xC1; + input.count = 1; + input.pointer = ¶m; + status = acpi_evaluate_integer(handle, "GRDI", &input, &output); + if (ACPI_SUCCESS(status)) + *value = output; + return status; +} + +static acpi_status cmpc_set_rfkill_wlan(acpi_handle handle, + unsigned long long value) +{ + union acpi_object param[2]; + struct acpi_object_list input; + acpi_status status; + unsigned long long output; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = 0xC1; + param[1].type = ACPI_TYPE_INTEGER; + param[1].integer.value = value; + input.count = 2; + input.pointer = param; + status = acpi_evaluate_integer(handle, "GWRI", &input, &output); + return status; +} + +static void cmpc_rfkill_query(struct rfkill *rfkill, void *data) +{ + acpi_status status; + acpi_handle handle; + unsigned long long state; + bool blocked; + + handle = data; + status = cmpc_get_rfkill_wlan(handle, &state); + if (ACPI_SUCCESS(status)) { + blocked = state & 1 ? false : true; + rfkill_set_sw_state(rfkill, blocked); + } +} + +static int cmpc_rfkill_block(void *data, bool blocked) { + acpi_status status; + acpi_handle handle; + unsigned long long state; + bool is_blocked; + + handle = data; + status = cmpc_get_rfkill_wlan(handle, &state); + if (ACPI_FAILURE(status)) + return -ENODEV; + /* Check if we really need to call cmpc_set_rfkill_wlan */ + is_blocked = state & 1 ? false : true; + if (is_blocked != blocked) { + state = blocked ? 0 : 1; + status = cmpc_set_rfkill_wlan(handle, state); + if (ACPI_FAILURE(status)) + return -ENODEV; + } + return 0; +} + +static const struct rfkill_ops cmpc_rfkill_ops = { + .query = cmpc_rfkill_query, + .set_block = cmpc_rfkill_block, +}; + +/* + * Common backlight and rfkill code. + */ + +struct ipml200_dev { struct backlight_device *bd; + struct rfkill *rf; +}; + +static int cmpc_ipml_add(struct acpi_device *acpi) +{ + int retval; + struct ipml200_dev *ipml; + struct backlight_properties props; + + ipml = kmalloc(sizeof(*ipml), GFP_KERNEL); + if (ipml == NULL) + return -ENOMEM; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = 7; + ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev, + acpi->handle, &cmpc_bl_ops, + &props); + if (IS_ERR(ipml->bd)) { + retval = PTR_ERR(ipml->bd); + goto out_bd; + } + + ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN, + &cmpc_rfkill_ops, acpi->handle); + /* + * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV). + * This is OK, however, since all other uses of the device will not + * derefence it. + */ + if (ipml->rf) { + retval = rfkill_register(ipml->rf); + if (retval) { + rfkill_destroy(ipml->rf); + ipml->rf = NULL; + } + } - bd = backlight_device_register("cmpc_bl", &acpi->dev, - acpi->handle, &cmpc_bl_ops); - bd->props.max_brightness = 7; - dev_set_drvdata(&acpi->dev, bd); + dev_set_drvdata(&acpi->dev, ipml); return 0; + +out_bd: + kfree(ipml); + return retval; } -static int cmpc_bl_remove(struct acpi_device *acpi, int type) +static int cmpc_ipml_remove(struct acpi_device *acpi) { - struct backlight_device *bd; + struct ipml200_dev *ipml; + + ipml = dev_get_drvdata(&acpi->dev); + + backlight_device_unregister(ipml->bd); + + if (ipml->rf) { + rfkill_unregister(ipml->rf); + rfkill_destroy(ipml->rf); + } + + kfree(ipml); - bd = dev_get_drvdata(&acpi->dev); - backlight_device_unregister(bd); return 0; } -static const struct acpi_device_id cmpc_device_ids[] = { - {"IPML200", 0}, +static const struct acpi_device_id cmpc_ipml_device_ids[] = { + {CMPC_IPML_HID, 0}, {"", 0} }; -MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); -static struct acpi_driver cmpc_bl_acpi_driver = { +static struct acpi_driver cmpc_ipml_acpi_driver = { .owner = THIS_MODULE, .name = "cmpc", .class = "cmpc", - .ids = cmpc_device_ids, + .ids = cmpc_ipml_device_ids, .ops = { - .add = cmpc_bl_add, - .remove = cmpc_bl_remove + .add = cmpc_ipml_add, + .remove = cmpc_ipml_remove } }; @@ -505,6 +1044,10 @@ static int cmpc_keys_codes[] = { KEY_BRIGHTNESSDOWN, KEY_BRIGHTNESSUP, KEY_VENDOR, + KEY_UNKNOWN, + KEY_CAMERA, + KEY_BACK, + KEY_FORWARD, KEY_MAX }; @@ -515,8 +1058,9 @@ static void cmpc_keys_handler(struct acpi_device *dev, u32 event) if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes)) code = cmpc_keys_codes[event & 0x0F]; - inputdev = dev_get_drvdata(&dev->dev);; + inputdev = dev_get_drvdata(&dev->dev); input_report_key(inputdev, code, !(event & 0x10)); + input_sync(inputdev); } static void cmpc_keys_idev_init(struct input_dev *inputdev) @@ -534,16 +1078,15 @@ static int cmpc_keys_add(struct acpi_device *acpi) cmpc_keys_idev_init); } -static int cmpc_keys_remove(struct acpi_device *acpi, int type) +static int cmpc_keys_remove(struct acpi_device *acpi) { return cmpc_remove_acpi_notify_device(acpi); } static const struct acpi_device_id cmpc_keys_device_ids[] = { - {"FnBT0000", 0}, + {CMPC_KEYS_HID, 0}, {"", 0} }; -MODULE_DEVICE_TABLE(acpi, cmpc_keys_device_ids); static struct acpi_driver cmpc_keys_acpi_driver = { .owner = THIS_MODULE, @@ -570,7 +1113,7 @@ static int cmpc_init(void) if (r) goto failed_keys; - r = acpi_bus_register_driver(&cmpc_bl_acpi_driver); + r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver); if (r) goto failed_bl; @@ -582,13 +1125,20 @@ static int cmpc_init(void) if (r) goto failed_accel; + r = acpi_bus_register_driver(&cmpc_accel_acpi_driver_v4); + if (r) + goto failed_accel_v4; + return r; +failed_accel_v4: + acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); + failed_accel: acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); failed_tablet: - acpi_bus_unregister_driver(&cmpc_bl_acpi_driver); + acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); failed_bl: acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); @@ -599,11 +1149,23 @@ failed_keys: static void cmpc_exit(void) { + acpi_bus_unregister_driver(&cmpc_accel_acpi_driver_v4); acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); - acpi_bus_unregister_driver(&cmpc_bl_acpi_driver); + acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); } module_init(cmpc_init); module_exit(cmpc_exit); + +static const struct acpi_device_id cmpc_device_ids[] = { + {CMPC_ACCEL_HID, 0}, + {CMPC_ACCEL_HID_V4, 0}, + {CMPC_TABLET_HID, 0}, + {CMPC_IPML_HID, 0}, + {CMPC_KEYS_HID, 0}, + {"", 0} +}; + +MODULE_DEVICE_TABLE(acpi, cmpc_device_ids); diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index 1a387e79f71..7297df2ebf5 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c @@ -24,26 +24,52 @@ */ /* - * comapl-laptop.c - Compal laptop support. + * compal-laptop.c - Compal laptop support. * * This driver exports a few files in /sys/devices/platform/compal-laptop/: + * wake_up_XXX Whether or not we listen to such wake up events (rw) * - * wlan - wlan subsystem state: contains 0 or 1 (rw) + * In addition to these platform device attributes the driver + * registers itself in the Linux backlight control, power_supply, rfkill + * and hwmon subsystem and is available to userspace under: * - * bluetooth - Bluetooth subsystem state: contains 0 or 1 (rw) + * /sys/class/backlight/compal-laptop/ + * /sys/class/power_supply/compal-laptop/ + * /sys/class/rfkill/rfkillX/ + * /sys/class/hwmon/hwmonX/ * - * raw - raw value taken from embedded controller register (ro) + * Notes on the power_supply battery interface: + * - the "minimum" design voltage is *the* design voltage + * - the ambient temperature is the average battery temperature + * and the value is an educated guess (see commented code below) * - * In addition to these platform device attributes the driver - * registers itself in the Linux backlight control subsystem and is - * available to userspace under /sys/class/backlight/compal-laptop/. * * This driver might work on other laptops produced by Compal. If you * want to try it you can pass force=1 as argument to the module which * will force it to load even when the DMI data doesn't identify the - * laptop as FL9x. + * laptop as compatible. + * + * Lots of data available at: + * http://service1.marasst.com/Compal/JHL90_91/Service%20Manual/ + * JHL90%20service%20manual-Final-0725.pdf + * + * + * + * Support for the Compal JHL90 added by Roald Frederickx + * (roald.frederickx@gmail.com): + * Driver got large revision. Added functionalities: backlight + * power, wake_on_XXX, a hwmon and power_supply interface. + * + * In case this gets merged into the kernel source: I want to dedicate this + * to Kasper Meerts, the awesome guy who showed me Linux and C! */ +/* NOTE: currently the wake_on_XXX, hwmon and power_supply interfaces are + * only enabled on a JHL90 board until it is verified that they work on the + * other boards too. See the extra_features variable. */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> @@ -51,222 +77,694 @@ #include <linux/dmi.h> #include <linux/backlight.h> #include <linux/platform_device.h> +#include <linux/rfkill.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/power_supply.h> +#include <linux/fb.h> + + +/* ======= */ +/* Defines */ +/* ======= */ +#define DRIVER_NAME "compal-laptop" +#define DRIVER_VERSION "0.2.7" + +#define BACKLIGHT_LEVEL_ADDR 0xB9 +#define BACKLIGHT_LEVEL_MAX 7 +#define BACKLIGHT_STATE_ADDR 0x59 +#define BACKLIGHT_STATE_ON_DATA 0xE1 +#define BACKLIGHT_STATE_OFF_DATA 0xE2 + +#define WAKE_UP_ADDR 0xA4 +#define WAKE_UP_PME (1 << 0) +#define WAKE_UP_MODEM (1 << 1) +#define WAKE_UP_LAN (1 << 2) +#define WAKE_UP_WLAN (1 << 4) +#define WAKE_UP_KEY (1 << 6) +#define WAKE_UP_MOUSE (1 << 7) + +#define WIRELESS_ADDR 0xBB +#define WIRELESS_WLAN (1 << 0) +#define WIRELESS_BT (1 << 1) +#define WIRELESS_WLAN_EXISTS (1 << 2) +#define WIRELESS_BT_EXISTS (1 << 3) +#define WIRELESS_KILLSWITCH (1 << 4) + +#define PWM_ADDRESS 0x46 +#define PWM_DISABLE_ADDR 0x59 +#define PWM_DISABLE_DATA 0xA5 +#define PWM_ENABLE_ADDR 0x59 +#define PWM_ENABLE_DATA 0xA8 + +#define FAN_ADDRESS 0x46 +#define FAN_DATA 0x81 +#define FAN_FULL_ON_CMD 0x59 /* Doesn't seem to work. Just */ +#define FAN_FULL_ON_ENABLE 0x76 /* force the pwm signal to its */ +#define FAN_FULL_ON_DISABLE 0x77 /* maximum value instead */ + +#define TEMP_CPU 0xB0 +#define TEMP_CPU_LOCAL 0xB1 +#define TEMP_CPU_DTS 0xB5 +#define TEMP_NORTHBRIDGE 0xB6 +#define TEMP_VGA 0xB4 +#define TEMP_SKIN 0xB2 + +#define BAT_MANUFACTURER_NAME_ADDR 0x10 +#define BAT_MANUFACTURER_NAME_LEN 9 +#define BAT_MODEL_NAME_ADDR 0x19 +#define BAT_MODEL_NAME_LEN 6 +#define BAT_SERIAL_NUMBER_ADDR 0xC4 +#define BAT_SERIAL_NUMBER_LEN 5 +#define BAT_CHARGE_NOW 0xC2 +#define BAT_CHARGE_DESIGN 0xCA +#define BAT_VOLTAGE_NOW 0xC6 +#define BAT_VOLTAGE_DESIGN 0xC8 +#define BAT_CURRENT_NOW 0xD0 +#define BAT_CURRENT_AVG 0xD2 +#define BAT_POWER 0xD4 +#define BAT_CAPACITY 0xCE +#define BAT_TEMP 0xD6 +#define BAT_TEMP_AVG 0xD7 +#define BAT_STATUS0 0xC1 +#define BAT_STATUS1 0xF0 +#define BAT_STATUS2 0xF1 +#define BAT_STOP_CHARGE1 0xF2 +#define BAT_STOP_CHARGE2 0xF3 + +#define BAT_S0_DISCHARGE (1 << 0) +#define BAT_S0_DISCHRG_CRITICAL (1 << 2) +#define BAT_S0_LOW (1 << 3) +#define BAT_S0_CHARGING (1 << 1) +#define BAT_S0_AC (1 << 7) +#define BAT_S1_EXISTS (1 << 0) +#define BAT_S1_FULL (1 << 1) +#define BAT_S1_EMPTY (1 << 2) +#define BAT_S1_LiION_OR_NiMH (1 << 7) +#define BAT_S2_LOW_LOW (1 << 0) +#define BAT_STOP_CHRG1_BAD_CELL (1 << 1) +#define BAT_STOP_CHRG1_COMM_FAIL (1 << 2) +#define BAT_STOP_CHRG1_OVERVOLTAGE (1 << 6) +#define BAT_STOP_CHRG1_OVERTEMPERATURE (1 << 7) + + +/* ======= */ +/* Structs */ +/* ======= */ +struct compal_data{ + /* Fan control */ + int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by motherboard */ + unsigned char curr_pwm; + + /* Power supply */ + struct power_supply psy; + struct power_supply_info psy_info; + char bat_model_name[BAT_MODEL_NAME_LEN + 1]; + char bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN + 1]; + char bat_serial_number[BAT_SERIAL_NUMBER_LEN + 1]; +}; -#define COMPAL_DRIVER_VERSION "0.2.6" - -#define COMPAL_LCD_LEVEL_MAX 8 - -#define COMPAL_EC_COMMAND_WIRELESS 0xBB -#define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9 - -#define KILLSWITCH_MASK 0x10 -#define WLAN_MASK 0x01 -#define BT_MASK 0x02 -static int force; +/* =============== */ +/* General globals */ +/* =============== */ +static bool force; module_param(force, bool, 0); MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); -/* Hardware access */ +/* Support for the wake_on_XXX, hwmon and power_supply interface. Currently + * only gets enabled on a JHL90 board. Might work with the others too */ +static bool extra_features; + +/* Nasty stuff. For some reason the fan control is very un-linear. I've + * come up with these values by looping through the possible inputs and + * watching the output of address 0x4F (do an ec_transaction writing 0x33 + * into 0x4F and read a few bytes from the output, like so: + * u8 writeData = 0x33; + * ec_transaction(0x4F, &writeData, 1, buffer, 32); + * That address is labeled "fan1 table information" in the service manual. + * It should be clear which value in 'buffer' changes). This seems to be + * related to fan speed. It isn't a proper 'realtime' fan speed value + * though, because physically stopping or speeding up the fan doesn't + * change it. It might be the average voltage or current of the pwm output. + * Nevertheless, it is more fine-grained than the actual RPM reading */ +static const unsigned char pwm_lookup_table[256] = { + 0, 0, 0, 1, 1, 1, 2, 253, 254, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, + 7, 7, 7, 8, 86, 86, 9, 9, 9, 10, 10, 10, 11, 92, 92, 12, 12, 95, + 13, 66, 66, 14, 14, 98, 15, 15, 15, 16, 16, 67, 17, 17, 72, 18, 70, + 75, 19, 90, 90, 73, 73, 73, 21, 21, 91, 91, 91, 96, 23, 94, 94, 94, + 94, 94, 94, 94, 94, 94, 94, 141, 141, 238, 223, 192, 139, 139, 139, + 139, 139, 142, 142, 142, 142, 142, 78, 78, 78, 78, 78, 76, 76, 76, + 76, 76, 79, 79, 79, 79, 79, 79, 79, 20, 20, 20, 20, 20, 22, 22, 22, + 22, 22, 24, 24, 24, 24, 24, 24, 219, 219, 219, 219, 219, 219, 219, + 219, 27, 27, 188, 188, 28, 28, 28, 29, 186, 186, 186, 186, 186, + 186, 186, 186, 186, 186, 31, 31, 31, 31, 31, 32, 32, 32, 41, 33, + 33, 33, 33, 33, 252, 252, 34, 34, 34, 43, 35, 35, 35, 36, 36, 38, + 206, 206, 206, 206, 206, 206, 206, 206, 206, 37, 37, 37, 46, 46, + 47, 47, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 48, 48, + 48, 48, 48, 40, 40, 40, 49, 42, 42, 42, 42, 42, 42, 42, 42, 44, + 189, 189, 189, 189, 54, 54, 45, 45, 45, 45, 45, 45, 45, 45, 251, + 191, 199, 199, 199, 199, 199, 215, 215, 215, 215, 187, 187, 187, + 187, 187, 193, 50 +}; -static int set_lcd_level(int level) -{ - if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX) - return -EINVAL; - ec_write(COMPAL_EC_COMMAND_LCD_LEVEL, level); - return 0; + +/* ========================= */ +/* Hardware access functions */ +/* ========================= */ +/* General access */ +static u8 ec_read_u8(u8 addr) +{ + u8 value; + ec_read(addr, &value); + return value; } -static int get_lcd_level(void) +static s8 ec_read_s8(u8 addr) { - u8 result; + return (s8)ec_read_u8(addr); +} - ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result); +static u16 ec_read_u16(u8 addr) +{ + int hi, lo; + lo = ec_read_u8(addr); + hi = ec_read_u8(addr + 1); + return (hi << 8) + lo; +} - return (int) result; +static s16 ec_read_s16(u8 addr) +{ + return (s16) ec_read_u16(addr); } -static int set_wlan_state(int state) +static void ec_read_sequence(u8 addr, u8 *buf, int len) { - u8 result, value; + int i; + for (i = 0; i < len; i++) + ec_read(addr + i, buf + i); +} - ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); - if ((result & KILLSWITCH_MASK) == 0) +/* Backlight access */ +static int set_backlight_level(int level) +{ + if (level < 0 || level > BACKLIGHT_LEVEL_MAX) return -EINVAL; - else { - if (state) - value = (u8) (result | WLAN_MASK); - else - value = (u8) (result & ~WLAN_MASK); - ec_write(COMPAL_EC_COMMAND_WIRELESS, value); - } + + ec_write(BACKLIGHT_LEVEL_ADDR, level); return 0; } -static int set_bluetooth_state(int state) +static int get_backlight_level(void) { - u8 result, value; + return (int) ec_read_u8(BACKLIGHT_LEVEL_ADDR); +} - ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); +static void set_backlight_state(bool on) +{ + u8 data = on ? BACKLIGHT_STATE_ON_DATA : BACKLIGHT_STATE_OFF_DATA; + ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0); +} - if ((result & KILLSWITCH_MASK) == 0) - return -EINVAL; - else { - if (state) - value = (u8) (result | BT_MASK); - else - value = (u8) (result & ~BT_MASK); - ec_write(COMPAL_EC_COMMAND_WIRELESS, value); - } - return 0; +/* Fan control access */ +static void pwm_enable_control(void) +{ + unsigned char writeData = PWM_ENABLE_DATA; + ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0); } -static int get_wireless_state(int *wlan, int *bluetooth) +static void pwm_disable_control(void) { - u8 result; + unsigned char writeData = PWM_DISABLE_DATA; + ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0); +} - ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); +static void set_pwm(int pwm) +{ + ec_transaction(PWM_ADDRESS, &pwm_lookup_table[pwm], 1, NULL, 0); +} + +static int get_fan_rpm(void) +{ + u8 value, data = FAN_DATA; + ec_transaction(FAN_ADDRESS, &data, 1, &value, 1); + return 100 * (int)value; +} - if (wlan) { - if ((result & KILLSWITCH_MASK) == 0) - *wlan = 0; - else - *wlan = result & WLAN_MASK; - } - if (bluetooth) { - if ((result & KILLSWITCH_MASK) == 0) - *bluetooth = 0; - else - *bluetooth = (result & BT_MASK) >> 1; - } - return 0; -} -/* Backlight device stuff */ +/* =================== */ +/* Interface functions */ +/* =================== */ +/* Backlight interface */ static int bl_get_brightness(struct backlight_device *b) { - return get_lcd_level(); + return get_backlight_level(); } - static int bl_update_status(struct backlight_device *b) { - return set_lcd_level(b->props.brightness); + int ret = set_backlight_level(b->props.brightness); + if (ret) + return ret; + + set_backlight_state((b->props.power == FB_BLANK_UNBLANK) + && !(b->props.state & BL_CORE_SUSPENDED) + && !(b->props.state & BL_CORE_FBBLANK)); + return 0; } -static struct backlight_ops compalbl_ops = { +static const struct backlight_ops compalbl_ops = { .get_brightness = bl_get_brightness, .update_status = bl_update_status, }; -static struct backlight_device *compalbl_device; - -/* Platform device */ -static ssize_t show_wlan(struct device *dev, - struct device_attribute *attr, char *buf) +/* Wireless interface */ +static int compal_rfkill_set(void *data, bool blocked) { - int ret, enabled; + unsigned long radio = (unsigned long) data; + u8 result = ec_read_u8(WIRELESS_ADDR); + u8 value; - ret = get_wireless_state(&enabled, NULL); - if (ret < 0) - return ret; + if (!blocked) + value = (u8) (result | radio); + else + value = (u8) (result & ~radio); + ec_write(WIRELESS_ADDR, value); - return sprintf(buf, "%i\n", enabled); + return 0; } -static ssize_t show_raw(struct device *dev, - struct device_attribute *attr, char *buf) +static void compal_rfkill_poll(struct rfkill *rfkill, void *data) { - u8 result; + u8 result = ec_read_u8(WIRELESS_ADDR); + bool hw_blocked = !(result & WIRELESS_KILLSWITCH); + rfkill_set_hw_state(rfkill, hw_blocked); +} + +static const struct rfkill_ops compal_rfkill_ops = { + .poll = compal_rfkill_poll, + .set_block = compal_rfkill_set, +}; - ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); - return sprintf(buf, "%i\n", result); +/* Wake_up interface */ +#define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK) \ +static ssize_t NAME##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \ +} \ +static ssize_t NAME##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + int state; \ + u8 old_val = ec_read_u8(ADDR); \ + if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1)) \ + return -EINVAL; \ + ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK)); \ + return count; \ } -static ssize_t show_bluetooth(struct device *dev, - struct device_attribute *attr, char *buf) +SIMPLE_MASKED_STORE_SHOW(wake_up_pme, WAKE_UP_ADDR, WAKE_UP_PME) +SIMPLE_MASKED_STORE_SHOW(wake_up_modem, WAKE_UP_ADDR, WAKE_UP_MODEM) +SIMPLE_MASKED_STORE_SHOW(wake_up_lan, WAKE_UP_ADDR, WAKE_UP_LAN) +SIMPLE_MASKED_STORE_SHOW(wake_up_wlan, WAKE_UP_ADDR, WAKE_UP_WLAN) +SIMPLE_MASKED_STORE_SHOW(wake_up_key, WAKE_UP_ADDR, WAKE_UP_KEY) +SIMPLE_MASKED_STORE_SHOW(wake_up_mouse, WAKE_UP_ADDR, WAKE_UP_MOUSE) + +/* Fan control interface */ +static ssize_t pwm_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) { - int ret, enabled; + struct compal_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->pwm_enable); +} - ret = get_wireless_state(NULL, &enabled); - if (ret < 0) - return ret; +static ssize_t pwm_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct compal_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + if (val < 0) + return -EINVAL; + + data->pwm_enable = val; + + switch (val) { + case 0: /* Full speed */ + pwm_enable_control(); + set_pwm(255); + break; + case 1: /* As set by pwm1 */ + pwm_enable_control(); + set_pwm(data->curr_pwm); + break; + default: /* Control by motherboard */ + pwm_disable_control(); + break; + } - return sprintf(buf, "%i\n", enabled); + return count; } -static ssize_t store_wlan_state(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) +static ssize_t pwm_show(struct device *dev, struct device_attribute *attr, + char *buf) { - int state, ret; + struct compal_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%hhu\n", data->curr_pwm); +} - if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) +static ssize_t pwm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct compal_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + if (val < 0 || val > 255) return -EINVAL; - ret = set_wlan_state(state); - if (ret < 0) - return ret; + data->curr_pwm = val; + + if (data->pwm_enable != 1) + return count; + set_pwm(val); return count; } -static ssize_t store_bluetooth_state(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) +static ssize_t fan_show(struct device *dev, struct device_attribute *attr, + char *buf) { - int state, ret; + return sprintf(buf, "%d\n", get_fan_rpm()); +} - if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) - return -EINVAL; - ret = set_bluetooth_state(state); - if (ret < 0) - return ret; +/* Temperature interface */ +#define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL) \ +static ssize_t temp_##POSTFIX(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \ +} \ +static ssize_t label_##POSTFIX(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%s\n", LABEL); \ +} - return count; +/* Labels as in service guide */ +TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu, TEMP_CPU, "CPU_TEMP"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_local, TEMP_CPU_LOCAL, "CPU_TEMP_LOCAL"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_DTS, TEMP_CPU_DTS, "CPU_DTS"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(northbridge,TEMP_NORTHBRIDGE,"NorthBridge"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(vga, TEMP_VGA, "VGA_TEMP"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(SKIN, TEMP_SKIN, "SKIN_TEMP90"); + + +/* Power supply interface */ +static int bat_status(void) +{ + u8 status0 = ec_read_u8(BAT_STATUS0); + u8 status1 = ec_read_u8(BAT_STATUS1); + + if (status0 & BAT_S0_CHARGING) + return POWER_SUPPLY_STATUS_CHARGING; + if (status0 & BAT_S0_DISCHARGE) + return POWER_SUPPLY_STATUS_DISCHARGING; + if (status1 & BAT_S1_FULL) + return POWER_SUPPLY_STATUS_FULL; + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static int bat_health(void) +{ + u8 status = ec_read_u8(BAT_STOP_CHARGE1); + + if (status & BAT_STOP_CHRG1_OVERTEMPERATURE) + return POWER_SUPPLY_HEALTH_OVERHEAT; + if (status & BAT_STOP_CHRG1_OVERVOLTAGE) + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + if (status & BAT_STOP_CHRG1_BAD_CELL) + return POWER_SUPPLY_HEALTH_DEAD; + if (status & BAT_STOP_CHRG1_COMM_FAIL) + return POWER_SUPPLY_HEALTH_UNKNOWN; + return POWER_SUPPLY_HEALTH_GOOD; } -static DEVICE_ATTR(bluetooth, 0644, show_bluetooth, store_bluetooth_state); -static DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan_state); -static DEVICE_ATTR(raw, 0444, show_raw, NULL); +static int bat_is_present(void) +{ + u8 status = ec_read_u8(BAT_STATUS2); + return ((status & BAT_S1_EXISTS) != 0); +} + +static int bat_technology(void) +{ + u8 status = ec_read_u8(BAT_STATUS1); + + if (status & BAT_S1_LiION_OR_NiMH) + return POWER_SUPPLY_TECHNOLOGY_LION; + return POWER_SUPPLY_TECHNOLOGY_NiMH; +} + +static int bat_capacity_level(void) +{ + u8 status0 = ec_read_u8(BAT_STATUS0); + u8 status1 = ec_read_u8(BAT_STATUS1); + u8 status2 = ec_read_u8(BAT_STATUS2); + + if (status0 & BAT_S0_DISCHRG_CRITICAL + || status1 & BAT_S1_EMPTY + || status2 & BAT_S2_LOW_LOW) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + if (status0 & BAT_S0_LOW) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + if (status1 & BAT_S1_FULL) + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; +} + +static int bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct compal_data *data; + data = container_of(psy, struct compal_data, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat_status(); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = bat_health(); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = bat_is_present(); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = bat_technology(); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: /* THE design voltage... */ + val->intval = ec_read_u16(BAT_VOLTAGE_DESIGN) * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = ec_read_u16(BAT_VOLTAGE_NOW) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ec_read_s16(BAT_CURRENT_NOW) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = ec_read_s16(BAT_CURRENT_AVG) * 1000; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + val->intval = ec_read_u8(BAT_POWER) * 1000000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = ec_read_u16(BAT_CHARGE_DESIGN) * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = ec_read_u16(BAT_CHARGE_NOW) * 1000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = ec_read_u8(BAT_CAPACITY); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = bat_capacity_level(); + break; + /* It smees that BAT_TEMP_AVG is a (2's complement?) value showing + * the number of degrees, whereas BAT_TEMP is somewhat more + * complicated. It looks like this is a negative nember with a + * 100/256 divider and an offset of 222. Both were determined + * experimentally by comparing BAT_TEMP and BAT_TEMP_AVG. */ + case POWER_SUPPLY_PROP_TEMP: + val->intval = ((222 - (int)ec_read_u8(BAT_TEMP)) * 1000) >> 8; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: /* Ambient, Avg, ... same thing */ + val->intval = ec_read_s8(BAT_TEMP_AVG) * 10; + break; + /* Neither the model name nor manufacturer name work for me. */ + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = data->bat_model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = data->bat_manufacturer_name; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = data->bat_serial_number; + break; + default: + break; + } + return 0; +} + + + -static struct attribute *compal_attributes[] = { - &dev_attr_bluetooth.attr, - &dev_attr_wlan.attr, - &dev_attr_raw.attr, + +/* ============== */ +/* Driver Globals */ +/* ============== */ +static DEVICE_ATTR(wake_up_pme, + 0644, wake_up_pme_show, wake_up_pme_store); +static DEVICE_ATTR(wake_up_modem, + 0644, wake_up_modem_show, wake_up_modem_store); +static DEVICE_ATTR(wake_up_lan, + 0644, wake_up_lan_show, wake_up_lan_store); +static DEVICE_ATTR(wake_up_wlan, + 0644, wake_up_wlan_show, wake_up_wlan_store); +static DEVICE_ATTR(wake_up_key, + 0644, wake_up_key_show, wake_up_key_store); +static DEVICE_ATTR(wake_up_mouse, + 0644, wake_up_mouse_show, wake_up_mouse_store); + +static DEVICE_ATTR(fan1_input, S_IRUGO, fan_show, NULL); +static DEVICE_ATTR(temp1_input, S_IRUGO, temp_cpu, NULL); +static DEVICE_ATTR(temp2_input, S_IRUGO, temp_cpu_local, NULL); +static DEVICE_ATTR(temp3_input, S_IRUGO, temp_cpu_DTS, NULL); +static DEVICE_ATTR(temp4_input, S_IRUGO, temp_northbridge, NULL); +static DEVICE_ATTR(temp5_input, S_IRUGO, temp_vga, NULL); +static DEVICE_ATTR(temp6_input, S_IRUGO, temp_SKIN, NULL); +static DEVICE_ATTR(temp1_label, S_IRUGO, label_cpu, NULL); +static DEVICE_ATTR(temp2_label, S_IRUGO, label_cpu_local, NULL); +static DEVICE_ATTR(temp3_label, S_IRUGO, label_cpu_DTS, NULL); +static DEVICE_ATTR(temp4_label, S_IRUGO, label_northbridge, NULL); +static DEVICE_ATTR(temp5_label, S_IRUGO, label_vga, NULL); +static DEVICE_ATTR(temp6_label, S_IRUGO, label_SKIN, NULL); +static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, pwm_show, pwm_store); +static DEVICE_ATTR(pwm1_enable, + S_IRUGO | S_IWUSR, pwm_enable_show, pwm_enable_store); + +static struct attribute *compal_platform_attrs[] = { + &dev_attr_wake_up_pme.attr, + &dev_attr_wake_up_modem.attr, + &dev_attr_wake_up_lan.attr, + &dev_attr_wake_up_wlan.attr, + &dev_attr_wake_up_key.attr, + &dev_attr_wake_up_mouse.attr, NULL }; +static struct attribute_group compal_platform_attr_group = { + .attrs = compal_platform_attrs +}; -static struct attribute_group compal_attribute_group = { - .attrs = compal_attributes +static struct attribute *compal_hwmon_attrs[] = { + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm1.attr, + &dev_attr_fan1_input.attr, + &dev_attr_temp1_input.attr, + &dev_attr_temp2_input.attr, + &dev_attr_temp3_input.attr, + &dev_attr_temp4_input.attr, + &dev_attr_temp5_input.attr, + &dev_attr_temp6_input.attr, + &dev_attr_temp1_label.attr, + &dev_attr_temp2_label.attr, + &dev_attr_temp3_label.attr, + &dev_attr_temp4_label.attr, + &dev_attr_temp5_label.attr, + &dev_attr_temp6_label.attr, + NULL }; +ATTRIBUTE_GROUPS(compal_hwmon); +static int compal_probe(struct platform_device *); +static int compal_remove(struct platform_device *); static struct platform_driver compal_driver = { .driver = { - .name = "compal-laptop", + .name = DRIVER_NAME, .owner = THIS_MODULE, - } + }, + .probe = compal_probe, + .remove = compal_remove, +}; + +static enum power_supply_property compal_bat_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, }; +static struct backlight_device *compalbl_device; + static struct platform_device *compal_device; -/* Initialization */ +static struct rfkill *wifi_rfkill; +static struct rfkill *bt_rfkill; + + + + + +/* =================================== */ +/* Initialization & clean-up functions */ +/* =================================== */ static int dmi_check_cb(const struct dmi_system_id *id) { - printk(KERN_INFO "compal-laptop: Identified laptop model '%s'.\n", - id->ident); + pr_info("Identified laptop model '%s'\n", id->ident); + extra_features = false; + return 1; +} - return 0; +static int dmi_check_cb_extra(const struct dmi_system_id *id) +{ + pr_info("Identified laptop model '%s', enabling extra features\n", + id->ident); + extra_features = true; + return 1; } static struct dmi_system_id __initdata compal_dmi_table[] = { @@ -310,96 +808,283 @@ static struct dmi_system_id __initdata compal_dmi_table[] = { }, .callback = dmi_check_cb }, + { + .ident = "Dell Mini 9", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"), + }, + .callback = dmi_check_cb + }, + { + .ident = "Dell Mini 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"), + }, + .callback = dmi_check_cb + }, + { + .ident = "Dell Mini 10v", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"), + }, + .callback = dmi_check_cb + }, + { + .ident = "Dell Mini 1012", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"), + }, + .callback = dmi_check_cb + }, + { + .ident = "Dell Inspiron 11z", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"), + }, + .callback = dmi_check_cb + }, + { + .ident = "Dell Mini 12", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"), + }, + .callback = dmi_check_cb + }, + { + .ident = "JHL90", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "JHL90"), + DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"), + }, + .callback = dmi_check_cb_extra + }, + { + .ident = "KHLB2", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "KHLB2"), + DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"), + }, + .callback = dmi_check_cb_extra + }, { } }; +MODULE_DEVICE_TABLE(dmi, compal_dmi_table); + +static void initialize_power_supply_data(struct compal_data *data) +{ + data->psy.name = DRIVER_NAME; + data->psy.type = POWER_SUPPLY_TYPE_BATTERY; + data->psy.properties = compal_bat_properties; + data->psy.num_properties = ARRAY_SIZE(compal_bat_properties); + data->psy.get_property = bat_get_property; + + ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR, + data->bat_manufacturer_name, + BAT_MANUFACTURER_NAME_LEN); + data->bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN] = 0; + + ec_read_sequence(BAT_MODEL_NAME_ADDR, + data->bat_model_name, + BAT_MODEL_NAME_LEN); + data->bat_model_name[BAT_MODEL_NAME_LEN] = 0; + + scnprintf(data->bat_serial_number, BAT_SERIAL_NUMBER_LEN + 1, "%d", + ec_read_u16(BAT_SERIAL_NUMBER_ADDR)); +} + +static void initialize_fan_control_data(struct compal_data *data) +{ + data->pwm_enable = 2; /* Keep motherboard in control for now */ + data->curr_pwm = 255; /* Try not to cause a CPU_on_fire exception + if we take over... */ +} + +static int setup_rfkill(void) +{ + int ret; + + wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev, + RFKILL_TYPE_WLAN, &compal_rfkill_ops, + (void *) WIRELESS_WLAN); + if (!wifi_rfkill) + return -ENOMEM; + + ret = rfkill_register(wifi_rfkill); + if (ret) + goto err_wifi; + + bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev, + RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops, + (void *) WIRELESS_BT); + if (!bt_rfkill) { + ret = -ENOMEM; + goto err_allocate_bt; + } + ret = rfkill_register(bt_rfkill); + if (ret) + goto err_register_bt; + + return 0; + +err_register_bt: + rfkill_destroy(bt_rfkill); + +err_allocate_bt: + rfkill_unregister(wifi_rfkill); + +err_wifi: + rfkill_destroy(wifi_rfkill); + + return ret; +} static int __init compal_init(void) { int ret; - if (acpi_disabled) + if (acpi_disabled) { + pr_err("ACPI needs to be enabled for this driver to work!\n"); return -ENODEV; + } - if (!force && !dmi_check_system(compal_dmi_table)) + if (!force && !dmi_check_system(compal_dmi_table)) { + pr_err("Motherboard not recognized (You could try the module's force-parameter)\n"); return -ENODEV; - - /* Register backlight stuff */ + } if (!acpi_video_backlight_support()) { - compalbl_device = backlight_device_register("compal-laptop", NULL, NULL, - &compalbl_ops); + struct backlight_properties props; + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = BACKLIGHT_LEVEL_MAX; + compalbl_device = backlight_device_register(DRIVER_NAME, + NULL, NULL, + &compalbl_ops, + &props); if (IS_ERR(compalbl_device)) return PTR_ERR(compalbl_device); - - compalbl_device->props.max_brightness = COMPAL_LCD_LEVEL_MAX-1; } ret = platform_driver_register(&compal_driver); if (ret) - goto fail_backlight; - - /* Register platform stuff */ + goto err_backlight; - compal_device = platform_device_alloc("compal-laptop", -1); + compal_device = platform_device_alloc(DRIVER_NAME, -1); if (!compal_device) { ret = -ENOMEM; - goto fail_platform_driver; + goto err_platform_driver; } - ret = platform_device_add(compal_device); + ret = platform_device_add(compal_device); /* This calls compal_probe */ if (ret) - goto fail_platform_device1; + goto err_platform_device; - ret = sysfs_create_group(&compal_device->dev.kobj, - &compal_attribute_group); + ret = setup_rfkill(); if (ret) - goto fail_platform_device2; - - printk(KERN_INFO "compal-laptop: driver "COMPAL_DRIVER_VERSION - " successfully loaded.\n"); + goto err_rfkill; + pr_info("Driver " DRIVER_VERSION " successfully loaded\n"); return 0; -fail_platform_device2: - +err_rfkill: platform_device_del(compal_device); -fail_platform_device1: - +err_platform_device: platform_device_put(compal_device); -fail_platform_driver: - +err_platform_driver: platform_driver_unregister(&compal_driver); -fail_backlight: - +err_backlight: backlight_device_unregister(compalbl_device); return ret; } -static void __exit compal_cleanup(void) +static int compal_probe(struct platform_device *pdev) { + int err; + struct compal_data *data; + struct device *hwmon_dev; + + if (!extra_features) + return 0; + + /* Fan control */ + data = devm_kzalloc(&pdev->dev, sizeof(struct compal_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + initialize_fan_control_data(data); + + err = sysfs_create_group(&pdev->dev.kobj, &compal_platform_attr_group); + if (err) + return err; + + hwmon_dev = hwmon_device_register_with_groups(&pdev->dev, + DRIVER_NAME, data, + compal_hwmon_groups); + if (IS_ERR(hwmon_dev)) { + err = PTR_ERR(hwmon_dev); + goto remove; + } + + /* Power supply */ + initialize_power_supply_data(data); + power_supply_register(&compal_device->dev, &data->psy); + + platform_set_drvdata(pdev, data); + + return 0; + +remove: + sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group); + return err; +} - sysfs_remove_group(&compal_device->dev.kobj, &compal_attribute_group); +static void __exit compal_cleanup(void) +{ platform_device_unregister(compal_device); platform_driver_unregister(&compal_driver); backlight_device_unregister(compalbl_device); + rfkill_unregister(wifi_rfkill); + rfkill_unregister(bt_rfkill); + rfkill_destroy(wifi_rfkill); + rfkill_destroy(bt_rfkill); - printk(KERN_INFO "compal-laptop: driver unloaded.\n"); + pr_info("Driver unloaded\n"); } +static int compal_remove(struct platform_device *pdev) +{ + struct compal_data *data; + + if (!extra_features) + return 0; + + pr_info("Unloading: resetting fan control to motherboard\n"); + pwm_disable_control(); + + data = platform_get_drvdata(pdev); + power_supply_unregister(&data->psy); + + sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group); + + return 0; +} + + module_init(compal_init); module_exit(compal_cleanup); MODULE_AUTHOR("Cezary Jackiewicz"); +MODULE_AUTHOR("Roald Frederickx (roald.frederickx@gmail.com)"); MODULE_DESCRIPTION("Compal Laptop Support"); -MODULE_VERSION(COMPAL_DRIVER_VERSION); +MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); - -MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*"); -MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*"); -MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*"); -MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*"); -MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*"); diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 3780994dc8f..fed4111ac31 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -11,6 +11,8 @@ * published by the Free Software Foundation. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> @@ -22,6 +24,11 @@ #include <linux/rfkill.h> #include <linux/power_supply.h> #include <linux/acpi.h> +#include <linux/mm.h> +#include <linux/i8042.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> #include "../../firmware/dcdbas.h" #define BRIGHTNESS_TOKEN 0x7d @@ -53,6 +60,22 @@ struct calling_interface_structure { struct calling_interface_token tokens[]; } __packed; +struct quirk_entry { + u8 touchpad_led; +}; + +static struct quirk_entry *quirks; + +static struct quirk_entry quirk_dell_vostro_v130 = { + .touchpad_led = 1, +}; + +static int dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + static int da_command_address; static int da_command_code; static int da_num_tokens; @@ -70,8 +93,12 @@ static struct backlight_device *dell_backlight_device; static struct rfkill *wifi_rfkill; static struct rfkill *bluetooth_rfkill; static struct rfkill *wwan_rfkill; +static bool force_rfkill; + +module_param(force_rfkill, bool, 0444); +MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); -static const struct dmi_system_id __initdata dell_device_table[] = { +static const struct dmi_system_id dell_device_table[] __initconst = { { .ident = "Dell laptop", .matches = { @@ -79,13 +106,193 @@ static const struct dmi_system_id __initdata dell_device_table[] = { DMI_MATCH(DMI_CHASSIS_TYPE, "8"), }, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ + }, + }, + { + .ident = "Dell Computer Corporation", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, dell_device_table); + +static struct dmi_system_id dell_quirks[] = { + { + .callback = dmi_matched, + .ident = "Dell Vostro V130", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V130"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro V131", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3350", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3350"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3555", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron N311z", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron M5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3460", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3460"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3560", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3560"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3450", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7520"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, { } }; +static struct calling_interface_buffer *buffer; +static struct page *bufferpage; +static DEFINE_MUTEX(buffer_mutex); + +static int hwswitch_state; + +static void get_buffer(void) +{ + mutex_lock(&buffer_mutex); + memset(buffer, 0, sizeof(struct calling_interface_buffer)); +} + +static void release_buffer(void) +{ + mutex_unlock(&buffer_mutex); +} + static void __init parse_da_table(const struct dmi_header *dm) { /* Final token is a terminator, so we don't want to copy it */ int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; + struct calling_interface_token *new_da_tokens; struct calling_interface_structure *table = container_of(dm, struct calling_interface_structure, header); @@ -98,12 +305,13 @@ static void __init parse_da_table(const struct dmi_header *dm) da_command_address = table->cmdIOAddress; da_command_code = table->cmdIOCode; - da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * - sizeof(struct calling_interface_token), - GFP_KERNEL); + new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * + sizeof(struct calling_interface_token), + GFP_KERNEL); - if (!da_tokens) + if (!new_da_tokens) return; + da_tokens = new_da_tokens; memcpy(da_tokens+da_num_tokens, table->tokens, sizeof(struct calling_interface_token) * tokens); @@ -115,9 +323,7 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) { switch (dm->type) { case 0xd4: /* Indexed IO */ - break; case 0xd5: /* Protected Area Type 1 */ - break; case 0xd6: /* Protected Area Type 2 */ break; case 0xda: /* Calling interface */ @@ -160,6 +366,8 @@ dell_send_request(struct calling_interface_buffer *buffer, int class, /* Derived from information in DellWirelessCtl.cpp: Class 17, select 11 is radio control. It returns an array of 32-bit values. + Input byte 0 = 0: Wireless information + result[0]: return code result[1]: Bit 0: Hardware switch supported @@ -180,33 +388,76 @@ dell_send_request(struct calling_interface_buffer *buffer, int class, Bits 20-31: Reserved result[2]: NVRAM size in bytes result[3]: NVRAM format version number + + Input byte 0 = 2: Wireless switch configuration + result[0]: return code + result[1]: + Bit 0: Wifi controlled by switch + Bit 1: Bluetooth controlled by switch + Bit 2: WWAN controlled by switch + Bits 3-6: Reserved + Bit 7: Wireless switch config locked + Bit 8: Wifi locator enabled + Bits 9-14: Reserved + Bit 15: Wifi locator setting locked + Bits 16-31: Reserved */ static int dell_rfkill_set(void *data, bool blocked) { - struct calling_interface_buffer buffer; int disable = blocked ? 1 : 0; unsigned long radio = (unsigned long)data; + int hwswitch_bit = (unsigned long)data - 1; + + get_buffer(); + dell_send_request(buffer, 17, 11); - memset(&buffer, 0, sizeof(struct calling_interface_buffer)); - buffer.input[0] = (1 | (radio<<8) | (disable << 16)); - dell_send_request(&buffer, 17, 11); + /* If the hardware switch controls this radio, and the hardware + switch is disabled, always disable the radio */ + if ((hwswitch_state & BIT(hwswitch_bit)) && + !(buffer->output[1] & BIT(16))) + disable = 1; + buffer->input[0] = (1 | (radio<<8) | (disable << 16)); + dell_send_request(buffer, 17, 11); + + release_buffer(); return 0; } +/* Must be called with the buffer held */ +static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, + int status) +{ + if (status & BIT(0)) { + /* Has hw-switch, sync sw_state to BIOS */ + int block = rfkill_blocked(rfkill); + buffer->input[0] = (1 | (radio << 8) | (block << 16)); + dell_send_request(buffer, 17, 11); + } else { + /* No hw-switch, sync BIOS state to sw_state */ + rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); + } +} + +static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, + int status) +{ + if (hwswitch_state & (BIT(radio - 1))) + rfkill_set_hw_state(rfkill, !(status & BIT(16))); +} + static void dell_rfkill_query(struct rfkill *rfkill, void *data) { - struct calling_interface_buffer buffer; int status; - int bit = (unsigned long)data + 16; - memset(&buffer, 0, sizeof(struct calling_interface_buffer)); - dell_send_request(&buffer, 17, 11); - status = buffer.output[1]; + get_buffer(); + dell_send_request(buffer, 17, 11); + status = buffer->output[1]; - rfkill_set_sw_state(rfkill, !!(status & BIT(bit))); - rfkill_set_hw_state(rfkill, !(status & BIT(16))); + dell_rfkill_update_hw_state(rfkill, (unsigned long)data, status); + + release_buffer(); } static const struct rfkill_ops dell_rfkill_ops = { @@ -214,15 +465,158 @@ static const struct rfkill_ops dell_rfkill_ops = { .query = dell_rfkill_query, }; -static int __init dell_setup_rfkill(void) +static struct dentry *dell_laptop_dir; + +static int dell_debugfs_show(struct seq_file *s, void *data) +{ + int status; + + get_buffer(); + dell_send_request(buffer, 17, 11); + status = buffer->output[1]; + release_buffer(); + + seq_printf(s, "status:\t0x%X\n", status); + seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n", + status & BIT(0)); + seq_printf(s, "Bit 1 : Wifi locator supported: %lu\n", + (status & BIT(1)) >> 1); + seq_printf(s, "Bit 2 : Wifi is supported: %lu\n", + (status & BIT(2)) >> 2); + seq_printf(s, "Bit 3 : Bluetooth is supported: %lu\n", + (status & BIT(3)) >> 3); + seq_printf(s, "Bit 4 : WWAN is supported: %lu\n", + (status & BIT(4)) >> 4); + seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n", + (status & BIT(5)) >> 5); + seq_printf(s, "Bit 8 : Wifi is installed: %lu\n", + (status & BIT(8)) >> 8); + seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n", + (status & BIT(9)) >> 9); + seq_printf(s, "Bit 10: WWAN is installed: %lu\n", + (status & BIT(10)) >> 10); + seq_printf(s, "Bit 16: Hardware switch is on: %lu\n", + (status & BIT(16)) >> 16); + seq_printf(s, "Bit 17: Wifi is blocked: %lu\n", + (status & BIT(17)) >> 17); + seq_printf(s, "Bit 18: Bluetooth is blocked: %lu\n", + (status & BIT(18)) >> 18); + seq_printf(s, "Bit 19: WWAN is blocked: %lu\n", + (status & BIT(19)) >> 19); + + seq_printf(s, "\nhwswitch_state:\t0x%X\n", hwswitch_state); + seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n", + hwswitch_state & BIT(0)); + seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n", + (hwswitch_state & BIT(1)) >> 1); + seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n", + (hwswitch_state & BIT(2)) >> 2); + seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n", + (hwswitch_state & BIT(7)) >> 7); + seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n", + (hwswitch_state & BIT(8)) >> 8); + seq_printf(s, "Bit 15: Wifi locator setting locked: %lu\n", + (hwswitch_state & BIT(15)) >> 15); + + return 0; +} + +static int dell_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, dell_debugfs_show, inode->i_private); +} + +static const struct file_operations dell_debugfs_fops = { + .owner = THIS_MODULE, + .open = dell_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void dell_update_rfkill(struct work_struct *ignored) { - struct calling_interface_buffer buffer; int status; - int ret; - memset(&buffer, 0, sizeof(struct calling_interface_buffer)); - dell_send_request(&buffer, 17, 11); - status = buffer.output[1]; + get_buffer(); + dell_send_request(buffer, 17, 11); + status = buffer->output[1]; + + if (wifi_rfkill) { + dell_rfkill_update_hw_state(wifi_rfkill, 1, status); + dell_rfkill_update_sw_state(wifi_rfkill, 1, status); + } + if (bluetooth_rfkill) { + dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status); + dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status); + } + if (wwan_rfkill) { + dell_rfkill_update_hw_state(wwan_rfkill, 3, status); + dell_rfkill_update_sw_state(wwan_rfkill, 3, status); + } + + release_buffer(); +} +static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); + +static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + static bool extended; + + if (str & 0x20) + return false; + + if (unlikely(data == 0xe0)) { + extended = true; + return false; + } else if (unlikely(extended)) { + switch (data) { + case 0x8: + schedule_delayed_work(&dell_rfkill_work, + round_jiffies_relative(HZ / 4)); + break; + } + extended = false; + } + + return false; +} + +static int __init dell_setup_rfkill(void) +{ + int status, ret, whitelisted; + const char *product; + + /* + * rfkill support causes trouble on various models, mostly Inspirons. + * So we whitelist certain series, and don't support rfkill on others. + */ + whitelisted = 0; + product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (product && (strncmp(product, "Latitude", 8) == 0 || + strncmp(product, "Precision", 9) == 0)) + whitelisted = 1; + if (!force_rfkill && !whitelisted) + return 0; + + get_buffer(); + dell_send_request(buffer, 17, 11); + status = buffer->output[1]; + buffer->input[0] = 0x2; + dell_send_request(buffer, 17, 11); + hwswitch_state = buffer->output[1]; + release_buffer(); + + if (!(status & BIT(0))) { + if (force_rfkill) { + /* No hwsitch, clear all hw-controlled bits */ + hwswitch_state &= ~7; + } else { + /* rfkill is only tested on laptops with a hwswitch */ + return 0; + } + } if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev, @@ -265,7 +659,16 @@ static int __init dell_setup_rfkill(void) goto err_wwan; } + ret = i8042_install_filter(dell_laptop_i8042_filter); + if (ret) { + pr_warn("Unable to install key filter\n"); + goto err_filter; + } + return 0; +err_filter: + if (wwan_rfkill) + rfkill_unregister(wwan_rfkill); err_wwan: rfkill_destroy(wwan_rfkill); if (bluetooth_rfkill) @@ -298,59 +701,111 @@ static void dell_cleanup_rfkill(void) static int dell_send_intensity(struct backlight_device *bd) { - struct calling_interface_buffer buffer; + int ret = 0; - memset(&buffer, 0, sizeof(struct calling_interface_buffer)); - buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); - buffer.input[1] = bd->props.brightness; + get_buffer(); + buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN); + buffer->input[1] = bd->props.brightness; - if (buffer.input[0] == -1) - return -ENODEV; + if (buffer->input[0] == -1) { + ret = -ENODEV; + goto out; + } if (power_supply_is_system_supplied() > 0) - dell_send_request(&buffer, 1, 2); + dell_send_request(buffer, 1, 2); else - dell_send_request(&buffer, 1, 1); + dell_send_request(buffer, 1, 1); - return 0; +out: + release_buffer(); + return ret; } static int dell_get_intensity(struct backlight_device *bd) { - struct calling_interface_buffer buffer; + int ret = 0; - memset(&buffer, 0, sizeof(struct calling_interface_buffer)); - buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); + get_buffer(); + buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN); - if (buffer.input[0] == -1) - return -ENODEV; + if (buffer->input[0] == -1) { + ret = -ENODEV; + goto out; + } if (power_supply_is_system_supplied() > 0) - dell_send_request(&buffer, 0, 2); + dell_send_request(buffer, 0, 2); else - dell_send_request(&buffer, 0, 1); + dell_send_request(buffer, 0, 1); + + ret = buffer->output[1]; - return buffer.output[1]; +out: + release_buffer(); + return ret; } -static struct backlight_ops dell_ops = { +static const struct backlight_ops dell_ops = { .get_brightness = dell_get_intensity, .update_status = dell_send_intensity, }; +static void touchpad_led_on(void) +{ + int command = 0x97; + char data = 1; + i8042_command(&data, command | 1 << 12); +} + +static void touchpad_led_off(void) +{ + int command = 0x97; + char data = 2; + i8042_command(&data, command | 1 << 12); +} + +static void touchpad_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value > 0) + touchpad_led_on(); + else + touchpad_led_off(); +} + +static struct led_classdev touchpad_led = { + .name = "dell-laptop::touchpad", + .brightness_set = touchpad_led_set, + .flags = LED_CORE_SUSPENDRESUME, +}; + +static int touchpad_led_init(struct device *dev) +{ + return led_classdev_register(dev, &touchpad_led); +} + +static void touchpad_led_exit(void) +{ + led_classdev_unregister(&touchpad_led); +} + static int __init dell_init(void) { - struct calling_interface_buffer buffer; int max_intensity = 0; int ret; if (!dmi_check_system(dell_device_table)) return -ENODEV; + quirks = NULL; + /* find if this machine support other functions */ + dmi_check_system(dell_quirks); + dmi_walk(find_tokens, NULL); if (!da_tokens) { - printk(KERN_INFO "dell-laptop: Unable to find dmi tokens\n"); + pr_info("Unable to find dmi tokens\n"); return -ENODEV; } @@ -366,13 +821,32 @@ static int __init dell_init(void) if (ret) goto fail_platform_device2; + /* + * Allocate buffer below 4GB for SMI data--only 32-bit physical addr + * is passed to SMI handler. + */ + bufferpage = alloc_page(GFP_KERNEL | GFP_DMA32); + if (!bufferpage) { + ret = -ENOMEM; + goto fail_buffer; + } + buffer = page_address(bufferpage); + ret = dell_setup_rfkill(); if (ret) { - printk(KERN_WARNING "dell-laptop: Unable to setup rfkill\n"); + pr_warn("Unable to setup rfkill\n"); goto fail_rfkill; } + if (quirks && quirks->touchpad_led) + touchpad_led_init(&platform_device->dev); + + dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); + if (dell_laptop_dir != NULL) + debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, + &dell_debugfs_fops); + #ifdef CONFIG_ACPI /* In the event of an ACPI backlight being available, don't * register the platform controller. @@ -381,19 +855,24 @@ static int __init dell_init(void) return 0; #endif - memset(&buffer, 0, sizeof(struct calling_interface_buffer)); - buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); - - if (buffer.input[0] != -1) { - dell_send_request(&buffer, 0, 2); - max_intensity = buffer.output[3]; + get_buffer(); + buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN); + if (buffer->input[0] != -1) { + dell_send_request(buffer, 0, 2); + max_intensity = buffer->output[3]; } + release_buffer(); if (max_intensity) { - dell_backlight_device = backlight_device_register( - "dell_backlight", - &platform_device->dev, NULL, - &dell_ops); + struct backlight_properties props; + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = max_intensity; + dell_backlight_device = backlight_device_register("dell_backlight", + &platform_device->dev, + NULL, + &dell_ops, + &props); if (IS_ERR(dell_backlight_device)) { ret = PTR_ERR(dell_backlight_device); @@ -401,7 +880,6 @@ static int __init dell_init(void) goto fail_backlight; } - dell_backlight_device->props.max_brightness = max_intensity; dell_backlight_device->props.brightness = dell_get_intensity(dell_backlight_device); backlight_update_status(dell_backlight_device); @@ -410,8 +888,12 @@ static int __init dell_init(void) return 0; fail_backlight: + i8042_remove_filter(dell_laptop_i8042_filter); + cancel_delayed_work_sync(&dell_rfkill_work); dell_cleanup_rfkill(); fail_rfkill: + free_page((unsigned long)bufferpage); +fail_buffer: platform_device_del(platform_device); fail_platform_device2: platform_device_put(platform_device); @@ -424,8 +906,19 @@ fail_platform_driver: static void __exit dell_exit(void) { + debugfs_remove_recursive(dell_laptop_dir); + if (quirks && quirks->touchpad_led) + touchpad_led_exit(); + i8042_remove_filter(dell_laptop_i8042_filter); + cancel_delayed_work_sync(&dell_rfkill_work); backlight_device_unregister(dell_backlight_device); dell_cleanup_rfkill(); + if (platform_device) { + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } + kfree(da_tokens); + free_page((unsigned long)buffer); } module_init(dell_init); @@ -434,4 +927,3 @@ module_exit(dell_exit); MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_DESCRIPTION("Dell laptop driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*"); diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell-smo8800.c new file mode 100644 index 00000000000..a653716055d --- /dev/null +++ b/drivers/platform/x86/dell-smo8800.c @@ -0,0 +1,233 @@ +/* + * dell-smo8800.c - Dell Latitude ACPI SMO8800/SMO8810 freefall sensor driver + * + * Copyright (C) 2012 Sonal Santan <sonal.santan@gmail.com> + * Copyright (C) 2014 Pali Rohár <pali.rohar@gmail.com> + * + * This is loosely based on lis3lv02d driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define DRIVER_NAME "smo8800" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/acpi.h> +#include <linux/interrupt.h> +#include <linux/miscdevice.h> + +struct smo8800_device { + u32 irq; /* acpi device irq */ + atomic_t counter; /* count after last read */ + struct miscdevice miscdev; /* for /dev/freefall */ + unsigned long misc_opened; /* whether the device is open */ + wait_queue_head_t misc_wait; /* Wait queue for the misc dev */ + struct device *dev; /* acpi device */ +}; + +static irqreturn_t smo8800_interrupt_quick(int irq, void *data) +{ + struct smo8800_device *smo8800 = data; + + atomic_inc(&smo8800->counter); + wake_up_interruptible(&smo8800->misc_wait); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t smo8800_interrupt_thread(int irq, void *data) +{ + struct smo8800_device *smo8800 = data; + + dev_info(smo8800->dev, "detected free fall\n"); + return IRQ_HANDLED; +} + +static acpi_status smo8800_get_resource(struct acpi_resource *resource, + void *context) +{ + struct acpi_resource_extended_irq *irq; + + if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) + return AE_OK; + + irq = &resource->data.extended_irq; + if (!irq || !irq->interrupt_count) + return AE_OK; + + *((u32 *)context) = irq->interrupts[0]; + return AE_CTRL_TERMINATE; +} + +static u32 smo8800_get_irq(struct acpi_device *device) +{ + u32 irq = 0; + acpi_status status; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + smo8800_get_resource, &irq); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "acpi_walk_resources failed\n"); + return 0; + } + + return irq; +} + +static ssize_t smo8800_misc_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + u32 data = 0; + unsigned char byte_data = 0; + ssize_t retval = 1; + + if (count < 1) + return -EINVAL; + + atomic_set(&smo8800->counter, 0); + retval = wait_event_interruptible(smo8800->misc_wait, + (data = atomic_xchg(&smo8800->counter, 0))); + + if (retval) + return retval; + + byte_data = 1; + retval = 1; + + if (data < 255) + byte_data = data; + else + byte_data = 255; + + if (put_user(byte_data, buf)) + retval = -EFAULT; + + return retval; +} + +static int smo8800_misc_open(struct inode *inode, struct file *file) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + if (test_and_set_bit(0, &smo8800->misc_opened)) + return -EBUSY; /* already open */ + + atomic_set(&smo8800->counter, 0); + return 0; +} + +static int smo8800_misc_release(struct inode *inode, struct file *file) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + clear_bit(0, &smo8800->misc_opened); /* release the device */ + return 0; +} + +static const struct file_operations smo8800_misc_fops = { + .owner = THIS_MODULE, + .read = smo8800_misc_read, + .open = smo8800_misc_open, + .release = smo8800_misc_release, +}; + +static int smo8800_add(struct acpi_device *device) +{ + int err; + struct smo8800_device *smo8800; + + smo8800 = devm_kzalloc(&device->dev, sizeof(*smo8800), GFP_KERNEL); + if (!smo8800) { + dev_err(&device->dev, "failed to allocate device data\n"); + return -ENOMEM; + } + + smo8800->dev = &device->dev; + smo8800->miscdev.minor = MISC_DYNAMIC_MINOR; + smo8800->miscdev.name = "freefall"; + smo8800->miscdev.fops = &smo8800_misc_fops; + + init_waitqueue_head(&smo8800->misc_wait); + + err = misc_register(&smo8800->miscdev); + if (err) { + dev_err(&device->dev, "failed to register misc dev: %d\n", err); + return err; + } + + device->driver_data = smo8800; + + smo8800->irq = smo8800_get_irq(device); + if (!smo8800->irq) { + dev_err(&device->dev, "failed to obtain IRQ\n"); + err = -EINVAL; + goto error; + } + + err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick, + smo8800_interrupt_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + DRIVER_NAME, smo8800); + if (err) { + dev_err(&device->dev, + "failed to request thread for IRQ %d: %d\n", + smo8800->irq, err); + goto error; + } + + dev_dbg(&device->dev, "device /dev/freefall registered with IRQ %d\n", + smo8800->irq); + return 0; + +error: + misc_deregister(&smo8800->miscdev); + return err; +} + +static int smo8800_remove(struct acpi_device *device) +{ + struct smo8800_device *smo8800 = device->driver_data; + + free_irq(smo8800->irq, smo8800); + misc_deregister(&smo8800->miscdev); + dev_dbg(&device->dev, "device /dev/freefall unregistered\n"); + return 0; +} + +static const struct acpi_device_id smo8800_ids[] = { + { "SMO8800", 0 }, + { "SMO8810", 0 }, + { "", 0 }, +}; + +MODULE_DEVICE_TABLE(acpi, smo8800_ids); + +static struct acpi_driver smo8800_driver = { + .name = DRIVER_NAME, + .class = "Latitude", + .ids = smo8800_ids, + .ops = { + .add = smo8800_add, + .remove = smo8800_remove, + }, + .owner = THIS_MODULE, +}; + +module_acpi_driver(smo8800_driver); + +MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO8800/SMO8810)"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sonal Santan, Pali Rohár"); diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c new file mode 100644 index 00000000000..dbc97a33bbc --- /dev/null +++ b/drivers/platform/x86/dell-wmi-aio.c @@ -0,0 +1,214 @@ +/* + * WMI hotkeys support for Dell All-In-One series + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/acpi.h> +#include <linux/string.h> + +MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); +MODULE_LICENSE("GPL"); + +#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" +#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" + +struct dell_wmi_event { + u16 length; + /* 0x000: A hot key pressed or an event occurred + * 0x00F: A sequence of hot keys are pressed */ + u16 type; + u16 event[]; +}; + +static const char *dell_wmi_aio_guids[] = { + EVENT_GUID1, + EVENT_GUID2, + NULL +}; + +MODULE_ALIAS("wmi:"EVENT_GUID1); +MODULE_ALIAS("wmi:"EVENT_GUID2); + +static const struct key_entry dell_wmi_aio_keymap[] = { + { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, + { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0xe030, { KEY_VOLUMEUP } }, + { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0xe020, { KEY_MUTE } }, + { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, + { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, + { KE_END, 0 } +}; + +static struct input_dev *dell_wmi_aio_input_dev; + +/* + * The new WMI event data format will follow the dell_wmi_event structure + * So, we will check if the buffer matches the format + */ +static bool dell_wmi_aio_event_check(u8 *buffer, int length) +{ + struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; + + if (event == NULL || length < 6) + return false; + + if ((event->type == 0 || event->type == 0xf) && + event->length >= 2) + return true; + + return false; +} + +static void dell_wmi_aio_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + struct dell_wmi_event *event; + acpi_status status; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_info("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + if (obj) { + unsigned int scancode = 0; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + /* Most All-In-One correctly return integer scancode */ + scancode = obj->integer.value; + sparse_keymap_report_event(dell_wmi_aio_input_dev, + scancode, 1, true); + break; + case ACPI_TYPE_BUFFER: + if (dell_wmi_aio_event_check(obj->buffer.pointer, + obj->buffer.length)) { + event = (struct dell_wmi_event *) + obj->buffer.pointer; + scancode = event->event[0]; + } else { + /* Broken machines return the scancode in a + buffer */ + if (obj->buffer.pointer && + obj->buffer.length > 0) + scancode = obj->buffer.pointer[0]; + } + if (scancode) + sparse_keymap_report_event( + dell_wmi_aio_input_dev, + scancode, 1, true); + break; + } + } + kfree(obj); +} + +static int __init dell_wmi_aio_input_setup(void) +{ + int err; + + dell_wmi_aio_input_dev = input_allocate_device(); + + if (!dell_wmi_aio_input_dev) + return -ENOMEM; + + dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; + dell_wmi_aio_input_dev->phys = "wmi/input0"; + dell_wmi_aio_input_dev->id.bustype = BUS_HOST; + + err = sparse_keymap_setup(dell_wmi_aio_input_dev, + dell_wmi_aio_keymap, NULL); + if (err) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; + } + err = input_register_device(dell_wmi_aio_input_dev); + if (err) { + pr_info("Unable to register input device\n"); + goto err_free_keymap; + } + return 0; + +err_free_keymap: + sparse_keymap_free(dell_wmi_aio_input_dev); +err_free_dev: + input_free_device(dell_wmi_aio_input_dev); + return err; +} + +static const char *dell_wmi_aio_find(void) +{ + int i; + + for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) + if (wmi_has_guid(dell_wmi_aio_guids[i])) + return dell_wmi_aio_guids[i]; + + return NULL; +} + +static int __init dell_wmi_aio_init(void) +{ + int err; + const char *guid; + + guid = dell_wmi_aio_find(); + if (!guid) { + pr_warn("No known WMI GUID found\n"); + return -ENXIO; + } + + err = dell_wmi_aio_input_setup(); + if (err) + return err; + + err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); + if (err) { + pr_err("Unable to register notify handler - %d\n", err); + sparse_keymap_free(dell_wmi_aio_input_dev); + input_unregister_device(dell_wmi_aio_input_dev); + return err; + } + + return 0; +} + +static void __exit dell_wmi_aio_exit(void) +{ + const char *guid; + + guid = dell_wmi_aio_find(); + wmi_remove_notify_handler(guid); + sparse_keymap_free(dell_wmi_aio_input_dev); + input_unregister_device(dell_wmi_aio_input_dev); +} + +module_init(dell_wmi_aio_init); +module_exit(dell_wmi_aio_exit); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 1b1dddbd574..390e8e33d5e 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -23,12 +23,15 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> +#include <linux/slab.h> #include <linux/types.h> #include <linux/input.h> -#include <acpi/acpi_drivers.h> +#include <linux/input/sparse-keymap.h> #include <linux/acpi.h> #include <linux/string.h> #include <linux/dmi.h> @@ -43,78 +46,80 @@ static int acpi_video; MODULE_ALIAS("wmi:"DELL_EVENT_GUID); -struct key_entry { - char type; /* See KE_* below */ - u16 code; - u16 keycode; -}; - -enum { KE_KEY, KE_SW, KE_IGNORE, KE_END }; - /* * Certain keys are flagged as KE_IGNORE. All of these are either * notifications (rather than requests for change) or are also sent * via the keyboard controller so should not be sent again. */ -static struct key_entry dell_legacy_wmi_keymap[] = { - {KE_KEY, 0xe045, KEY_PROG1}, - {KE_KEY, 0xe009, KEY_EJECTCD}, +static const struct key_entry dell_wmi_legacy_keymap[] __initconst = { + { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, + + { KE_KEY, 0xe045, { KEY_PROG1 } }, + { KE_KEY, 0xe009, { KEY_EJECTCD } }, /* These also contain the brightness level at offset 6 */ - {KE_KEY, 0xe006, KEY_BRIGHTNESSUP}, - {KE_KEY, 0xe005, KEY_BRIGHTNESSDOWN}, + { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, /* Battery health status button */ - {KE_KEY, 0xe007, KEY_BATTERY}, + { KE_KEY, 0xe007, { KEY_BATTERY } }, /* This is actually for all radios. Although physically a * switch, the notification does not provide an indication of * state and so it should be reported as a key */ - {KE_KEY, 0xe008, KEY_WLAN}, + { KE_KEY, 0xe008, { KEY_WLAN } }, /* The next device is at offset 6, the active devices are at offset 8 and the attached devices at offset 10 */ - {KE_KEY, 0xe00b, KEY_SWITCHVIDEOMODE}, + { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, - {KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, + { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, /* BIOS error detected */ - {KE_IGNORE, 0xe00d, KEY_RESERVED}, + { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, /* Wifi Catcher */ - {KE_KEY, 0xe011, KEY_PROG2}, + { KE_KEY, 0xe011, {KEY_PROG2 } }, /* Ambient light sensor toggle */ - {KE_IGNORE, 0xe013, KEY_RESERVED}, - - {KE_IGNORE, 0xe020, KEY_MUTE}, - {KE_IGNORE, 0xe02e, KEY_VOLUMEDOWN}, - {KE_IGNORE, 0xe030, KEY_VOLUMEUP}, - {KE_IGNORE, 0xe033, KEY_KBDILLUMUP}, - {KE_IGNORE, 0xe034, KEY_KBDILLUMDOWN}, - {KE_IGNORE, 0xe03a, KEY_CAPSLOCK}, - {KE_IGNORE, 0xe045, KEY_NUMLOCK}, - {KE_IGNORE, 0xe046, KEY_SCROLLLOCK}, - {KE_END, 0} + { KE_IGNORE, 0xe013, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe020, { KEY_MUTE } }, + + /* Shortcut and audio panel keys */ + { KE_IGNORE, 0xe025, { KEY_RESERVED } }, + { KE_IGNORE, 0xe026, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, + { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, + { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, + { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, + { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, + { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, + { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, + { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, + { KE_END, 0 } }; static bool dell_new_hk_type; -struct dell_new_keymap_entry { +struct dell_bios_keymap_entry { u16 scancode; u16 keycode; }; -struct dell_hotkey_table { +struct dell_bios_hotkey_table { struct dmi_header header; - struct dell_new_keymap_entry keymap[]; + struct dell_bios_keymap_entry keymap[]; }; -static struct key_entry *dell_new_wmi_keymap; +static const struct dell_bios_hotkey_table *dell_bios_hotkey_table; -static u16 bios_to_linux_keycode[256] = { +static const u16 bios_to_linux_keycode[256] __initconst = { KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, @@ -124,7 +129,9 @@ static u16 bios_to_linux_keycode[256] = { KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE, KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2, - KEY_UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_MICMUTE, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -133,196 +140,144 @@ static u16 bios_to_linux_keycode[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - KEY_PROG3 + 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PROG3 }; - -static struct key_entry *dell_wmi_keymap = dell_legacy_wmi_keymap; - static struct input_dev *dell_wmi_input_dev; -static struct key_entry *dell_wmi_get_entry_by_scancode(int code) -{ - struct key_entry *key; - - for (key = dell_wmi_keymap; key->type != KE_END; key++) - if (code == key->code) - return key; - - return NULL; -} - -static struct key_entry *dell_wmi_get_entry_by_keycode(int keycode) -{ - struct key_entry *key; - - for (key = dell_wmi_keymap; key->type != KE_END; key++) - if (key->type == KE_KEY && keycode == key->keycode) - return key; - - return NULL; -} - -static int dell_wmi_getkeycode(struct input_dev *dev, int scancode, - int *keycode) -{ - struct key_entry *key = dell_wmi_get_entry_by_scancode(scancode); - - if (key && key->type == KE_KEY) { - *keycode = key->keycode; - return 0; - } - - return -EINVAL; -} - -static int dell_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode) -{ - struct key_entry *key; - int old_keycode; - - if (keycode < 0 || keycode > KEY_MAX) - return -EINVAL; - - key = dell_wmi_get_entry_by_scancode(scancode); - if (key && key->type == KE_KEY) { - old_keycode = key->keycode; - key->keycode = keycode; - set_bit(keycode, dev->keybit); - if (!dell_wmi_get_entry_by_keycode(old_keycode)) - clear_bit(old_keycode, dev->keybit); - return 0; - } - return -EINVAL; -} - static void dell_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; - static struct key_entry *key; union acpi_object *obj; acpi_status status; status = wmi_get_event_data(value, &response); if (status != AE_OK) { - printk(KERN_INFO "dell-wmi: bad event status 0x%x\n", status); + pr_info("bad event status 0x%x\n", status); return; } obj = (union acpi_object *)response.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER) { + const struct key_entry *key; int reported_key; u16 *buffer_entry = (u16 *)obj->buffer.pointer; + if (dell_new_hk_type && (buffer_entry[1] != 0x10)) { - printk(KERN_INFO "dell-wmi: Received unknown WMI event" - " (0x%x)\n", buffer_entry[1]); + pr_info("Received unknown WMI event (0x%x)\n", + buffer_entry[1]); + kfree(obj); return; } - if (dell_new_hk_type) + if (dell_new_hk_type || buffer_entry[1] == 0x0) reported_key = (int)buffer_entry[2]; else reported_key = (int)buffer_entry[1] & 0xffff; - key = dell_wmi_get_entry_by_scancode(reported_key); - + key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev, + reported_key); if (!key) { - printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", - reported_key); + pr_info("Unknown key %x pressed\n", reported_key); } else if ((key->keycode == KEY_BRIGHTNESSUP || key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) { /* Don't report brightness notifications that will also * come via ACPI */ - return; + ; } else { - input_report_key(dell_wmi_input_dev, key->keycode, 1); - input_sync(dell_wmi_input_dev); - input_report_key(dell_wmi_input_dev, key->keycode, 0); - input_sync(dell_wmi_input_dev); + sparse_keymap_report_entry(dell_wmi_input_dev, key, + 1, true); } } kfree(obj); } - -static void setup_new_hk_map(const struct dmi_header *dm) +static const struct key_entry * __init dell_wmi_prepare_new_keymap(void) { - + int hotkey_num = (dell_bios_hotkey_table->header.length - 4) / + sizeof(struct dell_bios_keymap_entry); + struct key_entry *keymap; int i; - int hotkey_num = (dm->length-4)/sizeof(struct dell_new_keymap_entry); - struct dell_hotkey_table *table = - container_of(dm, struct dell_hotkey_table, header); - dell_new_wmi_keymap = kzalloc((hotkey_num+1) * - sizeof(struct key_entry), GFP_KERNEL); + keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) + return NULL; for (i = 0; i < hotkey_num; i++) { - dell_new_wmi_keymap[i].type = KE_KEY; - dell_new_wmi_keymap[i].code = table->keymap[i].scancode; - dell_new_wmi_keymap[i].keycode = - (table->keymap[i].keycode > 255) ? 0 : - bios_to_linux_keycode[table->keymap[i].keycode]; + const struct dell_bios_keymap_entry *bios_entry = + &dell_bios_hotkey_table->keymap[i]; + keymap[i].type = KE_KEY; + keymap[i].code = bios_entry->scancode; + keymap[i].keycode = bios_entry->keycode < 256 ? + bios_to_linux_keycode[bios_entry->keycode] : + KEY_RESERVED; } - dell_new_wmi_keymap[i].type = KE_END; - dell_new_wmi_keymap[i].code = 0; - dell_new_wmi_keymap[i].keycode = 0; - - dell_wmi_keymap = dell_new_wmi_keymap; - -} - - -static void find_hk_type(const struct dmi_header *dm, void *dummy) -{ - - if ((dm->type == 0xb2) && (dm->length > 6)) { - dell_new_hk_type = true; - setup_new_hk_map(dm); - } + keymap[hotkey_num].type = KE_END; + return keymap; } - static int __init dell_wmi_input_setup(void) { - struct key_entry *key; int err; dell_wmi_input_dev = input_allocate_device(); - if (!dell_wmi_input_dev) return -ENOMEM; dell_wmi_input_dev->name = "Dell WMI hotkeys"; dell_wmi_input_dev->phys = "wmi/input0"; dell_wmi_input_dev->id.bustype = BUS_HOST; - dell_wmi_input_dev->getkeycode = dell_wmi_getkeycode; - dell_wmi_input_dev->setkeycode = dell_wmi_setkeycode; - - for (key = dell_wmi_keymap; key->type != KE_END; key++) { - switch (key->type) { - case KE_KEY: - set_bit(EV_KEY, dell_wmi_input_dev->evbit); - set_bit(key->keycode, dell_wmi_input_dev->keybit); - break; - case KE_SW: - set_bit(EV_SW, dell_wmi_input_dev->evbit); - set_bit(key->keycode, dell_wmi_input_dev->swbit); - break; + + if (dell_new_hk_type) { + const struct key_entry *keymap = dell_wmi_prepare_new_keymap(); + if (!keymap) { + err = -ENOMEM; + goto err_free_dev; } - } - err = input_register_device(dell_wmi_input_dev); + err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL); - if (err) { - input_free_device(dell_wmi_input_dev); - return err; + /* + * Sparse keymap library makes a copy of keymap so we + * don't need the original one that was allocated. + */ + kfree(keymap); + } else { + err = sparse_keymap_setup(dell_wmi_input_dev, + dell_wmi_legacy_keymap, NULL); } + if (err) + goto err_free_dev; + + err = input_register_device(dell_wmi_input_dev); + if (err) + goto err_free_keymap; return 0; + + err_free_keymap: + sparse_keymap_free(dell_wmi_input_dev); + err_free_dev: + input_free_device(dell_wmi_input_dev); + return err; +} + +static void dell_wmi_input_destroy(void) +{ + sparse_keymap_free(dell_wmi_input_dev); + input_unregister_device(dell_wmi_input_dev); +} + +static void __init find_hk_type(const struct dmi_header *dm, void *dummy) +{ + if (dm->type == 0xb2 && dm->length > 6) { + dell_new_hk_type = true; + dell_bios_hotkey_table = + container_of(dm, struct dell_bios_hotkey_table, header); + } } static int __init dell_wmi_init(void) @@ -331,7 +286,7 @@ static int __init dell_wmi_init(void) acpi_status status; if (!wmi_has_guid(DELL_EVENT_GUID)) { - printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n"); + pr_warn("No known WMI GUID found\n"); return -ENODEV; } @@ -345,21 +300,18 @@ static int __init dell_wmi_init(void) status = wmi_install_notify_handler(DELL_EVENT_GUID, dell_wmi_notify, NULL); if (ACPI_FAILURE(status)) { - input_unregister_device(dell_wmi_input_dev); - printk(KERN_ERR - "dell-wmi: Unable to register notify handler - %d\n", - status); + dell_wmi_input_destroy(); + pr_err("Unable to register notify handler - %d\n", status); return -ENODEV; } return 0; } +module_init(dell_wmi_init); static void __exit dell_wmi_exit(void) { wmi_remove_notify_handler(DELL_EVENT_GUID); - input_unregister_device(dell_wmi_input_dev); + dell_wmi_input_destroy(); } - -module_init(dell_wmi_init); module_exit(dell_wmi_exit); diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 5838c69b2fb..9b0c57cd1d4 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -27,14 +27,16 @@ #include <linux/fb.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> -#include <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> +#include <linux/slab.h> +#include <linux/acpi.h> #include <linux/uaccess.h> #include <linux/input.h> +#include <linux/input/sparse-keymap.h> #include <linux/rfkill.h> #include <linux/pci.h> #include <linux/pci_hotplug.h> #include <linux/leds.h> +#include <linux/dmi.h> #define EEEPC_LAPTOP_VERSION "0.1" #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" @@ -48,6 +50,14 @@ MODULE_AUTHOR("Corentin Chary, Eric Cooper"); MODULE_DESCRIPTION(EEEPC_LAPTOP_NAME); MODULE_LICENSE("GPL"); +static bool hotplug_disabled; + +module_param(hotplug_disabled, bool, 0444); +MODULE_PARM_DESC(hotplug_disabled, + "Disable hotplug for wireless device. " + "If your laptop need that, please report to " + "acpi4asus-user@lists.sourceforge.net."); + /* * Definitions for Asus EeePC */ @@ -120,38 +130,28 @@ static const char *cm_setv[] = { NULL, NULL, "PBPS", "TPDS" }; -struct key_entry { - char type; - u8 code; - u16 keycode; -}; - -enum { KE_KEY, KE_END }; - static const struct key_entry eeepc_keymap[] = { - /* Sleep already handled via generic ACPI code */ - {KE_KEY, 0x10, KEY_WLAN }, - {KE_KEY, 0x11, KEY_WLAN }, - {KE_KEY, 0x12, KEY_PROG1 }, - {KE_KEY, 0x13, KEY_MUTE }, - {KE_KEY, 0x14, KEY_VOLUMEDOWN }, - {KE_KEY, 0x15, KEY_VOLUMEUP }, - {KE_KEY, 0x16, KEY_DISPLAY_OFF }, - {KE_KEY, 0x1a, KEY_COFFEE }, - {KE_KEY, 0x1b, KEY_ZOOM }, - {KE_KEY, 0x1c, KEY_PROG2 }, - {KE_KEY, 0x1d, KEY_PROG3 }, - {KE_KEY, NOTIFY_BRN_MIN, KEY_BRIGHTNESSDOWN }, - {KE_KEY, NOTIFY_BRN_MAX, KEY_BRIGHTNESSUP }, - {KE_KEY, 0x30, KEY_SWITCHVIDEOMODE }, - {KE_KEY, 0x31, KEY_SWITCHVIDEOMODE }, - {KE_KEY, 0x32, KEY_SWITCHVIDEOMODE }, - {KE_KEY, 0x37, KEY_F13 }, /* Disable Touchpad */ - {KE_KEY, 0x38, KEY_F14 }, - {KE_END, 0}, + { KE_KEY, 0x10, { KEY_WLAN } }, + { KE_KEY, 0x11, { KEY_WLAN } }, + { KE_KEY, 0x12, { KEY_PROG1 } }, + { KE_KEY, 0x13, { KEY_MUTE } }, + { KE_KEY, 0x14, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0x15, { KEY_VOLUMEUP } }, + { KE_KEY, 0x16, { KEY_DISPLAY_OFF } }, + { KE_KEY, 0x1a, { KEY_COFFEE } }, + { KE_KEY, 0x1b, { KEY_ZOOM } }, + { KE_KEY, 0x1c, { KEY_PROG2 } }, + { KE_KEY, 0x1d, { KEY_PROG3 } }, + { KE_KEY, NOTIFY_BRN_MIN, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, NOTIFY_BRN_MAX, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x30, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 0x31, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 0x32, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 0x37, { KEY_F13 } }, /* Disable Touchpad */ + { KE_KEY, 0x38, { KEY_F14 } }, + { KE_END, 0 }, }; - /* * This is the main structure, we can use it to store useful information */ @@ -159,14 +159,15 @@ struct eeepc_laptop { acpi_handle handle; /* the handle of the acpi device */ u32 cm_supported; /* the control methods supported by this BIOS */ + bool cpufv_disabled; + bool hotplug_disabled; u16 event_count[128]; /* count for each event */ struct platform_device *platform_device; - struct device *hwmon_device; + struct acpi_device *device; /* the device we are in */ struct backlight_device *backlight_device; struct input_dev *inputdev; - struct key_entry *keymap; struct rfkill *wlan_rfkill; struct rfkill *bluetooth_rfkill; @@ -187,16 +188,10 @@ struct eeepc_laptop { */ static int write_acpi_int(acpi_handle handle, const char *method, int val) { - struct acpi_object_list params; - union acpi_object in_obj; acpi_status status; - params.count = 1; - params.pointer = &in_obj; - in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = val; + status = acpi_execute_simple_method(handle, (char *)method, val); - status = acpi_evaluate_object(handle, (char *)method, ¶ms, NULL); return (status == AE_OK ? 0 : -1); } @@ -225,7 +220,7 @@ static int set_acpi(struct eeepc_laptop *eeepc, int cm, int value) return -ENODEV; if (write_acpi_int(eeepc->handle, method, value)) - pr_warning("Error writing %s\n", method); + pr_warn("Error writing %s\n", method); return 0; } @@ -240,7 +235,7 @@ static int get_acpi(struct eeepc_laptop *eeepc, int cm) return -ENODEV; if (read_acpi_int(eeepc->handle, method, &value)) - pr_warning("Error reading %s\n", method); + pr_warn("Error reading %s\n", method); return value; } @@ -258,7 +253,7 @@ static int acpi_setter_handle(struct eeepc_laptop *eeepc, int cm, status = acpi_get_handle(eeepc->handle, (char *)method, handle); if (status != AE_OK) { - pr_warning("Error finding %s\n", method); + pr_warn("Error finding %s\n", method); return -ENODEV; } return 0; @@ -378,6 +373,8 @@ static ssize_t store_cpufv(struct device *dev, struct eeepc_cpufv c; int rv, value; + if (eeepc->cpufv_disabled) + return -EPERM; if (get_cpufv(eeepc, &c)) return -ENODEV; rv = parse_arg(buf, count, &value); @@ -389,6 +386,41 @@ static ssize_t store_cpufv(struct device *dev, return rv; } +static ssize_t show_cpufv_disabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct eeepc_laptop *eeepc = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", eeepc->cpufv_disabled); +} + +static ssize_t store_cpufv_disabled(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct eeepc_laptop *eeepc = dev_get_drvdata(dev); + int rv, value; + + rv = parse_arg(buf, count, &value); + if (rv < 0) + return rv; + + switch (value) { + case 0: + if (eeepc->cpufv_disabled) + pr_warn("cpufv enabled (not officially supported " + "on this model)\n"); + eeepc->cpufv_disabled = false; + return rv; + case 1: + return -EPERM; + default: + return -EINVAL; + } +} + + static struct device_attribute dev_attr_cpufv = { .attr = { .name = "cpufv", @@ -404,12 +436,22 @@ static struct device_attribute dev_attr_available_cpufv = { .show = show_available_cpufv }; +static struct device_attribute dev_attr_cpufv_disabled = { + .attr = { + .name = "cpufv_disabled", + .mode = 0644 }, + .show = show_cpufv_disabled, + .store = store_cpufv_disabled +}; + + static struct attribute *platform_attributes[] = { &dev_attr_camera.attr, &dev_attr_cardr.attr, &dev_attr_disp.attr, &dev_attr_cpufv.attr, &dev_attr_available_cpufv.attr, + &dev_attr_cpufv_disabled.attr, NULL }; @@ -479,6 +521,15 @@ static void tpd_led_set(struct led_classdev *led_cdev, queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work); } +static enum led_brightness tpd_led_get(struct led_classdev *led_cdev) +{ + struct eeepc_laptop *eeepc; + + eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led); + + return get_acpi(eeepc, CM_ASL_TPD); +} + static int eeepc_led_init(struct eeepc_laptop *eeepc) { int rv; @@ -493,6 +544,8 @@ static int eeepc_led_init(struct eeepc_laptop *eeepc) eeepc->tpd_led.name = "eeepc::touchpad"; eeepc->tpd_led.brightness_set = tpd_led_set; + if (get_acpi(eeepc, CM_ASL_TPD) >= 0) /* if method is available */ + eeepc->tpd_led.brightness_get = tpd_led_get; eeepc->tpd_led.max_brightness = 1; rv = led_classdev_register(&eeepc->platform_device->dev, @@ -507,7 +560,7 @@ static int eeepc_led_init(struct eeepc_laptop *eeepc) static void eeepc_led_exit(struct eeepc_laptop *eeepc) { - if (eeepc->tpd_led.dev) + if (!IS_ERR_OR_NULL(eeepc->tpd_led.dev)) led_classdev_unregister(&eeepc->tpd_led); if (eeepc->led_workqueue) destroy_workqueue(eeepc->led_workqueue); @@ -524,50 +577,91 @@ static bool eeepc_wlan_rfkill_blocked(struct eeepc_laptop *eeepc) return true; } -static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc) +static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle) { + struct pci_dev *port; struct pci_dev *dev; struct pci_bus *bus; bool blocked = eeepc_wlan_rfkill_blocked(eeepc); + bool absent; + u32 l; if (eeepc->wlan_rfkill) rfkill_set_sw_state(eeepc->wlan_rfkill, blocked); mutex_lock(&eeepc->hotplug_lock); + pci_lock_rescan_remove(); if (eeepc->hotplug_slot) { - bus = pci_find_bus(0, 1); - if (!bus) { - pr_warning("Unable to find PCI bus 1?\n"); + port = acpi_get_pci_dev(handle); + if (!port) { + pr_warning("Unable to find port\n"); goto out_unlock; } + bus = port->subordinate; + + if (!bus) { + pr_warn("Unable to find PCI bus 1?\n"); + goto out_put_dev; + } + + if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) { + pr_err("Unable to read PCI config space?\n"); + goto out_put_dev; + } + + absent = (l == 0xffffffff); + + if (blocked != absent) { + pr_warn("BIOS says wireless lan is %s, " + "but the pci device is %s\n", + blocked ? "blocked" : "unblocked", + absent ? "absent" : "present"); + pr_warn("skipped wireless hotplug as probably " + "inappropriate for this model\n"); + goto out_put_dev; + } + if (!blocked) { dev = pci_get_slot(bus, 0); if (dev) { /* Device already present */ pci_dev_put(dev); - goto out_unlock; + goto out_put_dev; } dev = pci_scan_single_device(bus, 0); if (dev) { pci_bus_assign_resources(bus); - if (pci_bus_add_device(dev)) - pr_err("Unable to hotplug wifi\n"); + pci_bus_add_device(dev); } } else { dev = pci_get_slot(bus, 0); if (dev) { - pci_remove_bus_device(dev); + pci_stop_and_remove_bus_device(dev); pci_dev_put(dev); } } +out_put_dev: + pci_dev_put(port); } out_unlock: + pci_unlock_rescan_remove(); mutex_unlock(&eeepc->hotplug_lock); } +static void eeepc_rfkill_hotplug_update(struct eeepc_laptop *eeepc, char *node) +{ + acpi_status status = AE_OK; + acpi_handle handle; + + status = acpi_get_handle(NULL, node, &handle); + + if (ACPI_SUCCESS(status)) + eeepc_rfkill_hotplug(eeepc, handle); +} + static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) { struct eeepc_laptop *eeepc = data; @@ -575,7 +669,7 @@ static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) if (event != ACPI_NOTIFY_BUS_CHECK) return; - eeepc_rfkill_hotplug(eeepc); + eeepc_rfkill_hotplug(eeepc, handle); } static int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc, @@ -592,7 +686,13 @@ static int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc, eeepc_rfkill_notify, eeepc); if (ACPI_FAILURE(status)) - pr_warning("Failed to register notify on %s\n", node); + pr_warn("Failed to register notify on %s\n", node); + + /* + * Refresh pci hotplug in case the rfkill state was + * changed during setup. + */ + eeepc_rfkill_hotplug(eeepc, handle); } else return -ENODEV; @@ -614,6 +714,12 @@ static void eeepc_unregister_rfkill_notifier(struct eeepc_laptop *eeepc, if (ACPI_FAILURE(status)) pr_err("Error removing rfkill notify handler %s\n", node); + /* + * Refresh pci hotplug in case the rfkill + * state was changed after + * eeepc_unregister_rfkill_notifier() + */ + eeepc_rfkill_hotplug(eeepc, handle); } } @@ -737,11 +843,7 @@ static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc) rfkill_destroy(eeepc->wlan_rfkill); eeepc->wlan_rfkill = NULL; } - /* - * Refresh pci hotplug in case the rfkill state was changed after - * eeepc_unregister_rfkill_notifier() - */ - eeepc_rfkill_hotplug(eeepc); + if (eeepc->hotplug_slot) pci_hp_deregister(eeepc->hotplug_slot); @@ -796,6 +898,9 @@ static int eeepc_rfkill_init(struct eeepc_laptop *eeepc) if (result && result != -ENODEV) goto exit; + if (eeepc->hotplug_disabled) + return 0; + result = eeepc_setup_pci_hotplug(eeepc); /* * If we get -EBUSY then something else is handling the PCI hotplug - @@ -807,11 +912,6 @@ static int eeepc_rfkill_init(struct eeepc_laptop *eeepc) eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5"); eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6"); eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7"); - /* - * Refresh pci hotplug in case the rfkill state was changed during - * setup. - */ - eeepc_rfkill_hotplug(eeepc); exit: if (result && result != -ENODEV) @@ -846,8 +946,11 @@ static int eeepc_hotk_restore(struct device *device) struct eeepc_laptop *eeepc = dev_get_drvdata(device); /* Refresh both wlan rfkill state and pci hotplug */ - if (eeepc->wlan_rfkill) - eeepc_rfkill_hotplug(eeepc); + if (eeepc->wlan_rfkill) { + eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P5"); + eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P6"); + eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P7"); + } if (eeepc->bluetooth_rfkill) rfkill_set_sw_state(eeepc->bluetooth_rfkill, @@ -897,7 +1000,7 @@ static int eeepc_get_fan_pwm(void) static void eeepc_set_fan_pwm(int value) { - value = SENSORS_LIMIT(value, 0, 255); + value = clamp_val(value, 0, 255); value = value * 100 / 255; ec_write(EEEPC_EC_FAN_PWM, value); } @@ -963,7 +1066,7 @@ static ssize_t show_sys_hwmon(int (*get)(void), char *buf) { \ return store_sys_hwmon(_get, buf, count); \ } \ - static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0); + static DEVICE_ATTR(_name, _mode, show_##_name, store_##_name); EEEPC_CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, eeepc_get_fan_rpm, NULL); EEEPC_CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR, @@ -971,55 +1074,26 @@ EEEPC_CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR, EEEPC_CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, eeepc_get_fan_ctrl, eeepc_set_fan_ctrl); -static ssize_t -show_name(struct device *dev, struct device_attribute *attr, char *buf) -{ - return sprintf(buf, "eeepc\n"); -} -static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); - -static struct attribute *hwmon_attributes[] = { - &sensor_dev_attr_pwm1.dev_attr.attr, - &sensor_dev_attr_fan1_input.dev_attr.attr, - &sensor_dev_attr_pwm1_enable.dev_attr.attr, - &sensor_dev_attr_name.dev_attr.attr, +static struct attribute *hwmon_attrs[] = { + &dev_attr_pwm1.attr, + &dev_attr_fan1_input.attr, + &dev_attr_pwm1_enable.attr, NULL }; - -static struct attribute_group hwmon_attribute_group = { - .attrs = hwmon_attributes -}; - -static void eeepc_hwmon_exit(struct eeepc_laptop *eeepc) -{ - struct device *hwmon; - - hwmon = eeepc->hwmon_device; - if (!hwmon) - return; - sysfs_remove_group(&hwmon->kobj, - &hwmon_attribute_group); - hwmon_device_unregister(hwmon); - eeepc->hwmon_device = NULL; -} +ATTRIBUTE_GROUPS(hwmon); static int eeepc_hwmon_init(struct eeepc_laptop *eeepc) { + struct device *dev = &eeepc->platform_device->dev; struct device *hwmon; - int result; - hwmon = hwmon_device_register(&eeepc->platform_device->dev); + hwmon = devm_hwmon_device_register_with_groups(dev, "eeepc", NULL, + hwmon_groups); if (IS_ERR(hwmon)) { pr_err("Could not register eeepc hwmon device\n"); - eeepc->hwmon_device = NULL; return PTR_ERR(hwmon); } - eeepc->hwmon_device = hwmon; - result = sysfs_create_group(&hwmon->kobj, - &hwmon_attribute_group); - if (result) - eeepc_hwmon_exit(eeepc); - return result; + return 0; } /* @@ -1044,7 +1118,7 @@ static int update_bl_status(struct backlight_device *bd) return set_brightness(bd, bd->props.brightness); } -static struct backlight_ops eeepcbl_ops = { +static const struct backlight_ops eeepcbl_ops = { .get_brightness = read_brightness, .update_status = update_bl_status, }; @@ -1061,18 +1135,21 @@ static int eeepc_backlight_notify(struct eeepc_laptop *eeepc) static int eeepc_backlight_init(struct eeepc_laptop *eeepc) { + struct backlight_properties props; struct backlight_device *bd; + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = 15; bd = backlight_device_register(EEEPC_LAPTOP_FILE, - &eeepc->platform_device->dev, - eeepc, &eeepcbl_ops); + &eeepc->platform_device->dev, eeepc, + &eeepcbl_ops, &props); if (IS_ERR(bd)) { pr_err("Could not register eeepc backlight device\n"); eeepc->backlight_device = NULL; return PTR_ERR(bd); } eeepc->backlight_device = bd; - bd->props.max_brightness = 15; bd->props.brightness = read_brightness(bd); bd->props.power = FB_BLANK_UNBLANK; backlight_update_status(bd); @@ -1090,133 +1167,62 @@ static void eeepc_backlight_exit(struct eeepc_laptop *eeepc) /* * Input device (i.e. hotkeys) */ -static struct key_entry *eeepc_get_entry_by_scancode( - struct eeepc_laptop *eeepc, - int code) -{ - struct key_entry *key; - - for (key = eeepc->keymap; key->type != KE_END; key++) - if (code == key->code) - return key; - - return NULL; -} - -static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event) -{ - static struct key_entry *key; - - key = eeepc_get_entry_by_scancode(eeepc, event); - if (key) { - switch (key->type) { - case KE_KEY: - input_report_key(eeepc->inputdev, key->keycode, - 1); - input_sync(eeepc->inputdev); - input_report_key(eeepc->inputdev, key->keycode, - 0); - input_sync(eeepc->inputdev); - break; - } - } -} - -static struct key_entry *eeepc_get_entry_by_keycode( - struct eeepc_laptop *eeepc, int code) +static int eeepc_input_init(struct eeepc_laptop *eeepc) { - struct key_entry *key; - - for (key = eeepc->keymap; key->type != KE_END; key++) - if (code == key->keycode && key->type == KE_KEY) - return key; + struct input_dev *input; + int error; - return NULL; -} + input = input_allocate_device(); + if (!input) + return -ENOMEM; -static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode) -{ - struct eeepc_laptop *eeepc = input_get_drvdata(dev); - struct key_entry *key = eeepc_get_entry_by_scancode(eeepc, scancode); + input->name = "Asus EeePC extra buttons"; + input->phys = EEEPC_LAPTOP_FILE "/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &eeepc->platform_device->dev; - if (key && key->type == KE_KEY) { - *keycode = key->keycode; - return 0; + error = sparse_keymap_setup(input, eeepc_keymap, NULL); + if (error) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; } - return -EINVAL; -} - -static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode) -{ - struct eeepc_laptop *eeepc = input_get_drvdata(dev); - struct key_entry *key; - int old_keycode; - - if (keycode < 0 || keycode > KEY_MAX) - return -EINVAL; - - key = eeepc_get_entry_by_scancode(eeepc, scancode); - if (key && key->type == KE_KEY) { - old_keycode = key->keycode; - key->keycode = keycode; - set_bit(keycode, dev->keybit); - if (!eeepc_get_entry_by_keycode(eeepc, old_keycode)) - clear_bit(old_keycode, dev->keybit); - return 0; + error = input_register_device(input); + if (error) { + pr_err("Unable to register input device\n"); + goto err_free_keymap; } - return -EINVAL; -} - -static int eeepc_input_init(struct eeepc_laptop *eeepc) -{ - const struct key_entry *key; - int result; - - eeepc->inputdev = input_allocate_device(); - if (!eeepc->inputdev) { - pr_info("Unable to allocate input device\n"); - return -ENOMEM; - } - eeepc->inputdev->name = "Asus EeePC extra buttons"; - eeepc->inputdev->dev.parent = &eeepc->platform_device->dev; - eeepc->inputdev->phys = EEEPC_LAPTOP_FILE "/input0"; - eeepc->inputdev->id.bustype = BUS_HOST; - eeepc->inputdev->getkeycode = eeepc_getkeycode; - eeepc->inputdev->setkeycode = eeepc_setkeycode; - input_set_drvdata(eeepc->inputdev, eeepc); - - eeepc->keymap = kmemdup(eeepc_keymap, sizeof(eeepc_keymap), - GFP_KERNEL); - for (key = eeepc_keymap; key->type != KE_END; key++) { - switch (key->type) { - case KE_KEY: - set_bit(EV_KEY, eeepc->inputdev->evbit); - set_bit(key->keycode, eeepc->inputdev->keybit); - break; - } - } - result = input_register_device(eeepc->inputdev); - if (result) { - pr_info("Unable to register input device\n"); - input_free_device(eeepc->inputdev); - return result; - } + eeepc->inputdev = input; return 0; + +err_free_keymap: + sparse_keymap_free(input); +err_free_dev: + input_free_device(input); + return error; } static void eeepc_input_exit(struct eeepc_laptop *eeepc) { if (eeepc->inputdev) { + sparse_keymap_free(eeepc->inputdev); input_unregister_device(eeepc->inputdev); - kfree(eeepc->keymap); } + eeepc->inputdev = NULL; } /* * ACPI driver */ +static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event) +{ + if (!eeepc->inputdev) + return ; + if (!sparse_keymap_report_event(eeepc->inputdev, event, 1, true)) + pr_info("Unknown key %x pressed\n", event); +} + static void eeepc_acpi_notify(struct acpi_device *device, u32 event) { struct eeepc_laptop *eeepc = acpi_driver_data(device); @@ -1225,7 +1231,6 @@ static void eeepc_acpi_notify(struct acpi_device *device, u32 event) if (event > ACPI_MAX_SYS_NOTIFY) return; count = eeepc->event_count[event % 128]++; - acpi_bus_generate_proc_event(device, event, count); acpi_bus_generate_netlink_event(device->pnp.device_class, dev_name(&device->dev), event, count); @@ -1261,11 +1266,59 @@ static void eeepc_acpi_notify(struct acpi_device *device, u32 event) } } +static void eeepc_dmi_check(struct eeepc_laptop *eeepc) +{ + const char *model; + + model = dmi_get_system_info(DMI_PRODUCT_NAME); + if (!model) + return; + + /* + * Blacklist for setting cpufv (cpu speed). + * + * EeePC 4G ("701") implements CFVS, but it is not supported + * by the pre-installed OS, and the original option to change it + * in the BIOS setup screen was removed in later versions. + * + * Judging by the lack of "Super Hybrid Engine" on Asus product pages, + * this applies to all "701" models (4G/4G Surf/2G Surf). + * + * So Asus made a deliberate decision not to support it on this model. + * We have several reports that using it can cause the system to hang + * + * The hang has also been reported on a "702" (Model name "8G"?). + * + * We avoid dmi_check_system() / dmi_match(), because they use + * substring matching. We don't want to affect the "701SD" + * and "701SDX" models, because they do support S.H.E. + */ + if (strcmp(model, "701") == 0 || strcmp(model, "702") == 0) { + eeepc->cpufv_disabled = true; + pr_info("model %s does not officially support setting cpu " + "speed\n", model); + pr_info("cpufv disabled to avoid instability\n"); + } + + /* + * Blacklist for wlan hotplug + * + * Eeepc 1005HA doesn't work like others models and don't need the + * hotplug code. In fact, current hotplug code seems to unplug another + * device... + */ + if (strcmp(model, "1005HA") == 0 || strcmp(model, "1201N") == 0 || + strcmp(model, "1005PE") == 0) { + eeepc->hotplug_disabled = true; + pr_info("wlan hotplug disabled\n"); + } +} + static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name) { int dummy; - /* Some BIOSes do not report cm although it is avaliable. + /* Some BIOSes do not report cm although it is available. Check if cm_getv[cm] works and, if yes, assume cm should be set. */ if (!(eeepc->cm_supported & (1 << cm)) && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) { @@ -1283,16 +1336,15 @@ static void cmsg_quirks(struct eeepc_laptop *eeepc) cmsg_quirk(eeepc, CM_ASL_TPD, "TPD"); } -static int eeepc_acpi_init(struct eeepc_laptop *eeepc, - struct acpi_device *device) +static int eeepc_acpi_init(struct eeepc_laptop *eeepc) { unsigned int init_flags; int result; - result = acpi_bus_get_status(device); + result = acpi_bus_get_status(eeepc->device); if (result) return result; - if (!device->status.present) { + if (!eeepc->device->status.present) { pr_err("Hotkey device not present, aborting\n"); return -ENODEV; } @@ -1316,7 +1368,7 @@ static int eeepc_acpi_init(struct eeepc_laptop *eeepc, return 0; } -static void __devinit eeepc_enable_camera(struct eeepc_laptop *eeepc) +static void eeepc_enable_camera(struct eeepc_laptop *eeepc) { /* * If the following call to set_acpi() fails, it's because there's no @@ -1328,7 +1380,7 @@ static void __devinit eeepc_enable_camera(struct eeepc_laptop *eeepc) static bool eeepc_device_present; -static int __devinit eeepc_acpi_add(struct acpi_device *device) +static int eeepc_acpi_add(struct acpi_device *device) { struct eeepc_laptop *eeepc; int result; @@ -1341,8 +1393,13 @@ static int __devinit eeepc_acpi_add(struct acpi_device *device) strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS); device->driver_data = eeepc; + eeepc->device = device; - result = eeepc_acpi_init(eeepc, device); + eeepc->hotplug_disabled = hotplug_disabled; + + eeepc_dmi_check(eeepc); + + result = eeepc_acpi_init(eeepc); if (result) goto fail_platform; eeepc_enable_camera(eeepc); @@ -1392,7 +1449,6 @@ static int __devinit eeepc_acpi_add(struct acpi_device *device) fail_rfkill: eeepc_led_exit(eeepc); fail_led: - eeepc_hwmon_exit(eeepc); fail_hwmon: eeepc_input_exit(eeepc); fail_input: @@ -1405,14 +1461,13 @@ fail_platform: return result; } -static int eeepc_acpi_remove(struct acpi_device *device, int type) +static int eeepc_acpi_remove(struct acpi_device *device) { struct eeepc_laptop *eeepc = acpi_driver_data(device); eeepc_backlight_exit(eeepc); eeepc_rfkill_exit(eeepc); eeepc_input_exit(eeepc); - eeepc_hwmon_exit(eeepc); eeepc_led_exit(eeepc); eeepc_platform_exit(eeepc); @@ -1452,10 +1507,12 @@ static int __init eeepc_laptop_init(void) result = acpi_bus_register_driver(&eeepc_acpi_driver); if (result < 0) goto fail_acpi_driver; + if (!eeepc_device_present) { result = -ENODEV; goto fail_no_device; } + return 0; fail_no_device: diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c new file mode 100644 index 00000000000..6112933f627 --- /dev/null +++ b/drivers/platform/x86/eeepc-wmi.c @@ -0,0 +1,278 @@ +/* + * Eee PC WMI hotkey driver + * + * Copyright(C) 2010 Intel Corporation. + * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com> + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/dmi.h> +#include <linux/fb.h> +#include <linux/acpi.h> + +#include "asus-wmi.h" + +#define EEEPC_WMI_FILE "eeepc-wmi" + +MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>"); +MODULE_DESCRIPTION("Eee PC WMI Hotkey Driver"); +MODULE_LICENSE("GPL"); + +#define EEEPC_ACPI_HID "ASUS010" /* old _HID used in eeepc-laptop */ + +#define EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" + +MODULE_ALIAS("wmi:"EEEPC_WMI_EVENT_GUID); + +static bool hotplug_wireless; + +module_param(hotplug_wireless, bool, 0444); +MODULE_PARM_DESC(hotplug_wireless, + "Enable hotplug for wireless device. " + "If your laptop needs that, please report to " + "acpi4asus-user@lists.sourceforge.net."); + +/* Values for T101MT "Home" key */ +#define HOME_PRESS 0xe4 +#define HOME_HOLD 0xea +#define HOME_RELEASE 0xe5 + +static const struct key_entry eeepc_wmi_keymap[] = { + { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } }, + /* Sleep already handled via generic ACPI code */ + { KE_KEY, 0x30, { KEY_VOLUMEUP } }, + { KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0x32, { KEY_MUTE } }, + { KE_KEY, 0x5c, { KEY_F15 } }, /* Power Gear key */ + { KE_KEY, 0x5d, { KEY_WLAN } }, + { KE_KEY, 0x6b, { KEY_TOUCHPAD_TOGGLE } }, /* Toggle Touchpad */ + { KE_KEY, 0x82, { KEY_CAMERA } }, + { KE_KEY, 0x83, { KEY_CAMERA_ZOOMIN } }, + { KE_KEY, 0x88, { KEY_WLAN } }, + { KE_KEY, 0xbd, { KEY_CAMERA } }, + { KE_KEY, 0xcc, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 0xe0, { KEY_PROG1 } }, /* Task Manager */ + { KE_KEY, 0xe1, { KEY_F14 } }, /* Change Resolution */ + { KE_KEY, HOME_PRESS, { KEY_CONFIG } }, /* Home/Express gate key */ + { KE_KEY, 0xe8, { KEY_SCREENLOCK } }, + { KE_KEY, 0xe9, { KEY_DISPLAYTOGGLE } }, + { KE_KEY, 0xeb, { KEY_CAMERA_ZOOMOUT } }, + { KE_KEY, 0xec, { KEY_CAMERA_UP } }, + { KE_KEY, 0xed, { KEY_CAMERA_DOWN } }, + { KE_KEY, 0xee, { KEY_CAMERA_LEFT } }, + { KE_KEY, 0xef, { KEY_CAMERA_RIGHT } }, + { KE_KEY, 0xf3, { KEY_MENU } }, + { KE_KEY, 0xf5, { KEY_HOMEPAGE } }, + { KE_KEY, 0xf6, { KEY_ESC } }, + { KE_END, 0}, +}; + +static struct quirk_entry quirk_asus_unknown = { +}; + +static struct quirk_entry quirk_asus_1000h = { + .hotplug_wireless = true, +}; + +static struct quirk_entry quirk_asus_et2012_type1 = { + .store_backlight_power = true, +}; + +static struct quirk_entry quirk_asus_et2012_type3 = { + .scalar_panel_brightness = true, + .store_backlight_power = true, +}; + +static struct quirk_entry quirk_asus_x101ch = { + /* We need this when ACPI function doesn't do this well */ + .wmi_backlight_power = true, +}; + +static struct quirk_entry *quirks; + +static void et2012_quirks(void) +{ + const struct dmi_device *dev = NULL; + char oemstring[30]; + + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { + if (sscanf(dev->name, "AEMS%24c", oemstring) == 1) { + if (oemstring[18] == '1') + quirks = &quirk_asus_et2012_type1; + else if (oemstring[18] == '3') + quirks = &quirk_asus_et2012_type3; + break; + } + } +} + +static int dmi_matched(const struct dmi_system_id *dmi) +{ + char *model; + + quirks = dmi->driver_data; + + model = (char *)dmi->matches[1].substr; + if (unlikely(strncmp(model, "ET2012", 6) == 0)) + et2012_quirks(); + + return 1; +} + +static struct dmi_system_id asus_quirks[] = { + { + .callback = dmi_matched, + .ident = "ASUSTeK Computer INC. 1000H", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "1000H"), + }, + .driver_data = &quirk_asus_1000h, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK Computer INC. ET2012E/I", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ET2012"), + }, + .driver_data = &quirk_asus_unknown, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK Computer INC. X101CH", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X101CH"), + }, + .driver_data = &quirk_asus_x101ch, + }, + { + .callback = dmi_matched, + .ident = "ASUSTeK Computer INC. 1015CX", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "1015CX"), + }, + .driver_data = &quirk_asus_x101ch, + }, + {}, +}; + +static void eeepc_wmi_key_filter(struct asus_wmi_driver *asus_wmi, int *code, + unsigned int *value, bool *autorelease) +{ + switch (*code) { + case HOME_PRESS: + *value = 1; + *autorelease = 0; + break; + case HOME_HOLD: + *code = ASUS_WMI_KEY_IGNORE; + break; + case HOME_RELEASE: + *code = HOME_PRESS; + *value = 0; + *autorelease = 0; + break; + } +} + +static acpi_status eeepc_wmi_parse_device(acpi_handle handle, u32 level, + void *context, void **retval) +{ + pr_warn("Found legacy ATKD device (%s)\n", EEEPC_ACPI_HID); + *(bool *)context = true; + return AE_CTRL_TERMINATE; +} + +static int eeepc_wmi_check_atkd(void) +{ + acpi_status status; + bool found = false; + + status = acpi_get_devices(EEEPC_ACPI_HID, eeepc_wmi_parse_device, + &found, NULL); + + if (ACPI_FAILURE(status) || !found) + return 0; + return -1; +} + +static int eeepc_wmi_probe(struct platform_device *pdev) +{ + if (eeepc_wmi_check_atkd()) { + pr_warn("WMI device present, but legacy ATKD device is also " + "present and enabled\n"); + pr_warn("You probably booted with acpi_osi=\"Linux\" or " + "acpi_osi=\"!Windows 2009\"\n"); + pr_warn("Can't load eeepc-wmi, use default acpi_osi " + "(preferred) or eeepc-laptop\n"); + return -EBUSY; + } + return 0; +} + +static void eeepc_wmi_quirks(struct asus_wmi_driver *driver) +{ + quirks = &quirk_asus_unknown; + quirks->hotplug_wireless = hotplug_wireless; + + dmi_check_system(asus_quirks); + + driver->quirks = quirks; + driver->quirks->wapf = -1; + driver->panel_power = FB_BLANK_UNBLANK; +} + +static struct asus_wmi_driver asus_wmi_driver = { + .name = EEEPC_WMI_FILE, + .owner = THIS_MODULE, + .event_guid = EEEPC_WMI_EVENT_GUID, + .keymap = eeepc_wmi_keymap, + .input_name = "Eee PC WMI hotkeys", + .input_phys = EEEPC_WMI_FILE "/input0", + .key_filter = eeepc_wmi_key_filter, + .probe = eeepc_wmi_probe, + .detect_quirks = eeepc_wmi_quirks, +}; + + +static int __init eeepc_wmi_init(void) +{ + return asus_wmi_register_driver(&asus_wmi_driver); +} + +static void __exit eeepc_wmi_exit(void) +{ + asus_wmi_unregister_driver(&asus_wmi_driver); +} + +module_init(eeepc_wmi_init); +module_exit(eeepc_wmi_exit); diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 5f3320d468f..e6f336270c2 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -1,7 +1,7 @@ /*-*-linux-c-*-*/ /* - Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@physics.adelaide.edu.au> + Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net> Copyright (C) 2008 Peter Gruber <nokos@gmx.net> Copyright (C) 2008 Tony Vroon <tony@linx.net> Based on earlier work: @@ -56,6 +56,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> @@ -64,8 +66,8 @@ #include <linux/backlight.h> #include <linux/input.h> #include <linux/kfifo.h> -#include <linux/video_output.h> #include <linux/platform_device.h> +#include <linux/slab.h> #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) #include <linux/leds.h> #endif @@ -181,7 +183,7 @@ static enum led_brightness logolamp_get(struct led_classdev *cdev); static void logolamp_set(struct led_classdev *cdev, enum led_brightness brightness); -struct led_classdev logolamp_led = { +static struct led_classdev logolamp_led = { .name = "fujitsu::logolamp", .brightness_get = logolamp_get, .brightness_set = logolamp_set @@ -191,7 +193,7 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev); static void kblamps_set(struct led_classdev *cdev, enum led_brightness brightness); -struct led_classdev kblamps_led = { +static struct led_classdev kblamps_led = { .name = "fujitsu::kblamps", .brightness_get = kblamps_get, .brightness_set = kblamps_set @@ -216,8 +218,7 @@ static int call_fext_func(int cmd, int arg0, int arg1, int arg2) { .type = ACPI_TYPE_INTEGER } }; struct acpi_object_list arg_list = { 4, ¶ms[0] }; - struct acpi_buffer output; - union acpi_object out_obj; + unsigned long long value; acpi_handle handle = NULL; status = acpi_get_handle(fujitsu_hotkey->acpi_handle, "FUNC", &handle); @@ -232,10 +233,7 @@ static int call_fext_func(int cmd, int arg0, int arg1, int arg2) params[2].integer.value = arg1; params[3].integer.value = arg2; - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = acpi_evaluate_object(handle, NULL, &arg_list, &output); + status = acpi_evaluate_integer(handle, NULL, &arg_list, &value); if (ACPI_FAILURE(status)) { vdbg_printk(FUJLAPTOP_DBG_WARN, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) call failed\n", @@ -243,18 +241,10 @@ static int call_fext_func(int cmd, int arg0, int arg1, int arg2) return -ENODEV; } - if (out_obj.type != ACPI_TYPE_INTEGER) { - vdbg_printk(FUJLAPTOP_DBG_WARN, - "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) did not " - "return an integer\n", - cmd, arg0, arg1, arg2); - return -ENODEV; - } - vdbg_printk(FUJLAPTOP_DBG_TRACE, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n", - cmd, arg0, arg1, arg2, (int)out_obj.integer.value); - return out_obj.integer.value; + cmd, arg0, arg1, arg2, (int)value); + return value; } #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) @@ -314,8 +304,6 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev) static int set_lcd_level(int level) { acpi_status status = AE_OK; - union acpi_object arg0 = { ACPI_TYPE_INTEGER }; - struct acpi_object_list arg_list = { 1, &arg0 }; acpi_handle handle = NULL; vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBLL [%d]\n", @@ -330,9 +318,8 @@ static int set_lcd_level(int level) return -ENODEV; } - arg0.integer.value = level; - status = acpi_evaluate_object(handle, NULL, &arg_list, NULL); + status = acpi_execute_simple_method(handle, NULL, level); if (ACPI_FAILURE(status)) return -ENODEV; @@ -342,8 +329,6 @@ static int set_lcd_level(int level) static int set_lcd_level_alt(int level) { acpi_status status = AE_OK; - union acpi_object arg0 = { ACPI_TYPE_INTEGER }; - struct acpi_object_list arg_list = { 1, &arg0 }; acpi_handle handle = NULL; vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBL2 [%d]\n", @@ -358,9 +343,7 @@ static int set_lcd_level_alt(int level) return -ENODEV; } - arg0.integer.value = level; - - status = acpi_evaluate_object(handle, NULL, &arg_list, NULL); + status = acpi_execute_simple_method(handle, NULL, level); if (ACPI_FAILURE(status)) return -ENODEV; @@ -436,7 +419,7 @@ static int bl_update_status(struct backlight_device *b) return ret; } -static struct backlight_ops fujitsubl_ops = { +static const struct backlight_ops fujitsubl_ops = { .get_brightness = bl_get_brightness, .update_status = bl_update_status, }; @@ -583,12 +566,10 @@ static struct platform_driver fujitsupf_driver = { static void dmi_check_cb_common(const struct dmi_system_id *id) { - acpi_handle handle; - printk(KERN_INFO "fujitsu-laptop: Identified laptop model '%s'.\n", - id->ident); + pr_info("Identified laptop model '%s'\n", id->ident); if (use_alt_lcd_levels == -1) { - if (ACPI_SUCCESS(acpi_get_handle(NULL, - "\\_SB.PCI0.LPCB.FJEX.SBL2", &handle))) + if (acpi_has_method(NULL, + "\\_SB.PCI0.LPCB.FJEX.SBL2")) use_alt_lcd_levels = 1; else use_alt_lcd_levels = 0; @@ -602,7 +583,7 @@ static int dmi_check_cb_s6410(const struct dmi_system_id *id) dmi_check_cb_common(id); fujitsu->keycode1 = KEY_SCREENLOCK; /* "Lock" */ fujitsu->keycode2 = KEY_HELP; /* "Mobility Center" */ - return 0; + return 1; } static int dmi_check_cb_s6420(const struct dmi_system_id *id) @@ -610,7 +591,7 @@ static int dmi_check_cb_s6420(const struct dmi_system_id *id) dmi_check_cb_common(id); fujitsu->keycode1 = KEY_SCREENLOCK; /* "Lock" */ fujitsu->keycode2 = KEY_HELP; /* "Mobility Center" */ - return 0; + return 1; } static int dmi_check_cb_p8010(const struct dmi_system_id *id) @@ -619,7 +600,7 @@ static int dmi_check_cb_p8010(const struct dmi_system_id *id) fujitsu->keycode1 = KEY_HELP; /* "Support" */ fujitsu->keycode3 = KEY_SWITCHVIDEOMODE; /* "Presentation" */ fujitsu->keycode4 = KEY_WWW; /* "Internet" */ - return 0; + return 1; } static struct dmi_system_id fujitsu_dmi_table[] = { @@ -651,8 +632,6 @@ static struct dmi_system_id fujitsu_dmi_table[] = { static int acpi_fujitsu_add(struct acpi_device *device) { - acpi_handle handle; - int result = 0; int state = 0; struct input_dev *input; int error; @@ -688,25 +667,24 @@ static int acpi_fujitsu_add(struct acpi_device *device) if (error) goto err_free_input_dev; - result = acpi_bus_get_power(fujitsu->acpi_handle, &state); - if (result) { - printk(KERN_ERR "Error reading power state\n"); + error = acpi_bus_update_power(fujitsu->acpi_handle, &state); + if (error) { + pr_err("Error reading power state\n"); goto err_unregister_input_dev; } - printk(KERN_INFO "ACPI: %s [%s] (%s)\n", + pr_info("ACPI: %s [%s] (%s)\n", acpi_device_name(device), acpi_device_bid(device), !device->power.state ? "on" : "off"); fujitsu->dev = device; - if (ACPI_SUCCESS - (acpi_get_handle(device->handle, METHOD_NAME__INI, &handle))) { + if (acpi_has_method(device->handle, METHOD_NAME__INI)) { vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n"); if (ACPI_FAILURE (acpi_evaluate_object (device->handle, METHOD_NAME__INI, NULL, NULL))) - printk(KERN_ERR "_INI Method failed\n"); + pr_err("_INI Method failed\n"); } /* do config (detect defaults) */ @@ -720,25 +698,24 @@ static int acpi_fujitsu_add(struct acpi_device *device) fujitsu->max_brightness = FUJITSU_LCD_N_LEVELS; get_lcd_level(); - return result; + return 0; err_unregister_input_dev: input_unregister_device(input); + input = NULL; err_free_input_dev: input_free_device(input); err_stop: - return result; + return error; } -static int acpi_fujitsu_remove(struct acpi_device *device, int type) +static int acpi_fujitsu_remove(struct acpi_device *device) { struct fujitsu_t *fujitsu = acpi_driver_data(device); struct input_dev *input = fujitsu->input; input_unregister_device(input); - input_free_device(input); - fujitsu->acpi_handle = NULL; return 0; @@ -772,8 +749,6 @@ static void acpi_fujitsu_notify(struct acpi_device *device, u32 event) else set_lcd_level(newb); } - acpi_bus_generate_proc_event(fujitsu->dev, - ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, 0); keycode = KEY_BRIGHTNESSUP; } else if (oldb > newb) { if (disable_brightness_adjust != 1) { @@ -782,8 +757,6 @@ static void acpi_fujitsu_notify(struct acpi_device *device, u32 event) else set_lcd_level(newb); } - acpi_bus_generate_proc_event(fujitsu->dev, - ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, 0); keycode = KEY_BRIGHTNESSDOWN; } break; @@ -806,7 +779,6 @@ static void acpi_fujitsu_notify(struct acpi_device *device, u32 event) static int acpi_fujitsu_hotkey_add(struct acpi_device *device) { - acpi_handle handle; int result = 0; int state = 0; struct input_dev *input; @@ -827,7 +799,7 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) error = kfifo_alloc(&fujitsu_hotkey->fifo, RINGBUFFERSIZE * sizeof(int), GFP_KERNEL); if (error) { - printk(KERN_ERR "kfifo_alloc failed\n"); + pr_err("kfifo_alloc failed\n"); goto err_stop; } @@ -857,25 +829,24 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) if (error) goto err_free_input_dev; - result = acpi_bus_get_power(fujitsu_hotkey->acpi_handle, &state); - if (result) { - printk(KERN_ERR "Error reading power state\n"); + error = acpi_bus_update_power(fujitsu_hotkey->acpi_handle, &state); + if (error) { + pr_err("Error reading power state\n"); goto err_unregister_input_dev; } - printk(KERN_INFO "ACPI: %s [%s] (%s)\n", - acpi_device_name(device), acpi_device_bid(device), - !device->power.state ? "on" : "off"); + pr_info("ACPI: %s [%s] (%s)\n", + acpi_device_name(device), acpi_device_bid(device), + !device->power.state ? "on" : "off"); fujitsu_hotkey->dev = device; - if (ACPI_SUCCESS - (acpi_get_handle(device->handle, METHOD_NAME__INI, &handle))) { + if (acpi_has_method(device->handle, METHOD_NAME__INI)) { vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n"); if (ACPI_FAILURE (acpi_evaluate_object (device->handle, METHOD_NAME__INI, NULL, NULL))) - printk(KERN_ERR "_INI Method failed\n"); + pr_err("_INI Method failed\n"); } i = 0; @@ -897,8 +868,7 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0); /* Suspect this is a keymap of the application panel, print it */ - printk(KERN_INFO "fujitsu-laptop: BTNI: [0x%x]\n", - call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0)); + pr_info("BTNI: [0x%x]\n", call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0)); #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { @@ -907,8 +877,8 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) if (result == 0) { fujitsu_hotkey->logolamp_registered = 1; } else { - printk(KERN_ERR "fujitsu-laptop: Could not register " - "LED handler for logo lamp, error %i\n", result); + pr_err("Could not register LED handler for logo lamp, error %i\n", + result); } } @@ -919,8 +889,8 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) if (result == 0) { fujitsu_hotkey->kblamps_registered = 1; } else { - printk(KERN_ERR "fujitsu-laptop: Could not register " - "LED handler for keyboard lamps, error %i\n", result); + pr_err("Could not register LED handler for keyboard lamps, error %i\n", + result); } } #endif @@ -929,15 +899,16 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) err_unregister_input_dev: input_unregister_device(input); + input = NULL; err_free_input_dev: input_free_device(input); err_free_fifo: kfifo_free(&fujitsu_hotkey->fifo); err_stop: - return result; + return error; } -static int acpi_fujitsu_hotkey_remove(struct acpi_device *device, int type) +static int acpi_fujitsu_hotkey_remove(struct acpi_device *device) { struct fujitsu_hotkey_t *fujitsu_hotkey = acpi_driver_data(device); struct input_dev *input = fujitsu_hotkey->input; @@ -952,8 +923,6 @@ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device, int type) input_unregister_device(input); - input_free_device(input); - kfifo_free(&fujitsu_hotkey->fifo); fujitsu_hotkey->acpi_handle = NULL; @@ -1089,10 +1058,9 @@ static int __init fujitsu_init(void) if (acpi_disabled) return -ENODEV; - fujitsu = kmalloc(sizeof(struct fujitsu_t), GFP_KERNEL); + fujitsu = kzalloc(sizeof(struct fujitsu_t), GFP_KERNEL); if (!fujitsu) return -ENOMEM; - memset(fujitsu, 0, sizeof(struct fujitsu_t)); fujitsu->keycode1 = KEY_PROG1; fujitsu->keycode2 = KEY_PROG2; fujitsu->keycode3 = KEY_PROG3; @@ -1126,16 +1094,21 @@ static int __init fujitsu_init(void) /* Register backlight stuff */ if (!acpi_video_backlight_support()) { - fujitsu->bl_device = - backlight_device_register("fujitsu-laptop", NULL, NULL, - &fujitsubl_ops); + struct backlight_properties props; + + memset(&props, 0, sizeof(struct backlight_properties)); + max_brightness = fujitsu->max_brightness; + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = max_brightness - 1; + fujitsu->bl_device = backlight_device_register("fujitsu-laptop", + NULL, NULL, + &fujitsubl_ops, + &props); if (IS_ERR(fujitsu->bl_device)) { ret = PTR_ERR(fujitsu->bl_device); fujitsu->bl_device = NULL; goto fail_sysfs_group; } - max_brightness = fujitsu->max_brightness; - fujitsu->bl_device->props.max_brightness = max_brightness - 1; fujitsu->bl_device->props.brightness = fujitsu->brightness_level; } @@ -1145,12 +1118,11 @@ static int __init fujitsu_init(void) /* Register hotkey driver */ - fujitsu_hotkey = kmalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL); + fujitsu_hotkey = kzalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL); if (!fujitsu_hotkey) { ret = -ENOMEM; goto fail_hotkey; } - memset(fujitsu_hotkey, 0, sizeof(struct fujitsu_hotkey_t)); result = acpi_bus_register_driver(&acpi_fujitsu_hotkey_driver); if (result < 0) { @@ -1167,8 +1139,7 @@ static int __init fujitsu_init(void) fujitsu->bl_device->props.power = 0; } - printk(KERN_INFO "fujitsu-laptop: driver " FUJITSU_DRIVER_VERSION - " successfully loaded.\n"); + pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n"); return 0; @@ -1214,7 +1185,7 @@ static void __exit fujitsu_cleanup(void) kfree(fujitsu); - printk(KERN_INFO "fujitsu-laptop: driver unloaded.\n"); + pr_info("driver unloaded\n"); } module_init(fujitsu_init); @@ -1239,7 +1210,7 @@ MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*"); MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1E6:*:cvrS6420:*"); MODULE_ALIAS("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*"); -static struct pnp_device_id pnp_ids[] = { +static struct pnp_device_id pnp_ids[] __used = { {.id = "FUJ02bf"}, {.id = "FUJ02B1"}, {.id = "FUJ02E3"}, diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c new file mode 100644 index 00000000000..c3784baceae --- /dev/null +++ b/drivers/platform/x86/fujitsu-tablet.c @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2006-2012 Robert Gerlach <khnz@gmx.de> + * Copyright (C) 2005-2006 Jan Rychter <jan@rychter.com> + * + * You can redistribute and/or modify this program under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/dmi.h> + +#define MODULENAME "fujitsu-tablet" + +#define ACPI_FUJITSU_CLASS "fujitsu" + +#define INVERT_TABLET_MODE_BIT 0x01 +#define INVERT_DOCK_STATE_BIT 0x02 +#define FORCE_TABLET_MODE_IF_UNDOCK 0x04 + +#define KEYMAP_LEN 16 + +static const struct acpi_device_id fujitsu_ids[] = { + { .id = "FUJ02BD" }, + { .id = "FUJ02BF" }, + { .id = "" } +}; + +struct fujitsu_config { + unsigned short keymap[KEYMAP_LEN]; + unsigned int quirks; +}; + +static unsigned short keymap_Lifebook_Tseries[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_SCROLLDOWN, + KEY_SCROLLUP, + KEY_DIRECTION, + KEY_LEFTCTRL, + KEY_BRIGHTNESSUP, + KEY_BRIGHTNESSDOWN, + KEY_BRIGHTNESS_ZERO, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_LEFTALT +}; + +static unsigned short keymap_Lifebook_T901[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_SCROLLDOWN, + KEY_SCROLLUP, + KEY_CYCLEWINDOWS, + KEY_LEFTCTRL, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_LEFTMETA +}; + +static unsigned short keymap_Lifebook_T902[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_VOLUMEDOWN, + KEY_VOLUMEUP, + KEY_CYCLEWINDOWS, + KEY_PROG1, + KEY_PROG2, + KEY_LEFTMETA, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, +}; + +static unsigned short keymap_Lifebook_U810[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_PROG1, + KEY_PROG2, + KEY_DIRECTION, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_UP, + KEY_DOWN, + KEY_RESERVED, + KEY_RESERVED, + KEY_LEFTCTRL, + KEY_LEFTALT +}; + +static unsigned short keymap_Stylistic_Tseries[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_PRINT, + KEY_BACKSPACE, + KEY_SPACE, + KEY_ENTER, + KEY_BRIGHTNESSUP, + KEY_BRIGHTNESSDOWN, + KEY_DOWN, + KEY_UP, + KEY_SCROLLUP, + KEY_SCROLLDOWN, + KEY_LEFTCTRL, + KEY_LEFTALT +}; + +static unsigned short keymap_Stylistic_ST5xxx[KEYMAP_LEN] __initdata = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_MAIL, + KEY_DIRECTION, + KEY_ESC, + KEY_ENTER, + KEY_BRIGHTNESSUP, + KEY_BRIGHTNESSDOWN, + KEY_DOWN, + KEY_UP, + KEY_SCROLLUP, + KEY_SCROLLDOWN, + KEY_LEFTCTRL, + KEY_LEFTALT +}; + +static struct { + struct input_dev *idev; + struct fujitsu_config config; + unsigned long prev_keymask; + + char phys[21]; + + int irq; + int io_base; + int io_length; +} fujitsu; + +static u8 fujitsu_ack(void) +{ + return inb(fujitsu.io_base + 2); +} + +static u8 fujitsu_status(void) +{ + return inb(fujitsu.io_base + 6); +} + +static u8 fujitsu_read_register(const u8 addr) +{ + outb(addr, fujitsu.io_base); + return inb(fujitsu.io_base + 4); +} + +static void fujitsu_send_state(void) +{ + int state; + int dock, tablet_mode; + + state = fujitsu_read_register(0xdd); + + dock = state & 0x02; + if (fujitsu.config.quirks & INVERT_DOCK_STATE_BIT) + dock = !dock; + + if ((fujitsu.config.quirks & FORCE_TABLET_MODE_IF_UNDOCK) && (!dock)) { + tablet_mode = 1; + } else{ + tablet_mode = state & 0x01; + if (fujitsu.config.quirks & INVERT_TABLET_MODE_BIT) + tablet_mode = !tablet_mode; + } + + input_report_switch(fujitsu.idev, SW_DOCK, dock); + input_report_switch(fujitsu.idev, SW_TABLET_MODE, tablet_mode); + input_sync(fujitsu.idev); +} + +static void fujitsu_reset(void) +{ + int timeout = 50; + + fujitsu_ack(); + + while ((fujitsu_status() & 0x02) && (--timeout)) + msleep(20); + + fujitsu_send_state(); +} + +static int input_fujitsu_setup(struct device *parent, const char *name, + const char *phys) +{ + struct input_dev *idev; + int error; + int i; + + idev = input_allocate_device(); + if (!idev) + return -ENOMEM; + + idev->dev.parent = parent; + idev->phys = phys; + idev->name = name; + idev->id.bustype = BUS_HOST; + idev->id.vendor = 0x1734; /* Fujitsu Siemens Computer GmbH */ + idev->id.product = 0x0001; + idev->id.version = 0x0101; + + idev->keycode = fujitsu.config.keymap; + idev->keycodesize = sizeof(fujitsu.config.keymap[0]); + idev->keycodemax = ARRAY_SIZE(fujitsu.config.keymap); + + __set_bit(EV_REP, idev->evbit); + + for (i = 0; i < ARRAY_SIZE(fujitsu.config.keymap); i++) + if (fujitsu.config.keymap[i]) + input_set_capability(idev, EV_KEY, fujitsu.config.keymap[i]); + + input_set_capability(idev, EV_MSC, MSC_SCAN); + + input_set_capability(idev, EV_SW, SW_DOCK); + input_set_capability(idev, EV_SW, SW_TABLET_MODE); + + error = input_register_device(idev); + if (error) { + input_free_device(idev); + return error; + } + + fujitsu.idev = idev; + return 0; +} + +static void input_fujitsu_remove(void) +{ + input_unregister_device(fujitsu.idev); +} + +static irqreturn_t fujitsu_interrupt(int irq, void *dev_id) +{ + unsigned long keymask, changed; + unsigned int keycode; + int pressed; + int i; + + if (unlikely(!(fujitsu_status() & 0x01))) + return IRQ_NONE; + + fujitsu_send_state(); + + keymask = fujitsu_read_register(0xde); + keymask |= fujitsu_read_register(0xdf) << 8; + keymask ^= 0xffff; + + changed = keymask ^ fujitsu.prev_keymask; + if (changed) { + fujitsu.prev_keymask = keymask; + + for_each_set_bit(i, &changed, KEYMAP_LEN) { + keycode = fujitsu.config.keymap[i]; + pressed = keymask & changed & BIT(i); + + if (pressed) + input_event(fujitsu.idev, EV_MSC, MSC_SCAN, i); + + input_report_key(fujitsu.idev, keycode, pressed); + input_sync(fujitsu.idev); + } + } + + fujitsu_ack(); + return IRQ_HANDLED; +} + +static void fujitsu_dmi_common(const struct dmi_system_id *dmi) +{ + pr_info("%s\n", dmi->ident); + memcpy(fujitsu.config.keymap, dmi->driver_data, + sizeof(fujitsu.config.keymap)); +} + +static int fujitsu_dmi_lifebook(const struct dmi_system_id *dmi) +{ + fujitsu_dmi_common(dmi); + fujitsu.config.quirks |= INVERT_TABLET_MODE_BIT; + return 1; +} + +static int fujitsu_dmi_stylistic(const struct dmi_system_id *dmi) +{ + fujitsu_dmi_common(dmi); + fujitsu.config.quirks |= FORCE_TABLET_MODE_IF_UNDOCK; + fujitsu.config.quirks |= INVERT_DOCK_STATE_BIT; + return 1; +} + +static const struct dmi_system_id dmi_ids[] __initconst = { + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Lifebook T901", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T901") + }, + .driver_data = keymap_Lifebook_T901 + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Lifebook T901", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T901") + }, + .driver_data = keymap_Lifebook_T901 + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Lifebook T902", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T902") + }, + .driver_data = keymap_Lifebook_T902 + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Siemens P/T Series", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK") + }, + .driver_data = keymap_Lifebook_Tseries + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu Lifebook T Series", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T") + }, + .driver_data = keymap_Lifebook_Tseries + }, + { + .callback = fujitsu_dmi_stylistic, + .ident = "Fujitsu Siemens Stylistic T Series", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic T") + }, + .driver_data = keymap_Stylistic_Tseries + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Fujitsu LifeBook U810", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook U810") + }, + .driver_data = keymap_Lifebook_U810 + }, + { + .callback = fujitsu_dmi_stylistic, + .ident = "Fujitsu Siemens Stylistic ST5xxx Series", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "STYLISTIC ST5") + }, + .driver_data = keymap_Stylistic_ST5xxx + }, + { + .callback = fujitsu_dmi_stylistic, + .ident = "Fujitsu Siemens Stylistic ST5xxx Series", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic ST5") + }, + .driver_data = keymap_Stylistic_ST5xxx + }, + { + .callback = fujitsu_dmi_lifebook, + .ident = "Unknown (using defaults)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, ""), + DMI_MATCH(DMI_PRODUCT_NAME, "") + }, + .driver_data = keymap_Lifebook_Tseries + }, + { NULL } +}; + +static acpi_status fujitsu_walk_resources(struct acpi_resource *res, void *data) +{ + switch (res->type) { + case ACPI_RESOURCE_TYPE_IRQ: + fujitsu.irq = res->data.irq.interrupts[0]; + return AE_OK; + + case ACPI_RESOURCE_TYPE_IO: + fujitsu.io_base = res->data.io.minimum; + fujitsu.io_length = res->data.io.address_length; + return AE_OK; + + case ACPI_RESOURCE_TYPE_END_TAG: + if (fujitsu.irq && fujitsu.io_base) + return AE_OK; + else + return AE_NOT_FOUND; + + default: + return AE_ERROR; + } +} + +static int acpi_fujitsu_add(struct acpi_device *adev) +{ + acpi_status status; + int error; + + if (!adev) + return -EINVAL; + + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS, + fujitsu_walk_resources, NULL); + if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) + return -ENODEV; + + sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev)); + sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS); + + snprintf(fujitsu.phys, sizeof(fujitsu.phys), + "%s/input0", acpi_device_hid(adev)); + + error = input_fujitsu_setup(&adev->dev, + acpi_device_name(adev), fujitsu.phys); + if (error) + return error; + + if (!request_region(fujitsu.io_base, fujitsu.io_length, MODULENAME)) { + input_fujitsu_remove(); + return -EBUSY; + } + + fujitsu_reset(); + + error = request_irq(fujitsu.irq, fujitsu_interrupt, + IRQF_SHARED, MODULENAME, fujitsu_interrupt); + if (error) { + release_region(fujitsu.io_base, fujitsu.io_length); + input_fujitsu_remove(); + return error; + } + + return 0; +} + +static int acpi_fujitsu_remove(struct acpi_device *adev) +{ + free_irq(fujitsu.irq, fujitsu_interrupt); + release_region(fujitsu.io_base, fujitsu.io_length); + input_fujitsu_remove(); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int acpi_fujitsu_resume(struct device *dev) +{ + fujitsu_reset(); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(acpi_fujitsu_pm, NULL, acpi_fujitsu_resume); + +static struct acpi_driver acpi_fujitsu_driver = { + .name = MODULENAME, + .class = "hotkey", + .ids = fujitsu_ids, + .ops = { + .add = acpi_fujitsu_add, + .remove = acpi_fujitsu_remove, + }, + .drv.pm = &acpi_fujitsu_pm, +}; + +static int __init fujitsu_module_init(void) +{ + int error; + + dmi_check_system(dmi_ids); + + error = acpi_bus_register_driver(&acpi_fujitsu_driver); + if (error) + return error; + + return 0; +} + +static void __exit fujitsu_module_exit(void) +{ + acpi_bus_unregister_driver(&acpi_fujitsu_driver); +} + +module_init(fujitsu_module_init); +module_exit(fujitsu_module_exit); + +MODULE_AUTHOR("Robert Gerlach <khnz@gmx.de>"); +MODULE_DESCRIPTION("Fujitsu tablet pc extras driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("2.5"); + +MODULE_DEVICE_TABLE(acpi, fujitsu_ids); diff --git a/drivers/platform/x86/hdaps.c b/drivers/platform/x86/hdaps.c new file mode 100644 index 00000000000..777c7e3dda5 --- /dev/null +++ b/drivers/platform/x86/hdaps.c @@ -0,0 +1,642 @@ +/* + * hdaps.c - driver for IBM's Hard Drive Active Protection System + * + * Copyright (C) 2005 Robert Love <rml@novell.com> + * Copyright (C) 2005 Jesper Juhl <jj@chaosbits.net> + * + * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads + * starting with the R40, T41, and X40. It provides a basic two-axis + * accelerometer and other data, such as the device's temperature. + * + * This driver is based on the document by Mark A. Smith available at + * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial + * and error. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/input-polldev.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/timer.h> +#include <linux/dmi.h> +#include <linux/jiffies.h> +#include <linux/io.h> + +#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */ +#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */ + +#define HDAPS_PORT_STATE 0x1611 /* device state */ +#define HDAPS_PORT_YPOS 0x1612 /* y-axis position */ +#define HDAPS_PORT_XPOS 0x1614 /* x-axis position */ +#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in Celsius */ +#define HDAPS_PORT_YVAR 0x1617 /* y-axis variance (what is this?) */ +#define HDAPS_PORT_XVAR 0x1619 /* x-axis variance (what is this?) */ +#define HDAPS_PORT_TEMP2 0x161b /* device temperature (again?) */ +#define HDAPS_PORT_UNKNOWN 0x161c /* what is this? */ +#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */ + +#define STATE_FRESH 0x50 /* accelerometer data is fresh */ + +#define KEYBD_MASK 0x20 /* set if keyboard activity */ +#define MOUSE_MASK 0x40 /* set if mouse activity */ +#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK)) /* keyboard used? */ +#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK)) /* mouse used? */ + +#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */ +#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */ + +#define HDAPS_POLL_INTERVAL 50 /* poll for input every 1/20s (50 ms)*/ +#define HDAPS_INPUT_FUZZ 4 /* input event threshold */ +#define HDAPS_INPUT_FLAT 4 + +#define HDAPS_X_AXIS (1 << 0) +#define HDAPS_Y_AXIS (1 << 1) +#define HDAPS_BOTH_AXES (HDAPS_X_AXIS | HDAPS_Y_AXIS) + +static struct platform_device *pdev; +static struct input_polled_dev *hdaps_idev; +static unsigned int hdaps_invert; +static u8 km_activity; +static int rest_x; +static int rest_y; + +static DEFINE_MUTEX(hdaps_mtx); + +/* + * __get_latch - Get the value from a given port. Callers must hold hdaps_mtx. + */ +static inline u8 __get_latch(u16 port) +{ + return inb(port) & 0xff; +} + +/* + * __check_latch - Check a port latch for a given value. Returns zero if the + * port contains the given value. Callers must hold hdaps_mtx. + */ +static inline int __check_latch(u16 port, u8 val) +{ + if (__get_latch(port) == val) + return 0; + return -EINVAL; +} + +/* + * __wait_latch - Wait up to 100us for a port latch to get a certain value, + * returning zero if the value is obtained. Callers must hold hdaps_mtx. + */ +static int __wait_latch(u16 port, u8 val) +{ + unsigned int i; + + for (i = 0; i < 20; i++) { + if (!__check_latch(port, val)) + return 0; + udelay(5); + } + + return -EIO; +} + +/* + * __device_refresh - request a refresh from the accelerometer. Does not wait + * for refresh to complete. Callers must hold hdaps_mtx. + */ +static void __device_refresh(void) +{ + udelay(200); + if (inb(0x1604) != STATE_FRESH) { + outb(0x11, 0x1610); + outb(0x01, 0x161f); + } +} + +/* + * __device_refresh_sync - request a synchronous refresh from the + * accelerometer. We wait for the refresh to complete. Returns zero if + * successful and nonzero on error. Callers must hold hdaps_mtx. + */ +static int __device_refresh_sync(void) +{ + __device_refresh(); + return __wait_latch(0x1604, STATE_FRESH); +} + +/* + * __device_complete - indicate to the accelerometer that we are done reading + * data, and then initiate an async refresh. Callers must hold hdaps_mtx. + */ +static inline void __device_complete(void) +{ + inb(0x161f); + inb(0x1604); + __device_refresh(); +} + +/* + * hdaps_readb_one - reads a byte from a single I/O port, placing the value in + * the given pointer. Returns zero on success or a negative error on failure. + * Can sleep. + */ +static int hdaps_readb_one(unsigned int port, u8 *val) +{ + int ret; + + mutex_lock(&hdaps_mtx); + + /* do a sync refresh -- we need to be sure that we read fresh data */ + ret = __device_refresh_sync(); + if (ret) + goto out; + + *val = inb(port); + __device_complete(); + +out: + mutex_unlock(&hdaps_mtx); + return ret; +} + +/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */ +static int __hdaps_read_pair(unsigned int port1, unsigned int port2, + int *x, int *y) +{ + /* do a sync refresh -- we need to be sure that we read fresh data */ + if (__device_refresh_sync()) + return -EIO; + + *y = inw(port2); + *x = inw(port1); + km_activity = inb(HDAPS_PORT_KMACT); + __device_complete(); + + /* hdaps_invert is a bitvector to negate the axes */ + if (hdaps_invert & HDAPS_X_AXIS) + *x = -*x; + if (hdaps_invert & HDAPS_Y_AXIS) + *y = -*y; + + return 0; +} + +/* + * hdaps_read_pair - reads the values from a pair of ports, placing the values + * in the given pointers. Returns zero on success. Can sleep. + */ +static int hdaps_read_pair(unsigned int port1, unsigned int port2, + int *val1, int *val2) +{ + int ret; + + mutex_lock(&hdaps_mtx); + ret = __hdaps_read_pair(port1, port2, val1, val2); + mutex_unlock(&hdaps_mtx); + + return ret; +} + +/* + * hdaps_device_init - initialize the accelerometer. Returns zero on success + * and negative error code on failure. Can sleep. + */ +static int hdaps_device_init(void) +{ + int total, ret = -ENXIO; + + mutex_lock(&hdaps_mtx); + + outb(0x13, 0x1610); + outb(0x01, 0x161f); + if (__wait_latch(0x161f, 0x00)) + goto out; + + /* + * Most ThinkPads return 0x01. + * + * Others--namely the R50p, T41p, and T42p--return 0x03. These laptops + * have "inverted" axises. + * + * The 0x02 value occurs when the chip has been previously initialized. + */ + if (__check_latch(0x1611, 0x03) && + __check_latch(0x1611, 0x02) && + __check_latch(0x1611, 0x01)) + goto out; + + printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n", + __get_latch(0x1611)); + + outb(0x17, 0x1610); + outb(0x81, 0x1611); + outb(0x01, 0x161f); + if (__wait_latch(0x161f, 0x00)) + goto out; + if (__wait_latch(0x1611, 0x00)) + goto out; + if (__wait_latch(0x1612, 0x60)) + goto out; + if (__wait_latch(0x1613, 0x00)) + goto out; + outb(0x14, 0x1610); + outb(0x01, 0x1611); + outb(0x01, 0x161f); + if (__wait_latch(0x161f, 0x00)) + goto out; + outb(0x10, 0x1610); + outb(0xc8, 0x1611); + outb(0x00, 0x1612); + outb(0x02, 0x1613); + outb(0x01, 0x161f); + if (__wait_latch(0x161f, 0x00)) + goto out; + if (__device_refresh_sync()) + goto out; + if (__wait_latch(0x1611, 0x00)) + goto out; + + /* we have done our dance, now let's wait for the applause */ + for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) { + int x, y; + + /* a read of the device helps push it into action */ + __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y); + if (!__wait_latch(0x1611, 0x02)) { + ret = 0; + break; + } + + msleep(INIT_WAIT_MSECS); + } + +out: + mutex_unlock(&hdaps_mtx); + return ret; +} + + +/* Device model stuff */ + +static int hdaps_probe(struct platform_device *dev) +{ + int ret; + + ret = hdaps_device_init(); + if (ret) + return ret; + + pr_info("device successfully initialized\n"); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int hdaps_resume(struct device *dev) +{ + return hdaps_device_init(); +} +#endif + +static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume); + +static struct platform_driver hdaps_driver = { + .probe = hdaps_probe, + .driver = { + .name = "hdaps", + .owner = THIS_MODULE, + .pm = &hdaps_pm, + }, +}; + +/* + * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_mtx. + */ +static void hdaps_calibrate(void) +{ + __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y); +} + +static void hdaps_mousedev_poll(struct input_polled_dev *dev) +{ + struct input_dev *input_dev = dev->input; + int x, y; + + mutex_lock(&hdaps_mtx); + + if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y)) + goto out; + + input_report_abs(input_dev, ABS_X, x - rest_x); + input_report_abs(input_dev, ABS_Y, y - rest_y); + input_sync(input_dev); + +out: + mutex_unlock(&hdaps_mtx); +} + + +/* Sysfs Files */ + +static ssize_t hdaps_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret, x, y; + + ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y); + if (ret) + return ret; + + return sprintf(buf, "(%d,%d)\n", x, y); +} + +static ssize_t hdaps_variance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret, x, y; + + ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y); + if (ret) + return ret; + + return sprintf(buf, "(%d,%d)\n", x, y); +} + +static ssize_t hdaps_temp1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 uninitialized_var(temp); + int ret; + + ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp); + if (ret) + return ret; + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t hdaps_temp2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 uninitialized_var(temp); + int ret; + + ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp); + if (ret) + return ret; + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t hdaps_keyboard_activity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity)); +} + +static ssize_t hdaps_mouse_activity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity)); +} + +static ssize_t hdaps_calibrate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "(%d,%d)\n", rest_x, rest_y); +} + +static ssize_t hdaps_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + mutex_lock(&hdaps_mtx); + hdaps_calibrate(); + mutex_unlock(&hdaps_mtx); + + return count; +} + +static ssize_t hdaps_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", hdaps_invert); +} + +static ssize_t hdaps_invert_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int invert; + + if (sscanf(buf, "%d", &invert) != 1 || + invert < 0 || invert > HDAPS_BOTH_AXES) + return -EINVAL; + + hdaps_invert = invert; + hdaps_calibrate(); + + return count; +} + +static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL); +static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL); +static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL); +static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL); +static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL); +static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL); +static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store); +static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store); + +static struct attribute *hdaps_attributes[] = { + &dev_attr_position.attr, + &dev_attr_variance.attr, + &dev_attr_temp1.attr, + &dev_attr_temp2.attr, + &dev_attr_keyboard_activity.attr, + &dev_attr_mouse_activity.attr, + &dev_attr_calibrate.attr, + &dev_attr_invert.attr, + NULL, +}; + +static struct attribute_group hdaps_attribute_group = { + .attrs = hdaps_attributes, +}; + + +/* Module stuff */ + +/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */ +static int __init hdaps_dmi_match(const struct dmi_system_id *id) +{ + pr_info("%s detected\n", id->ident); + return 1; +} + +/* hdaps_dmi_match_invert - found an inverted match. */ +static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id) +{ + hdaps_invert = (unsigned long)id->driver_data; + pr_info("inverting axis (%u) readings\n", hdaps_invert); + return hdaps_dmi_match(id); +} + +#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) { \ + .ident = vendor " " model, \ + .callback = hdaps_dmi_match_invert, \ + .driver_data = (void *)axes, \ + .matches = { \ + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ + DMI_MATCH(DMI_PRODUCT_VERSION, model) \ + } \ +} + +#define HDAPS_DMI_MATCH_NORMAL(vendor, model) \ + HDAPS_DMI_MATCH_INVERT(vendor, model, 0) + +/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match + "ThinkPad T42p", so the order of the entries matters. + If your ThinkPad is not recognized, please update to latest + BIOS. This is especially the case for some R52 ThinkPads. */ +static struct dmi_system_id __initdata hdaps_whitelist[] = { + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"), + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"), + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES), + { .ident = NULL } +}; + +static int __init hdaps_init(void) +{ + struct input_dev *idev; + int ret; + + if (!dmi_check_system(hdaps_whitelist)) { + pr_warn("supported laptop not found!\n"); + ret = -ENODEV; + goto out; + } + + if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) { + ret = -ENXIO; + goto out; + } + + ret = platform_driver_register(&hdaps_driver); + if (ret) + goto out_region; + + pdev = platform_device_register_simple("hdaps", -1, NULL, 0); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_driver; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group); + if (ret) + goto out_device; + + hdaps_idev = input_allocate_polled_device(); + if (!hdaps_idev) { + ret = -ENOMEM; + goto out_group; + } + + hdaps_idev->poll = hdaps_mousedev_poll; + hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL; + + /* initial calibrate for the input device */ + hdaps_calibrate(); + + /* initialize the input class */ + idev = hdaps_idev->input; + idev->name = "hdaps"; + idev->phys = "isa1600/input0"; + idev->id.bustype = BUS_ISA; + idev->dev.parent = &pdev->dev; + idev->evbit[0] = BIT_MASK(EV_ABS); + input_set_abs_params(idev, ABS_X, + -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); + input_set_abs_params(idev, ABS_Y, + -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); + + ret = input_register_polled_device(hdaps_idev); + if (ret) + goto out_idev; + + pr_info("driver successfully loaded\n"); + return 0; + +out_idev: + input_free_polled_device(hdaps_idev); +out_group: + sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); +out_device: + platform_device_unregister(pdev); +out_driver: + platform_driver_unregister(&hdaps_driver); +out_region: + release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS); +out: + pr_warn("driver init failed (ret=%d)!\n", ret); + return ret; +} + +static void __exit hdaps_exit(void) +{ + input_unregister_polled_device(hdaps_idev); + input_free_polled_device(hdaps_idev); + sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); + platform_device_unregister(pdev); + platform_driver_unregister(&hdaps_driver); + release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS); + + pr_info("driver unloaded\n"); +} + +module_init(hdaps_init); +module_exit(hdaps_exit); + +module_param_named(invert, hdaps_invert, int, 0); +MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, " + "2 invert y-axis, 3 invert both axes."); + +MODULE_AUTHOR("Robert Love"); +MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/hp-wireless.c b/drivers/platform/x86/hp-wireless.c new file mode 100644 index 00000000000..415348fc121 --- /dev/null +++ b/drivers/platform/x86/hp-wireless.c @@ -0,0 +1,132 @@ +/* + * hp-wireless button for Windows 8 + * + * Copyright (C) 2014 Alex Hung <alex.hung@canonical.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> +#include <acpi/acpi_bus.h> + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alex Hung"); +MODULE_ALIAS("acpi*:HPQ6001:*"); + +static struct input_dev *hpwl_input_dev; + +static const struct acpi_device_id hpwl_ids[] = { + {"HPQ6001", 0}, + {"", 0}, +}; + +static int hp_wireless_input_setup(void) +{ + int err; + + hpwl_input_dev = input_allocate_device(); + if (!hpwl_input_dev) + return -ENOMEM; + + hpwl_input_dev->name = "HP Wireless hotkeys"; + hpwl_input_dev->phys = "hpq6001/input0"; + hpwl_input_dev->id.bustype = BUS_HOST; + hpwl_input_dev->evbit[0] = BIT(EV_KEY); + set_bit(KEY_RFKILL, hpwl_input_dev->keybit); + + err = input_register_device(hpwl_input_dev); + if (err) + goto err_free_dev; + + return 0; + +err_free_dev: + input_free_device(hpwl_input_dev); + return err; +} + +static void hp_wireless_input_destroy(void) +{ + input_unregister_device(hpwl_input_dev); +} + +static void hpwl_notify(struct acpi_device *acpi_dev, u32 event) +{ + if (event != 0x80) { + pr_info("Received unknown event (0x%x)\n", event); + return; + } + + input_report_key(hpwl_input_dev, KEY_RFKILL, 1); + input_sync(hpwl_input_dev); + input_report_key(hpwl_input_dev, KEY_RFKILL, 0); + input_sync(hpwl_input_dev); +} + +static int hpwl_add(struct acpi_device *device) +{ + int err; + + err = hp_wireless_input_setup(); + return err; +} + +static int hpwl_remove(struct acpi_device *device) +{ + hp_wireless_input_destroy(); + return 0; +} + +static struct acpi_driver hpwl_driver = { + .name = "hp-wireless", + .owner = THIS_MODULE, + .ids = hpwl_ids, + .ops = { + .add = hpwl_add, + .remove = hpwl_remove, + .notify = hpwl_notify, + }, +}; + +static int __init hpwl_init(void) +{ + int err; + + pr_info("Initializing HPQ6001 module\n"); + err = acpi_bus_register_driver(&hpwl_driver); + if (err) { + pr_err("Unable to register HP wireless control driver.\n"); + goto error_acpi_register; + } + + return 0; + +error_acpi_register: + return err; +} + +static void __exit hpwl_exit(void) +{ + pr_info("Exiting HPQ6001 module\n"); + acpi_bus_unregister_driver(&hpwl_driver); +} + +module_init(hpwl_init); +module_exit(hpwl_exit); diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index ad4c414dbfb..484a8673b83 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -2,6 +2,7 @@ * HP WMI hotkeys * * Copyright (C) 2008 Red Hat <mjg@redhat.com> + * Copyright (C) 2010, 2011 Anssi Hannula <anssi.hannula@iki.fi> * * Portions based on wistron_btns.c: * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> @@ -23,12 +24,15 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> +#include <linux/slab.h> #include <linux/types.h> #include <linux/input.h> -#include <acpi/acpi_drivers.h> +#include <linux/input/sparse-keymap.h> #include <linux/platform_device.h> #include <linux/acpi.h> #include <linux/rfkill.h> @@ -49,17 +53,36 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); #define HPWMI_ALS_QUERY 0x3 #define HPWMI_HARDWARE_QUERY 0x4 #define HPWMI_WIRELESS_QUERY 0x5 +#define HPWMI_BIOS_QUERY 0x9 #define HPWMI_HOTKEY_QUERY 0xc +#define HPWMI_FEATURE_QUERY 0xd +#define HPWMI_WIRELESS2_QUERY 0x1b +#define HPWMI_POSTCODEERROR_QUERY 0x2a enum hp_wmi_radio { HPWMI_WIFI = 0, HPWMI_BLUETOOTH = 1, HPWMI_WWAN = 2, + HPWMI_GPS = 3, }; -static int __init hp_wmi_bios_setup(struct platform_device *device); -static int __exit hp_wmi_bios_remove(struct platform_device *device); -static int hp_wmi_resume_handler(struct device *device); +enum hp_wmi_event_ids { + HPWMI_DOCK_EVENT = 1, + HPWMI_PARK_HDD = 2, + HPWMI_SMART_ADAPTER = 3, + HPWMI_BEZEL_BUTTON = 4, + HPWMI_WIRELESS = 5, + HPWMI_CPU_BATTERY_THROTTLE = 6, + HPWMI_LOCK_SWITCH = 7, + HPWMI_LID_SWITCH = 8, + HPWMI_SCREEN_ROTATION = 9, + HPWMI_COOLSENSE_SYSTEM_MOBILE = 0x0A, + HPWMI_COOLSENSE_SYSTEM_HOT = 0x0B, + HPWMI_PROXIMITY_SENSOR = 0x0C, + HPWMI_BACKLIT_KB_BRIGHTNESS = 0x0D, + HPWMI_PEAKSHIFT_PERIOD = 0x0F, + HPWMI_BATTERY_CHARGE_PERIOD = 0x10, +}; struct bios_args { u32 signature; @@ -72,25 +95,59 @@ struct bios_args { struct bios_return { u32 sigpass; u32 return_code; - u32 value; }; -struct key_entry { - char type; /* See KE_* below */ - u16 code; - u16 keycode; +enum hp_return_value { + HPWMI_RET_WRONG_SIGNATURE = 0x02, + HPWMI_RET_UNKNOWN_COMMAND = 0x03, + HPWMI_RET_UNKNOWN_CMDTYPE = 0x04, + HPWMI_RET_INVALID_PARAMETERS = 0x05, }; -enum { KE_KEY, KE_END }; +enum hp_wireless2_bits { + HPWMI_POWER_STATE = 0x01, + HPWMI_POWER_SOFT = 0x02, + HPWMI_POWER_BIOS = 0x04, + HPWMI_POWER_HARD = 0x08, +}; -static struct key_entry hp_wmi_keymap[] = { - {KE_KEY, 0x02, KEY_BRIGHTNESSUP}, - {KE_KEY, 0x03, KEY_BRIGHTNESSDOWN}, - {KE_KEY, 0x20e6, KEY_PROG1}, - {KE_KEY, 0x2142, KEY_MEDIA}, - {KE_KEY, 0x213b, KEY_INFO}, - {KE_KEY, 0x231b, KEY_HELP}, - {KE_END, 0} +#define IS_HWBLOCKED(x) ((x & (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) \ + != (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) +#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) + +struct bios_rfkill2_device_state { + u8 radio_type; + u8 bus_type; + u16 vendor_id; + u16 product_id; + u16 subsys_vendor_id; + u16 subsys_product_id; + u8 rfkill_id; + u8 power; + u8 unknown[4]; +}; + +/* 7 devices fit into the 128 byte buffer */ +#define HPWMI_MAX_RFKILL2_DEVICES 7 + +struct bios_rfkill2_state { + u8 unknown[7]; + u8 count; + u8 pad[8]; + struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES]; +}; + +static const struct key_entry hp_wmi_keymap[] = { + { KE_KEY, 0x02, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x03, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x20e6, { KEY_PROG1 } }, + { KE_KEY, 0x20e8, { KEY_MEDIA } }, + { KE_KEY, 0x2142, { KEY_MEDIA } }, + { KE_KEY, 0x213b, { KEY_INFO } }, + { KE_KEY, 0x2169, { KEY_DIRECTION } }, + { KE_KEY, 0x216a, { KEY_SETUP } }, + { KE_KEY, 0x231b, { KEY_HELP } }, + { KE_END, 0 } }; static struct input_dev *hp_wmi_input_dev; @@ -99,38 +156,59 @@ static struct platform_device *hp_wmi_platform_dev; static struct rfkill *wifi_rfkill; static struct rfkill *bluetooth_rfkill; static struct rfkill *wwan_rfkill; +static struct rfkill *gps_rfkill; -static const struct dev_pm_ops hp_wmi_pm_ops = { - .resume = hp_wmi_resume_handler, - .restore = hp_wmi_resume_handler, +struct rfkill2_device { + u8 id; + int num; + struct rfkill *rfkill; }; -static struct platform_driver hp_wmi_driver = { - .driver = { - .name = "hp-wmi", - .owner = THIS_MODULE, - .pm = &hp_wmi_pm_ops, - }, - .probe = hp_wmi_bios_setup, - .remove = hp_wmi_bios_remove, -}; +static int rfkill2_count; +static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; -static int hp_wmi_perform_query(int query, int write, int value) +/* + * hp_wmi_perform_query + * + * query: The commandtype -> What should be queried + * write: The command -> 0 read, 1 write, 3 ODM specific + * buffer: Buffer used as input and/or output + * insize: Size of input buffer + * outsize: Size of output buffer + * + * returns zero on success + * an HP WMI query specific error code (which is positive) + * -EINVAL if the query was not successful at all + * -EINVAL if the output buffer size exceeds buffersize + * + * Note: The buffersize must at least be the maximum of the input and output + * size. E.g. Battery info query (0x7) is defined to have 1 byte input + * and 128 byte output. The caller would do: + * buffer = kzalloc(128, GFP_KERNEL); + * ret = hp_wmi_perform_query(0x7, 0, buffer, 1, 128) + */ +static int hp_wmi_perform_query(int query, int write, void *buffer, + int insize, int outsize) { - struct bios_return bios_return; - acpi_status status; + struct bios_return *bios_return; + int actual_outsize; union acpi_object *obj; struct bios_args args = { .signature = 0x55434553, .command = write ? 0x2 : 0x1, .commandtype = query, - .datasize = write ? 0x4 : 0, - .data = value, + .datasize = insize, + .data = 0, }; struct acpi_buffer input = { sizeof(struct bios_args), &args }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 rc; + + if (WARN_ON(insize > sizeof(args.data))) + return -EINVAL; + memcpy(&args.data, buffer, insize); - status = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output); + wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output); obj = output.pointer; @@ -141,55 +219,117 @@ static int hp_wmi_perform_query(int query, int write, int value) return -EINVAL; } - bios_return = *((struct bios_return *)obj->buffer.pointer); + bios_return = (struct bios_return *)obj->buffer.pointer; + rc = bios_return->return_code; + + if (rc) { + if (rc != HPWMI_RET_UNKNOWN_CMDTYPE) + pr_warn("query 0x%x returned error 0x%x\n", query, rc); + kfree(obj); + return rc; + } + + if (!outsize) { + /* ignore output data */ + kfree(obj); + return 0; + } + + actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return))); + memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize); + memset(buffer + actual_outsize, 0, outsize - actual_outsize); kfree(obj); - if (bios_return.return_code > 0) - return bios_return.return_code * -1; - else - return bios_return.value; + return 0; } static int hp_wmi_display_state(void) { - return hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, 0); + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state, + sizeof(state), sizeof(state)); + if (ret) + return -EINVAL; + return state; } static int hp_wmi_hddtemp_state(void) { - return hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, 0); + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, &state, + sizeof(state), sizeof(state)); + if (ret) + return -EINVAL; + return state; } static int hp_wmi_als_state(void) { - return hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, 0); + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, &state, + sizeof(state), sizeof(state)); + if (ret) + return -EINVAL; + return state; } static int hp_wmi_dock_state(void) { - int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, + sizeof(state), sizeof(state)); - if (ret < 0) - return ret; + if (ret) + return -EINVAL; - return ret & 0x1; + return state & 0x1; } static int hp_wmi_tablet_state(void) { - int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, + sizeof(state), sizeof(state)); + if (ret) + return ret; - if (ret < 0) + return (state & 0x4) ? 1 : 0; +} + +static int hp_wmi_bios_2009_later(void) +{ + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, 0, &state, + sizeof(state), sizeof(state)); + if (ret) return ret; - return (ret & 0x4) ? 1 : 0; + return (state & 0x10) ? 1 : 0; +} + +static int hp_wmi_enable_hotkeys(void) +{ + int ret; + int query = 0x6e; + + ret = hp_wmi_perform_query(HPWMI_BIOS_QUERY, 1, &query, sizeof(query), + 0); + + if (ret) + return -EINVAL; + return 0; } static int hp_wmi_set_block(void *data, bool blocked) { enum hp_wmi_radio r = (enum hp_wmi_radio) data; int query = BIT(r + 8) | ((!blocked) << r); + int ret; - return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, query); + ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, + &query, sizeof(query), 0); + if (ret) + return -EINVAL; + return 0; } static const struct rfkill_ops hp_wmi_rfkill_ops = { @@ -198,8 +338,14 @@ static const struct rfkill_ops hp_wmi_rfkill_ops = { static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) { - int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); - int mask = 0x200 << (r * 8); + int wireless = 0; + int mask; + hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, + &wireless, sizeof(wireless), + sizeof(wireless)); + /* TBD: Pass error */ + + mask = 0x200 << (r * 8); if (wireless & mask) return false; @@ -209,8 +355,14 @@ static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) { - int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); - int mask = 0x800 << (r * 8); + int wireless = 0; + int mask; + hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, + &wireless, sizeof(wireless), + sizeof(wireless)); + /* TBD: Pass error */ + + mask = 0x800 << (r * 8); if (wireless & mask) return false; @@ -218,6 +370,60 @@ static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) return true; } +static int hp_wmi_rfkill2_set_block(void *data, bool blocked) +{ + int rfkill_id = (int)(long)data; + char buffer[4] = { 0x01, 0x00, rfkill_id, !blocked }; + + if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1, + buffer, sizeof(buffer), 0)) + return -EINVAL; + return 0; +} + +static const struct rfkill_ops hp_wmi_rfkill2_ops = { + .set_block = hp_wmi_rfkill2_set_block, +}; + +static int hp_wmi_rfkill2_refresh(void) +{ + int err, i; + struct bios_rfkill2_state state; + + err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state, + 0, sizeof(state)); + if (err) + return err; + + for (i = 0; i < rfkill2_count; i++) { + int num = rfkill2[i].num; + struct bios_rfkill2_device_state *devstate; + devstate = &state.device[num]; + + if (num >= state.count || + devstate->rfkill_id != rfkill2[i].id) { + pr_warn("power configuration of the wireless devices unexpectedly changed\n"); + continue; + } + + rfkill_set_states(rfkill2[i].rfkill, + IS_SWBLOCKED(devstate->power), + IS_HWBLOCKED(devstate->power)); + } + + return 0; +} + +static int hp_wmi_post_code_state(void) +{ + int state = 0; + int ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 0, &state, + sizeof(state), sizeof(state)); + if (ret) + return -EINVAL; + return state; +} + static ssize_t show_display(struct device *dev, struct device_attribute *attr, char *buf) { @@ -263,121 +469,129 @@ static ssize_t show_tablet(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%d\n", value); } -static ssize_t set_als(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - u32 tmp = simple_strtoul(buf, NULL, 10); - hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, tmp); - return count; -} - -static DEVICE_ATTR(display, S_IRUGO, show_display, NULL); -static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL); -static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als); -static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL); -static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL); - -static struct key_entry *hp_wmi_get_entry_by_scancode(int code) -{ - struct key_entry *key; - - for (key = hp_wmi_keymap; key->type != KE_END; key++) - if (code == key->code) - return key; - - return NULL; -} - -static struct key_entry *hp_wmi_get_entry_by_keycode(int keycode) +static ssize_t show_postcode(struct device *dev, struct device_attribute *attr, + char *buf) { - struct key_entry *key; - - for (key = hp_wmi_keymap; key->type != KE_END; key++) - if (key->type == KE_KEY && keycode == key->keycode) - return key; - - return NULL; + /* Get the POST error code of previous boot failure. */ + int value = hp_wmi_post_code_state(); + if (value < 0) + return -EINVAL; + return sprintf(buf, "0x%x\n", value); } -static int hp_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode) +static ssize_t set_als(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { - struct key_entry *key = hp_wmi_get_entry_by_scancode(scancode); - - if (key && key->type == KE_KEY) { - *keycode = key->keycode; - return 0; - } + u32 tmp = simple_strtoul(buf, NULL, 10); + int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, &tmp, + sizeof(tmp), sizeof(tmp)); + if (ret) + return -EINVAL; - return -EINVAL; + return count; } -static int hp_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode) +static ssize_t set_postcode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { - struct key_entry *key; - int old_keycode; + int ret; + u32 tmp; + long unsigned int tmp2; - if (keycode < 0 || keycode > KEY_MAX) + ret = kstrtoul(buf, 10, &tmp2); + if (ret || tmp2 != 1) return -EINVAL; - key = hp_wmi_get_entry_by_scancode(scancode); - if (key && key->type == KE_KEY) { - old_keycode = key->keycode; - key->keycode = keycode; - set_bit(keycode, dev->keybit); - if (!hp_wmi_get_entry_by_keycode(old_keycode)) - clear_bit(old_keycode, dev->keybit); - return 0; - } + /* Clear the POST error code. It is kept until until cleared. */ + tmp = (u32) tmp2; + ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 1, &tmp, + sizeof(tmp), sizeof(tmp)); + if (ret) + return -EINVAL; - return -EINVAL; + return count; } +static DEVICE_ATTR(display, S_IRUGO, show_display, NULL); +static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL); +static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als); +static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL); +static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL); +static DEVICE_ATTR(postcode, S_IRUGO | S_IWUSR, show_postcode, set_postcode); + static void hp_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; - static struct key_entry *key; union acpi_object *obj; - int eventcode; + u32 event_id, event_data; + int key_code = 0, ret; + u32 *location; acpi_status status; status = wmi_get_event_data(value, &response); if (status != AE_OK) { - printk(KERN_INFO "hp-wmi: bad event status 0x%x\n", status); + pr_info("bad event status 0x%x\n", status); return; } obj = (union acpi_object *)response.pointer; - if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length != 8) { - printk(KERN_INFO "HP WMI: Unknown response received\n"); + if (!obj) + return; + if (obj->type != ACPI_TYPE_BUFFER) { + pr_info("Unknown response received %d\n", obj->type); kfree(obj); return; } - eventcode = *((u8 *) obj->buffer.pointer); + /* + * Depending on ACPI version the concatenation of id and event data + * inside _WED function will result in a 8 or 16 byte buffer. + */ + location = (u32 *)obj->buffer.pointer; + if (obj->buffer.length == 8) { + event_id = *location; + event_data = *(location + 1); + } else if (obj->buffer.length == 16) { + event_id = *location; + event_data = *(location + 2); + } else { + pr_info("Unknown buffer length %d\n", obj->buffer.length); + kfree(obj); + return; + } kfree(obj); - if (eventcode == 0x4) - eventcode = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, - 0); - key = hp_wmi_get_entry_by_scancode(eventcode); - if (key) { - switch (key->type) { - case KE_KEY: - input_report_key(hp_wmi_input_dev, - key->keycode, 1); - input_sync(hp_wmi_input_dev); - input_report_key(hp_wmi_input_dev, - key->keycode, 0); - input_sync(hp_wmi_input_dev); - break; - } - } else if (eventcode == 0x1) { + + switch (event_id) { + case HPWMI_DOCK_EVENT: input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state()); input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, hp_wmi_tablet_state()); input_sync(hp_wmi_input_dev); - } else if (eventcode == 0x5) { + break; + case HPWMI_PARK_HDD: + break; + case HPWMI_SMART_ADAPTER: + break; + case HPWMI_BEZEL_BUTTON: + ret = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, + &key_code, + sizeof(key_code), + sizeof(key_code)); + if (ret) + break; + + if (!sparse_keymap_report_event(hp_wmi_input_dev, + key_code, 1, true)) + pr_info("Unknown key code - 0x%x\n", key_code); + break; + case HPWMI_WIRELESS: + if (rfkill2_count) { + hp_wmi_rfkill2_refresh(); + break; + } + if (wifi_rfkill) rfkill_set_states(wifi_rfkill, hp_wmi_get_sw_state(HPWMI_WIFI), @@ -390,36 +604,58 @@ static void hp_wmi_notify(u32 value, void *context) rfkill_set_states(wwan_rfkill, hp_wmi_get_sw_state(HPWMI_WWAN), hp_wmi_get_hw_state(HPWMI_WWAN)); - } else - printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n", - eventcode); + if (gps_rfkill) + rfkill_set_states(gps_rfkill, + hp_wmi_get_sw_state(HPWMI_GPS), + hp_wmi_get_hw_state(HPWMI_GPS)); + break; + case HPWMI_CPU_BATTERY_THROTTLE: + pr_info("Unimplemented CPU throttle because of 3 Cell battery event detected\n"); + break; + case HPWMI_LOCK_SWITCH: + break; + case HPWMI_LID_SWITCH: + break; + case HPWMI_SCREEN_ROTATION: + break; + case HPWMI_COOLSENSE_SYSTEM_MOBILE: + break; + case HPWMI_COOLSENSE_SYSTEM_HOT: + break; + case HPWMI_PROXIMITY_SENSOR: + break; + case HPWMI_BACKLIT_KB_BRIGHTNESS: + break; + case HPWMI_PEAKSHIFT_PERIOD: + break; + case HPWMI_BATTERY_CHARGE_PERIOD: + break; + default: + pr_info("Unknown event_id - %d - 0x%x\n", event_id, event_data); + break; + } } static int __init hp_wmi_input_setup(void) { - struct key_entry *key; + acpi_status status; int err; hp_wmi_input_dev = input_allocate_device(); + if (!hp_wmi_input_dev) + return -ENOMEM; hp_wmi_input_dev->name = "HP WMI hotkeys"; hp_wmi_input_dev->phys = "wmi/input0"; hp_wmi_input_dev->id.bustype = BUS_HOST; - hp_wmi_input_dev->getkeycode = hp_wmi_getkeycode; - hp_wmi_input_dev->setkeycode = hp_wmi_setkeycode; - - for (key = hp_wmi_keymap; key->type != KE_END; key++) { - switch (key->type) { - case KE_KEY: - set_bit(EV_KEY, hp_wmi_input_dev->evbit); - set_bit(key->keycode, hp_wmi_input_dev->keybit); - break; - } - } - set_bit(EV_SW, hp_wmi_input_dev->evbit); - set_bit(SW_DOCK, hp_wmi_input_dev->swbit); - set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + __set_bit(EV_SW, hp_wmi_input_dev->evbit); + __set_bit(SW_DOCK, hp_wmi_input_dev->swbit); + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + + err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL); + if (err) + goto err_free_dev; /* Set initial hardware state */ input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state()); @@ -427,14 +663,35 @@ static int __init hp_wmi_input_setup(void) hp_wmi_tablet_state()); input_sync(hp_wmi_input_dev); - err = input_register_device(hp_wmi_input_dev); + if (hp_wmi_bios_2009_later() == 4) + hp_wmi_enable_hotkeys(); - if (err) { - input_free_device(hp_wmi_input_dev); - return err; + status = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL); + if (ACPI_FAILURE(status)) { + err = -EIO; + goto err_free_keymap; } + err = input_register_device(hp_wmi_input_dev); + if (err) + goto err_uninstall_notifier; + return 0; + + err_uninstall_notifier: + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + err_free_keymap: + sparse_keymap_free(hp_wmi_input_dev); + err_free_dev: + input_free_device(hp_wmi_input_dev); + return err; +} + +static void hp_wmi_input_destroy(void) +{ + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + sparse_keymap_free(hp_wmi_input_dev); + input_unregister_device(hp_wmi_input_dev); } static void cleanup_sysfs(struct platform_device *device) @@ -444,34 +701,26 @@ static void cleanup_sysfs(struct platform_device *device) device_remove_file(&device->dev, &dev_attr_als); device_remove_file(&device->dev, &dev_attr_dock); device_remove_file(&device->dev, &dev_attr_tablet); + device_remove_file(&device->dev, &dev_attr_postcode); } -static int __init hp_wmi_bios_setup(struct platform_device *device) +static int hp_wmi_rfkill_setup(struct platform_device *device) { int err; - int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); + int wireless = 0; - err = device_create_file(&device->dev, &dev_attr_display); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_hddtemp); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_als); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_dock); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_tablet); + err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless, + sizeof(wireless), sizeof(wireless)); if (err) - goto add_sysfs_error; + return err; if (wireless & 0x1) { wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev, RFKILL_TYPE_WLAN, &hp_wmi_rfkill_ops, (void *) HPWMI_WIFI); + if (!wifi_rfkill) + return -ENOMEM; rfkill_init_sw_state(wifi_rfkill, hp_wmi_get_sw_state(HPWMI_WIFI)); rfkill_set_hw_state(wifi_rfkill, @@ -486,6 +735,10 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) RFKILL_TYPE_BLUETOOTH, &hp_wmi_rfkill_ops, (void *) HPWMI_BLUETOOTH); + if (!bluetooth_rfkill) { + err = -ENOMEM; + goto register_wifi_error; + } rfkill_init_sw_state(bluetooth_rfkill, hp_wmi_get_sw_state(HPWMI_BLUETOOTH)); rfkill_set_hw_state(bluetooth_rfkill, @@ -500,26 +753,177 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) RFKILL_TYPE_WWAN, &hp_wmi_rfkill_ops, (void *) HPWMI_WWAN); + if (!wwan_rfkill) { + err = -ENOMEM; + goto register_bluetooth_error; + } rfkill_init_sw_state(wwan_rfkill, hp_wmi_get_sw_state(HPWMI_WWAN)); rfkill_set_hw_state(wwan_rfkill, hp_wmi_get_hw_state(HPWMI_WWAN)); err = rfkill_register(wwan_rfkill); if (err) - goto register_wwan_err; + goto register_wwan_error; + } + + if (wireless & 0x8) { + gps_rfkill = rfkill_alloc("hp-gps", &device->dev, + RFKILL_TYPE_GPS, + &hp_wmi_rfkill_ops, + (void *) HPWMI_GPS); + if (!gps_rfkill) { + err = -ENOMEM; + goto register_wwan_error; + } + rfkill_init_sw_state(gps_rfkill, + hp_wmi_get_sw_state(HPWMI_GPS)); + rfkill_set_hw_state(gps_rfkill, + hp_wmi_get_hw_state(HPWMI_GPS)); + err = rfkill_register(gps_rfkill); + if (err) + goto register_gps_error; } return 0; -register_wwan_err: - rfkill_destroy(wwan_rfkill); +register_gps_error: + rfkill_destroy(gps_rfkill); + gps_rfkill = NULL; if (bluetooth_rfkill) rfkill_unregister(bluetooth_rfkill); +register_wwan_error: + rfkill_destroy(wwan_rfkill); + wwan_rfkill = NULL; + if (gps_rfkill) + rfkill_unregister(gps_rfkill); register_bluetooth_error: rfkill_destroy(bluetooth_rfkill); + bluetooth_rfkill = NULL; if (wifi_rfkill) rfkill_unregister(wifi_rfkill); register_wifi_error: rfkill_destroy(wifi_rfkill); + wifi_rfkill = NULL; + return err; +} + +static int hp_wmi_rfkill2_setup(struct platform_device *device) +{ + int err, i; + struct bios_rfkill2_state state; + err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state, + 0, sizeof(state)); + if (err) + return err; + + if (state.count > HPWMI_MAX_RFKILL2_DEVICES) { + pr_warn("unable to parse 0x1b query output\n"); + return -EINVAL; + } + + for (i = 0; i < state.count; i++) { + struct rfkill *rfkill; + enum rfkill_type type; + char *name; + switch (state.device[i].radio_type) { + case HPWMI_WIFI: + type = RFKILL_TYPE_WLAN; + name = "hp-wifi"; + break; + case HPWMI_BLUETOOTH: + type = RFKILL_TYPE_BLUETOOTH; + name = "hp-bluetooth"; + break; + case HPWMI_WWAN: + type = RFKILL_TYPE_WWAN; + name = "hp-wwan"; + break; + case HPWMI_GPS: + type = RFKILL_TYPE_GPS; + name = "hp-gps"; + break; + default: + pr_warn("unknown device type 0x%x\n", + state.device[i].radio_type); + continue; + } + + if (!state.device[i].vendor_id) { + pr_warn("zero device %d while %d reported\n", + i, state.count); + continue; + } + + rfkill = rfkill_alloc(name, &device->dev, type, + &hp_wmi_rfkill2_ops, (void *)(long)i); + if (!rfkill) { + err = -ENOMEM; + goto fail; + } + + rfkill2[rfkill2_count].id = state.device[i].rfkill_id; + rfkill2[rfkill2_count].num = i; + rfkill2[rfkill2_count].rfkill = rfkill; + + rfkill_init_sw_state(rfkill, + IS_SWBLOCKED(state.device[i].power)); + rfkill_set_hw_state(rfkill, + IS_HWBLOCKED(state.device[i].power)); + + if (!(state.device[i].power & HPWMI_POWER_BIOS)) + pr_info("device %s blocked by BIOS\n", name); + + err = rfkill_register(rfkill); + if (err) { + rfkill_destroy(rfkill); + goto fail; + } + + rfkill2_count++; + } + + return 0; +fail: + for (; rfkill2_count > 0; rfkill2_count--) { + rfkill_unregister(rfkill2[rfkill2_count - 1].rfkill); + rfkill_destroy(rfkill2[rfkill2_count - 1].rfkill); + } + return err; +} + +static int __init hp_wmi_bios_setup(struct platform_device *device) +{ + int err; + + /* clear detected rfkill devices */ + wifi_rfkill = NULL; + bluetooth_rfkill = NULL; + wwan_rfkill = NULL; + gps_rfkill = NULL; + rfkill2_count = 0; + + if (hp_wmi_bios_2009_later() || hp_wmi_rfkill_setup(device)) + hp_wmi_rfkill2_setup(device); + + err = device_create_file(&device->dev, &dev_attr_display); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_hddtemp); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_als); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_dock); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_tablet); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_postcode); + if (err) + goto add_sysfs_error; + return 0; + add_sysfs_error: cleanup_sysfs(device); return err; @@ -527,8 +931,14 @@ add_sysfs_error: static int __exit hp_wmi_bios_remove(struct platform_device *device) { + int i; cleanup_sysfs(device); + for (i = 0; i < rfkill2_count; i++) { + rfkill_unregister(rfkill2[i].rfkill); + rfkill_destroy(rfkill2[i].rfkill); + } + if (wifi_rfkill) { rfkill_unregister(wifi_rfkill); rfkill_destroy(wifi_rfkill); @@ -541,6 +951,10 @@ static int __exit hp_wmi_bios_remove(struct platform_device *device) rfkill_unregister(wwan_rfkill); rfkill_destroy(wwan_rfkill); } + if (gps_rfkill) { + rfkill_unregister(gps_rfkill); + rfkill_destroy(gps_rfkill); + } return 0; } @@ -561,6 +975,9 @@ static int hp_wmi_resume_handler(struct device *device) input_sync(hp_wmi_input_dev); } + if (rfkill2_count) + hp_wmi_rfkill2_refresh(); + if (wifi_rfkill) rfkill_set_states(wifi_rfkill, hp_wmi_get_sw_state(HPWMI_WIFI), @@ -573,47 +990,76 @@ static int hp_wmi_resume_handler(struct device *device) rfkill_set_states(wwan_rfkill, hp_wmi_get_sw_state(HPWMI_WWAN), hp_wmi_get_hw_state(HPWMI_WWAN)); + if (gps_rfkill) + rfkill_set_states(gps_rfkill, + hp_wmi_get_sw_state(HPWMI_GPS), + hp_wmi_get_hw_state(HPWMI_GPS)); return 0; } +static const struct dev_pm_ops hp_wmi_pm_ops = { + .resume = hp_wmi_resume_handler, + .restore = hp_wmi_resume_handler, +}; + +static struct platform_driver hp_wmi_driver = { + .driver = { + .name = "hp-wmi", + .owner = THIS_MODULE, + .pm = &hp_wmi_pm_ops, + }, + .remove = __exit_p(hp_wmi_bios_remove), +}; + static int __init hp_wmi_init(void) { int err; + int event_capable = wmi_has_guid(HPWMI_EVENT_GUID); + int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID); - if (wmi_has_guid(HPWMI_EVENT_GUID)) { - err = wmi_install_notify_handler(HPWMI_EVENT_GUID, - hp_wmi_notify, NULL); - if (ACPI_SUCCESS(err)) - hp_wmi_input_setup(); - } + if (!bios_capable && !event_capable) + return -ENODEV; - if (wmi_has_guid(HPWMI_BIOS_GUID)) { - err = platform_driver_register(&hp_wmi_driver); + if (event_capable) { + err = hp_wmi_input_setup(); if (err) - return 0; - hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1); - if (!hp_wmi_platform_dev) { - platform_driver_unregister(&hp_wmi_driver); - return 0; + return err; + } + + if (bios_capable) { + hp_wmi_platform_dev = + platform_device_register_simple("hp-wmi", -1, NULL, 0); + if (IS_ERR(hp_wmi_platform_dev)) { + err = PTR_ERR(hp_wmi_platform_dev); + goto err_destroy_input; } - platform_device_add(hp_wmi_platform_dev); + + err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); + if (err) + goto err_unregister_device; } return 0; + +err_unregister_device: + platform_device_unregister(hp_wmi_platform_dev); +err_destroy_input: + if (event_capable) + hp_wmi_input_destroy(); + + return err; } +module_init(hp_wmi_init); static void __exit hp_wmi_exit(void) { - if (wmi_has_guid(HPWMI_EVENT_GUID)) { - wmi_remove_notify_handler(HPWMI_EVENT_GUID); - input_unregister_device(hp_wmi_input_dev); - } + if (wmi_has_guid(HPWMI_EVENT_GUID)) + hp_wmi_input_destroy(); + if (hp_wmi_platform_dev) { - platform_device_del(hp_wmi_platform_dev); + platform_device_unregister(hp_wmi_platform_dev); platform_driver_unregister(&hp_wmi_driver); } } - -module_init(hp_wmi_init); module_exit(hp_wmi_exit); diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c new file mode 100644 index 00000000000..3dc934438c2 --- /dev/null +++ b/drivers/platform/x86/hp_accel.c @@ -0,0 +1,391 @@ +/* + * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS + * + * Copyright (C) 2007-2008 Yan Burman + * Copyright (C) 2008 Eric Piel + * Copyright (C) 2008-2009 Pavel Machek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/freezer.h> +#include <linux/uaccess.h> +#include <linux/leds.h> +#include <linux/atomic.h> +#include <linux/acpi.h> +#include "../../misc/lis3lv02d/lis3lv02d.h" + +#define DRIVER_NAME "hp_accel" +#define ACPI_MDPS_CLASS "accelerometer" + +/* Delayed LEDs infrastructure ------------------------------------ */ + +/* Special LED class that can defer work */ +struct delayed_led_classdev { + struct led_classdev led_classdev; + struct work_struct work; + enum led_brightness new_brightness; + + unsigned int led; /* For driver */ + void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value); +}; + +static inline void delayed_set_status_worker(struct work_struct *work) +{ + struct delayed_led_classdev *data = + container_of(work, struct delayed_led_classdev, work); + + data->set_brightness(data, data->new_brightness); +} + +static inline void delayed_sysfs_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct delayed_led_classdev *data = container_of(led_cdev, + struct delayed_led_classdev, led_classdev); + data->new_brightness = brightness; + schedule_work(&data->work); +} + +/* HP-specific accelerometer driver ------------------------------------ */ + +/* For automatic insertion of the module */ +static struct acpi_device_id lis3lv02d_device_ids[] = { + {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */ + {"HPQ6000", 0}, /* HP Mobile Data Protection System PNP */ + {"HPQ6007", 0}, /* HP Mobile Data Protection System PNP */ + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); + + +/** + * lis3lv02d_acpi_init - ACPI _INI method: initialize the device. + * @lis3: pointer to the device struct + * + * Returns 0 on success. + */ +static int lis3lv02d_acpi_init(struct lis3lv02d *lis3) +{ + struct acpi_device *dev = lis3->bus_priv; + if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI, + NULL, NULL) != AE_OK) + return -EINVAL; + + return 0; +} + +/** + * lis3lv02d_acpi_read - ACPI ALRD method: read a register + * @lis3: pointer to the device struct + * @reg: the register to read + * @ret: result of the operation + * + * Returns 0 on success. + */ +static int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) +{ + struct acpi_device *dev = lis3->bus_priv; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + unsigned long long lret; + acpi_status status; + + arg0.integer.value = reg; + + status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret); + *ret = lret; + return (status != AE_OK) ? -EINVAL : 0; +} + +/** + * lis3lv02d_acpi_write - ACPI ALWR method: write to a register + * @lis3: pointer to the device struct + * @reg: the register to write to + * @val: the value to write + * + * Returns 0 on success. + */ +static int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) +{ + struct acpi_device *dev = lis3->bus_priv; + unsigned long long ret; /* Not used when writting */ + union acpi_object in_obj[2]; + struct acpi_object_list args = { 2, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = reg; + in_obj[1].type = ACPI_TYPE_INTEGER; + in_obj[1].integer.value = val; + + if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK) + return -EINVAL; + + return 0; +} + +static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi) +{ + lis3_dev.ac = *((union axis_conversion *)dmi->driver_data); + pr_info("hardware type %s found\n", dmi->ident); + + return 1; +} + +/* Represents, for each axis seen by userspace, the corresponding hw axis (+1). + * If the value is negative, the opposite of the hw value is used. */ +#define DEFINE_CONV(name, x, y, z) \ + static union axis_conversion lis3lv02d_axis_##name = \ + { .as_array = { x, y, z } } +DEFINE_CONV(normal, 1, 2, 3); +DEFINE_CONV(y_inverted, 1, -2, 3); +DEFINE_CONV(x_inverted, -1, 2, 3); +DEFINE_CONV(z_inverted, 1, 2, -3); +DEFINE_CONV(xy_swap, 2, 1, 3); +DEFINE_CONV(xy_rotated_left, -2, 1, 3); +DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3); +DEFINE_CONV(xy_swap_inverted, -2, -1, 3); +DEFINE_CONV(xy_rotated_right, 2, -1, 3); +DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3); + +#define AXIS_DMI_MATCH(_ident, _name, _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_PRODUCT_NAME, _name) \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} + +#define AXIS_DMI_MATCH2(_ident, _class1, _name1, \ + _class2, _name2, \ + _axis) { \ + .ident = _ident, \ + .callback = lis3lv02d_dmi_matched, \ + .matches = { \ + DMI_MATCH(DMI_##_class1, _name1), \ + DMI_MATCH(DMI_##_class2, _name2), \ + }, \ + .driver_data = &lis3lv02d_axis_##_axis \ +} +static struct dmi_system_id lis3lv02d_dmi_ids[] = { + /* product names are truncated to match all kinds of a same model */ + AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted), + AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted), + AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted), + AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted), + AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted), + AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap), + AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted), + AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left), + AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted), + AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd), + AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap), + AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right), + AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted), + AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right), + AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap), + AXIS_DMI_MATCH("NC854xx", "HP EliteBook 854", y_inverted), + AXIS_DMI_MATCH("NC273xx", "HP EliteBook 273", y_inverted), + /* Intel-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_I", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3603", + x_inverted), + /* AMD-based HP Pavilion dv5 */ + AXIS_DMI_MATCH2("HPDV5_A", + PRODUCT_NAME, "HP Pavilion dv5", + BOARD_NAME, "3600", + y_inverted), + AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted), + AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted), + AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted), + AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left), + AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), + AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), + AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), + AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), + AXIS_DMI_MATCH("HPB655x", "HP ProBook 655", xy_swap_inverted), + AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd), + AXIS_DMI_MATCH("HPB63xx", "HP ProBook 63", xy_swap), + AXIS_DMI_MATCH("HPB64xx", "HP ProBook 64", xy_swap), + AXIS_DMI_MATCH("HPB64xx", "HP EliteBook 84", xy_swap), + AXIS_DMI_MATCH("HPB65xx", "HP ProBook 65", x_inverted), + { NULL, } +/* Laptop models without axis info (yet): + * "NC6910" "HP Compaq 6910" + * "NC2400" "HP Compaq nc2400" + * "NX74x0" "HP Compaq nx74" + * "NX6325" "HP Compaq nx6325" + * "NC4400" "HP Compaq nc4400" + */ +}; + +static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value) +{ + struct acpi_device *dev = lis3_dev.bus_priv; + unsigned long long ret; /* Not used when writing */ + union acpi_object in_obj[1]; + struct acpi_object_list args = { 1, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = !!value; + + acpi_evaluate_integer(dev->handle, "ALED", &args, &ret); +} + +static struct delayed_led_classdev hpled_led = { + .led_classdev = { + .name = "hp::hddprotect", + .default_trigger = "none", + .brightness_set = delayed_sysfs_set, + .flags = LED_CORE_SUSPENDRESUME, + }, + .set_brightness = hpled_set, +}; + +static acpi_status +lis3lv02d_get_resource(struct acpi_resource *resource, void *context) +{ + if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { + struct acpi_resource_extended_irq *irq; + u32 *device_irq = context; + + irq = &resource->data.extended_irq; + *device_irq = irq->interrupts[0]; + } + + return AE_OK; +} + +static void lis3lv02d_enum_resources(struct acpi_device *device) +{ + acpi_status status; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + lis3lv02d_get_resource, &lis3_dev.irq); + if (ACPI_FAILURE(status)) + printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); +} + +static int lis3lv02d_add(struct acpi_device *device) +{ + int ret; + + if (!device) + return -EINVAL; + + lis3_dev.bus_priv = device; + lis3_dev.init = lis3lv02d_acpi_init; + lis3_dev.read = lis3lv02d_acpi_read; + lis3_dev.write = lis3lv02d_acpi_write; + strcpy(acpi_device_name(device), DRIVER_NAME); + strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); + device->driver_data = &lis3_dev; + + /* obtain IRQ number of our device from ACPI */ + lis3lv02d_enum_resources(device); + + /* If possible use a "standard" axes order */ + if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { + pr_info("Using custom axes %d,%d,%d\n", + lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z); + } else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) { + pr_info("laptop model unknown, using default axes configuration\n"); + lis3_dev.ac = lis3lv02d_axis_normal; + } + + /* call the core layer do its init */ + ret = lis3lv02d_init_device(&lis3_dev); + if (ret) + return ret; + + INIT_WORK(&hpled_led.work, delayed_set_status_worker); + ret = led_classdev_register(NULL, &hpled_led.led_classdev); + if (ret) { + lis3lv02d_joystick_disable(&lis3_dev); + lis3lv02d_poweroff(&lis3_dev); + flush_work(&hpled_led.work); + return ret; + } + + return ret; +} + +static int lis3lv02d_remove(struct acpi_device *device) +{ + if (!device) + return -EINVAL; + + lis3lv02d_joystick_disable(&lis3_dev); + lis3lv02d_poweroff(&lis3_dev); + + led_classdev_unregister(&hpled_led.led_classdev); + flush_work(&hpled_led.work); + + return lis3lv02d_remove_fs(&lis3_dev); +} + + +#ifdef CONFIG_PM_SLEEP +static int lis3lv02d_suspend(struct device *dev) +{ + /* make sure the device is off when we suspend */ + lis3lv02d_poweroff(&lis3_dev); + return 0; +} + +static int lis3lv02d_resume(struct device *dev) +{ + lis3lv02d_poweron(&lis3_dev); + return 0; +} + +static SIMPLE_DEV_PM_OPS(hp_accel_pm, lis3lv02d_suspend, lis3lv02d_resume); +#define HP_ACCEL_PM (&hp_accel_pm) +#else +#define HP_ACCEL_PM NULL +#endif + +/* For the HP MDPS aka 3D Driveguard */ +static struct acpi_driver lis3lv02d_driver = { + .name = DRIVER_NAME, + .class = ACPI_MDPS_CLASS, + .ids = lis3lv02d_device_ids, + .ops = { + .add = lis3lv02d_add, + .remove = lis3lv02d_remove, + }, + .drv.pm = HP_ACCEL_PM, +}; +module_acpi_driver(lis3lv02d_driver); + +MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED."); +MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c new file mode 100644 index 00000000000..97c2be195ef --- /dev/null +++ b/drivers/platform/x86/ibm_rtl.c @@ -0,0 +1,328 @@ +/* + * IBM Real-Time Linux driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Copyright (C) IBM Corporation, 2010 + * + * Author: Keith Mannthey <kmannth@us.ibm.com> + * Vernon Mauery <vernux@us.ibm.com> + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/dmi.h> +#include <linux/efi.h> +#include <linux/mutex.h> +#include <asm/bios_ebda.h> + +#include <asm-generic/io-64-nonatomic-lo-hi.h> + +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); + +static bool debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Show debug output"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>"); +MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>"); + +#define RTL_ADDR_TYPE_IO 1 +#define RTL_ADDR_TYPE_MMIO 2 + +#define RTL_CMD_ENTER_PRTM 1 +#define RTL_CMD_EXIT_PRTM 2 + +/* The RTL table as presented by the EBDA: */ +struct ibm_rtl_table { + char signature[5]; /* signature should be "_RTL_" */ + u8 version; + u8 rt_status; + u8 command; + u8 command_status; + u8 cmd_address_type; + u8 cmd_granularity; + u8 cmd_offset; + u16 reserve1; + u32 cmd_port_address; /* platform dependent address */ + u32 cmd_port_value; /* platform dependent value */ +} __attribute__((packed)); + +/* to locate "_RTL_" signature do a masked 5-byte integer compare */ +#define RTL_SIGNATURE 0x0000005f4c54525fULL +#define RTL_MASK 0x000000ffffffffffULL + +#define RTL_DEBUG(fmt, ...) \ +do { \ + if (debug) \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) + +static DEFINE_MUTEX(rtl_lock); +static struct ibm_rtl_table __iomem *rtl_table; +static void __iomem *ebda_map; +static void __iomem *rtl_cmd_addr; +static u8 rtl_cmd_type; +static u8 rtl_cmd_width; + +static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len) +{ + if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO) + return ioremap(addr, len); + return ioport_map(addr, len); +} + +static void rtl_port_unmap(void __iomem *addr) +{ + if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO) + iounmap(addr); + else + ioport_unmap(addr); +} + +static int ibm_rtl_write(u8 value) +{ + int ret = 0, count = 0; + static u32 cmd_port_val; + + RTL_DEBUG("%s(%d)\n", __func__, value); + + value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM; + + mutex_lock(&rtl_lock); + + if (ioread8(&rtl_table->rt_status) != value) { + iowrite8(value, &rtl_table->command); + + switch (rtl_cmd_width) { + case 8: + cmd_port_val = ioread8(&rtl_table->cmd_port_value); + RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); + iowrite8((u8)cmd_port_val, rtl_cmd_addr); + break; + case 16: + cmd_port_val = ioread16(&rtl_table->cmd_port_value); + RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); + iowrite16((u16)cmd_port_val, rtl_cmd_addr); + break; + case 32: + cmd_port_val = ioread32(&rtl_table->cmd_port_value); + RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); + iowrite32(cmd_port_val, rtl_cmd_addr); + break; + } + + while (ioread8(&rtl_table->command)) { + msleep(10); + if (count++ > 500) { + pr_err("Hardware not responding to " + "mode switch request\n"); + ret = -EIO; + break; + } + + } + + if (ioread8(&rtl_table->command_status)) { + RTL_DEBUG("command_status reports failed command\n"); + ret = -EIO; + } + } + + mutex_unlock(&rtl_lock); + return ret; +} + +static ssize_t rtl_show_version(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version)); +} + +static ssize_t rtl_show_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status)); +} + +static ssize_t rtl_set_state(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + ssize_t ret; + + if (count < 1 || count > 2) + return -EINVAL; + + switch (buf[0]) { + case '0': + ret = ibm_rtl_write(0); + break; + case '1': + ret = ibm_rtl_write(1); + break; + default: + ret = -EINVAL; + } + if (ret >= 0) + ret = count; + + return ret; +} + +static struct bus_type rtl_subsys = { + .name = "ibm_rtl", + .dev_name = "ibm_rtl", +}; + +static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL); +static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state); + +static struct device_attribute *rtl_attributes[] = { + &dev_attr_version, + &dev_attr_state, + NULL +}; + + +static int rtl_setup_sysfs(void) { + int ret, i; + + ret = subsys_system_register(&rtl_subsys, NULL); + if (!ret) { + for (i = 0; rtl_attributes[i]; i ++) + device_create_file(rtl_subsys.dev_root, rtl_attributes[i]); + } + return ret; +} + +static void rtl_teardown_sysfs(void) { + int i; + for (i = 0; rtl_attributes[i]; i ++) + device_remove_file(rtl_subsys.dev_root, rtl_attributes[i]); + bus_unregister(&rtl_subsys); +} + + +static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = { + { \ + .matches = { \ + DMI_MATCH(DMI_SYS_VENDOR, "IBM"), \ + }, \ + }, + { } +}; + +static int __init ibm_rtl_init(void) { + unsigned long ebda_addr, ebda_size; + unsigned int ebda_kb; + int ret = -ENODEV, i; + + if (force) + pr_warn("module loaded by force\n"); + /* first ensure that we are running on IBM HW */ + else if (efi_enabled(EFI_BOOT) || !dmi_check_system(ibm_rtl_dmi_table)) + return -ENODEV; + + /* Get the address for the Extended BIOS Data Area */ + ebda_addr = get_bios_ebda(); + if (!ebda_addr) { + RTL_DEBUG("no BIOS EBDA found\n"); + return -ENODEV; + } + + ebda_map = ioremap(ebda_addr, 4); + if (!ebda_map) + return -ENOMEM; + + /* First word in the EDBA is the Size in KB */ + ebda_kb = ioread16(ebda_map); + RTL_DEBUG("EBDA is %d kB\n", ebda_kb); + + if (ebda_kb == 0) + goto out; + + iounmap(ebda_map); + ebda_size = ebda_kb*1024; + + /* Remap the whole table */ + ebda_map = ioremap(ebda_addr, ebda_size); + if (!ebda_map) + return -ENOMEM; + + /* search for the _RTL_ signature at the start of the table */ + for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) { + struct ibm_rtl_table __iomem * tmp; + tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i); + if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) { + phys_addr_t addr; + unsigned int plen; + RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp); + rtl_table = tmp; + /* The address, value, width and offset are platform + * dependent and found in the ibm_rtl_table */ + rtl_cmd_width = ioread8(&rtl_table->cmd_granularity); + rtl_cmd_type = ioread8(&rtl_table->cmd_address_type); + RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n", + rtl_cmd_width, rtl_cmd_type); + addr = ioread32(&rtl_table->cmd_port_address); + RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr); + plen = rtl_cmd_width/sizeof(char); + rtl_cmd_addr = rtl_port_map(addr, plen); + RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr); + if (!rtl_cmd_addr) { + ret = -ENOMEM; + break; + } + ret = rtl_setup_sysfs(); + break; + } + } + +out: + if (ret) { + iounmap(ebda_map); + rtl_port_unmap(rtl_cmd_addr); + } + + return ret; +} + +static void __exit ibm_rtl_exit(void) +{ + if (rtl_table) { + RTL_DEBUG("cleaning up"); + /* do not leave the machine in SMI-free mode */ + ibm_rtl_write(0); + /* unmap, unlink and remove all traces */ + rtl_teardown_sysfs(); + iounmap(ebda_map); + rtl_port_unmap(rtl_cmd_addr); + } +} + +module_init(ibm_rtl_init); +module_exit(ibm_rtl_exit); diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c new file mode 100644 index 00000000000..b4c495a62ee --- /dev/null +++ b/drivers/platform/x86/ideapad-laptop.c @@ -0,0 +1,959 @@ +/* + * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras + * + * Copyright © 2010 Intel Corporation + * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/acpi.h> +#include <linux/rfkill.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/backlight.h> +#include <linux/fb.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/i8042.h> +#include <linux/dmi.h> +#include <linux/device.h> + +#define IDEAPAD_RFKILL_DEV_NUM (3) + +#define CFG_BT_BIT (16) +#define CFG_3G_BIT (17) +#define CFG_WIFI_BIT (18) +#define CFG_CAMERA_BIT (19) + +enum { + VPCCMD_R_VPC1 = 0x10, + VPCCMD_R_BL_MAX, + VPCCMD_R_BL, + VPCCMD_W_BL, + VPCCMD_R_WIFI, + VPCCMD_W_WIFI, + VPCCMD_R_BT, + VPCCMD_W_BT, + VPCCMD_R_BL_POWER, + VPCCMD_R_NOVO, + VPCCMD_R_VPC2, + VPCCMD_R_TOUCHPAD, + VPCCMD_W_TOUCHPAD, + VPCCMD_R_CAMERA, + VPCCMD_W_CAMERA, + VPCCMD_R_3G, + VPCCMD_W_3G, + VPCCMD_R_ODD, /* 0x21 */ + VPCCMD_W_FAN, + VPCCMD_R_RF, + VPCCMD_W_RF, + VPCCMD_R_FAN = 0x2B, + VPCCMD_R_SPECIAL_BUTTONS = 0x31, + VPCCMD_W_BL_POWER = 0x33, +}; + +struct ideapad_rfk_priv { + int dev; + struct ideapad_private *priv; +}; + +struct ideapad_private { + struct acpi_device *adev; + struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; + struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; + struct platform_device *platform_device; + struct input_dev *inputdev; + struct backlight_device *blightdev; + struct dentry *debug; + unsigned long cfg; +}; + +static bool no_bt_rfkill; +module_param(no_bt_rfkill, bool, 0444); +MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); + +/* + * ACPI Helpers + */ +#define IDEAPAD_EC_TIMEOUT (100) /* in ms */ + +static int read_method_int(acpi_handle handle, const char *method, int *val) +{ + acpi_status status; + unsigned long long result; + + status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); + if (ACPI_FAILURE(status)) { + *val = -1; + return -1; + } else { + *val = result; + return 0; + } +} + +static int method_vpcr(acpi_handle handle, int cmd, int *ret) +{ + acpi_status status; + unsigned long long result; + struct acpi_object_list params; + union acpi_object in_obj; + + params.count = 1; + params.pointer = &in_obj; + in_obj.type = ACPI_TYPE_INTEGER; + in_obj.integer.value = cmd; + + status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); + + if (ACPI_FAILURE(status)) { + *ret = -1; + return -1; + } else { + *ret = result; + return 0; + } +} + +static int method_vpcw(acpi_handle handle, int cmd, int data) +{ + struct acpi_object_list params; + union acpi_object in_obj[2]; + acpi_status status; + + params.count = 2; + params.pointer = in_obj; + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = cmd; + in_obj[1].type = ACPI_TYPE_INTEGER; + in_obj[1].integer.value = data; + + status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); + if (status != AE_OK) + return -1; + return 0; +} + +static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) +{ + int val; + unsigned long int end_jiffies; + + if (method_vpcw(handle, 1, cmd)) + return -1; + + for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; + time_before(jiffies, end_jiffies);) { + schedule(); + if (method_vpcr(handle, 1, &val)) + return -1; + if (val == 0) { + if (method_vpcr(handle, 0, &val)) + return -1; + *data = val; + return 0; + } + } + pr_err("timeout in read_ec_cmd\n"); + return -1; +} + +static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) +{ + int val; + unsigned long int end_jiffies; + + if (method_vpcw(handle, 0, data)) + return -1; + if (method_vpcw(handle, 1, cmd)) + return -1; + + for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; + time_before(jiffies, end_jiffies);) { + schedule(); + if (method_vpcr(handle, 1, &val)) + return -1; + if (val == 0) + return 0; + } + pr_err("timeout in write_ec_cmd\n"); + return -1; +} + +/* + * debugfs + */ +static int debugfs_status_show(struct seq_file *s, void *data) +{ + struct ideapad_private *priv = s->private; + unsigned long value; + + if (!priv) + return -EINVAL; + + if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) + seq_printf(s, "Backlight max:\t%lu\n", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) + seq_printf(s, "Backlight now:\t%lu\n", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) + seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off"); + seq_printf(s, "=====================\n"); + + if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) + seq_printf(s, "Radio status:\t%s(%lu)\n", + value ? "On" : "Off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) + seq_printf(s, "Wifi status:\t%s(%lu)\n", + value ? "On" : "Off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) + seq_printf(s, "BT status:\t%s(%lu)\n", + value ? "On" : "Off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) + seq_printf(s, "3G status:\t%s(%lu)\n", + value ? "On" : "Off", value); + seq_printf(s, "=====================\n"); + + if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) + seq_printf(s, "Touchpad status:%s(%lu)\n", + value ? "On" : "Off", value); + if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) + seq_printf(s, "Camera status:\t%s(%lu)\n", + value ? "On" : "Off", value); + + return 0; +} + +static int debugfs_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, debugfs_status_show, inode->i_private); +} + +static const struct file_operations debugfs_status_fops = { + .owner = THIS_MODULE, + .open = debugfs_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int debugfs_cfg_show(struct seq_file *s, void *data) +{ + struct ideapad_private *priv = s->private; + + if (!priv) { + seq_printf(s, "cfg: N/A\n"); + } else { + seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", + priv->cfg); + if (test_bit(CFG_BT_BIT, &priv->cfg)) + seq_printf(s, "Bluetooth "); + if (test_bit(CFG_3G_BIT, &priv->cfg)) + seq_printf(s, "3G "); + if (test_bit(CFG_WIFI_BIT, &priv->cfg)) + seq_printf(s, "Wireless "); + if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) + seq_printf(s, "Camera "); + seq_printf(s, "\nGraphic: "); + switch ((priv->cfg)&0x700) { + case 0x100: + seq_printf(s, "Intel"); + break; + case 0x200: + seq_printf(s, "ATI"); + break; + case 0x300: + seq_printf(s, "Nvidia"); + break; + case 0x400: + seq_printf(s, "Intel and ATI"); + break; + case 0x500: + seq_printf(s, "Intel and Nvidia"); + break; + } + seq_printf(s, "\n"); + } + return 0; +} + +static int debugfs_cfg_open(struct inode *inode, struct file *file) +{ + return single_open(file, debugfs_cfg_show, inode->i_private); +} + +static const struct file_operations debugfs_cfg_fops = { + .owner = THIS_MODULE, + .open = debugfs_cfg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int ideapad_debugfs_init(struct ideapad_private *priv) +{ + struct dentry *node; + + priv->debug = debugfs_create_dir("ideapad", NULL); + if (priv->debug == NULL) { + pr_err("failed to create debugfs directory"); + goto errout; + } + + node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv, + &debugfs_cfg_fops); + if (!node) { + pr_err("failed to create cfg in debugfs"); + goto errout; + } + + node = debugfs_create_file("status", S_IRUGO, priv->debug, priv, + &debugfs_status_fops); + if (!node) { + pr_err("failed to create status in debugfs"); + goto errout; + } + + return 0; + +errout: + return -ENOMEM; +} + +static void ideapad_debugfs_exit(struct ideapad_private *priv) +{ + debugfs_remove_recursive(priv->debug); + priv->debug = NULL; +} + +/* + * sysfs + */ +static ssize_t show_ideapad_cam(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned long result; + struct ideapad_private *priv = dev_get_drvdata(dev); + + if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result)) + return sprintf(buf, "-1\n"); + return sprintf(buf, "%lu\n", result); +} + +static ssize_t store_ideapad_cam(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret, state; + struct ideapad_private *priv = dev_get_drvdata(dev); + + if (!count) + return 0; + if (sscanf(buf, "%i", &state) != 1) + return -EINVAL; + ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); + if (ret < 0) + return -EIO; + return count; +} + +static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); + +static ssize_t show_ideapad_fan(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned long result; + struct ideapad_private *priv = dev_get_drvdata(dev); + + if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result)) + return sprintf(buf, "-1\n"); + return sprintf(buf, "%lu\n", result); +} + +static ssize_t store_ideapad_fan(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret, state; + struct ideapad_private *priv = dev_get_drvdata(dev); + + if (!count) + return 0; + if (sscanf(buf, "%i", &state) != 1) + return -EINVAL; + if (state < 0 || state > 4 || state == 3) + return -EINVAL; + ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); + if (ret < 0) + return -EIO; + return count; +} + +static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); + +static struct attribute *ideapad_attributes[] = { + &dev_attr_camera_power.attr, + &dev_attr_fan_mode.attr, + NULL +}; + +static umode_t ideapad_is_visible(struct kobject *kobj, + struct attribute *attr, + int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct ideapad_private *priv = dev_get_drvdata(dev); + bool supported; + + if (attr == &dev_attr_camera_power.attr) + supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg)); + else if (attr == &dev_attr_fan_mode.attr) { + unsigned long value; + supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN, + &value); + } else + supported = true; + + return supported ? attr->mode : 0; +} + +static struct attribute_group ideapad_attribute_group = { + .is_visible = ideapad_is_visible, + .attrs = ideapad_attributes +}; + +/* + * Rfkill + */ +struct ideapad_rfk_data { + char *name; + int cfgbit; + int opcode; + int type; +}; + +const struct ideapad_rfk_data ideapad_rfk_data[] = { + { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, + { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, + { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, +}; + +static int ideapad_rfk_set(void *data, bool blocked) +{ + struct ideapad_rfk_priv *priv = data; + + return write_ec_cmd(priv->priv->adev->handle, priv->dev, !blocked); +} + +static struct rfkill_ops ideapad_rfk_ops = { + .set_block = ideapad_rfk_set, +}; + +static void ideapad_sync_rfk_state(struct ideapad_private *priv) +{ + unsigned long hw_blocked; + int i; + + if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) + return; + hw_blocked = !hw_blocked; + + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) + if (priv->rfk[i]) + rfkill_set_hw_state(priv->rfk[i], hw_blocked); +} + +static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) +{ + int ret; + unsigned long sw_blocked; + + if (no_bt_rfkill && + (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { + /* Force to enable bluetooth when no_bt_rfkill=1 */ + write_ec_cmd(priv->adev->handle, + ideapad_rfk_data[dev].opcode, 1); + return 0; + } + priv->rfk_priv[dev].dev = dev; + priv->rfk_priv[dev].priv = priv; + + priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, + &priv->platform_device->dev, + ideapad_rfk_data[dev].type, + &ideapad_rfk_ops, + &priv->rfk_priv[dev]); + if (!priv->rfk[dev]) + return -ENOMEM; + + if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1, + &sw_blocked)) { + rfkill_init_sw_state(priv->rfk[dev], 0); + } else { + sw_blocked = !sw_blocked; + rfkill_init_sw_state(priv->rfk[dev], sw_blocked); + } + + ret = rfkill_register(priv->rfk[dev]); + if (ret) { + rfkill_destroy(priv->rfk[dev]); + return ret; + } + return 0; +} + +static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) +{ + if (!priv->rfk[dev]) + return; + + rfkill_unregister(priv->rfk[dev]); + rfkill_destroy(priv->rfk[dev]); +} + +/* + * Platform device + */ +static int ideapad_sysfs_init(struct ideapad_private *priv) +{ + return sysfs_create_group(&priv->platform_device->dev.kobj, + &ideapad_attribute_group); +} + +static void ideapad_sysfs_exit(struct ideapad_private *priv) +{ + sysfs_remove_group(&priv->platform_device->dev.kobj, + &ideapad_attribute_group); +} + +/* + * input device + */ +static const struct key_entry ideapad_keymap[] = { + { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 7, { KEY_CAMERA } }, + { KE_KEY, 11, { KEY_F16 } }, + { KE_KEY, 13, { KEY_WLAN } }, + { KE_KEY, 16, { KEY_PROG1 } }, + { KE_KEY, 17, { KEY_PROG2 } }, + { KE_KEY, 64, { KEY_PROG3 } }, + { KE_KEY, 65, { KEY_PROG4 } }, + { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, + { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, + { KE_END, 0 }, +}; + +static int ideapad_input_init(struct ideapad_private *priv) +{ + struct input_dev *inputdev; + int error; + + inputdev = input_allocate_device(); + if (!inputdev) + return -ENOMEM; + + inputdev->name = "Ideapad extra buttons"; + inputdev->phys = "ideapad/input0"; + inputdev->id.bustype = BUS_HOST; + inputdev->dev.parent = &priv->platform_device->dev; + + error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); + if (error) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; + } + + error = input_register_device(inputdev); + if (error) { + pr_err("Unable to register input device\n"); + goto err_free_keymap; + } + + priv->inputdev = inputdev; + return 0; + +err_free_keymap: + sparse_keymap_free(inputdev); +err_free_dev: + input_free_device(inputdev); + return error; +} + +static void ideapad_input_exit(struct ideapad_private *priv) +{ + sparse_keymap_free(priv->inputdev); + input_unregister_device(priv->inputdev); + priv->inputdev = NULL; +} + +static void ideapad_input_report(struct ideapad_private *priv, + unsigned long scancode) +{ + sparse_keymap_report_event(priv->inputdev, scancode, 1, true); +} + +static void ideapad_input_novokey(struct ideapad_private *priv) +{ + unsigned long long_pressed; + + if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) + return; + if (long_pressed) + ideapad_input_report(priv, 17); + else + ideapad_input_report(priv, 16); +} + +static void ideapad_check_special_buttons(struct ideapad_private *priv) +{ + unsigned long bit, value; + + read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value); + + for (bit = 0; bit < 16; bit++) { + if (test_bit(bit, &value)) { + switch (bit) { + case 0: /* Z580 */ + case 6: /* Z570 */ + /* Thermal Management button */ + ideapad_input_report(priv, 65); + break; + case 1: + /* OneKey Theater button */ + ideapad_input_report(priv, 64); + break; + default: + pr_info("Unknown special button: %lu\n", bit); + break; + } + } + } +} + +/* + * backlight + */ +static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) +{ + struct ideapad_private *priv = bl_get_data(blightdev); + unsigned long now; + + if (!priv) + return -EINVAL; + + if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) + return -EIO; + return now; +} + +static int ideapad_backlight_update_status(struct backlight_device *blightdev) +{ + struct ideapad_private *priv = bl_get_data(blightdev); + + if (!priv) + return -EINVAL; + + if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, + blightdev->props.brightness)) + return -EIO; + if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, + blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1)) + return -EIO; + + return 0; +} + +static const struct backlight_ops ideapad_backlight_ops = { + .get_brightness = ideapad_backlight_get_brightness, + .update_status = ideapad_backlight_update_status, +}; + +static int ideapad_backlight_init(struct ideapad_private *priv) +{ + struct backlight_device *blightdev; + struct backlight_properties props; + unsigned long max, now, power; + + if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max)) + return -EIO; + if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) + return -EIO; + if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) + return -EIO; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.max_brightness = max; + props.type = BACKLIGHT_PLATFORM; + blightdev = backlight_device_register("ideapad", + &priv->platform_device->dev, + priv, + &ideapad_backlight_ops, + &props); + if (IS_ERR(blightdev)) { + pr_err("Could not register backlight device\n"); + return PTR_ERR(blightdev); + } + + priv->blightdev = blightdev; + blightdev->props.brightness = now; + blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; + backlight_update_status(blightdev); + + return 0; +} + +static void ideapad_backlight_exit(struct ideapad_private *priv) +{ + if (priv->blightdev) + backlight_device_unregister(priv->blightdev); + priv->blightdev = NULL; +} + +static void ideapad_backlight_notify_power(struct ideapad_private *priv) +{ + unsigned long power; + struct backlight_device *blightdev = priv->blightdev; + + if (!blightdev) + return; + if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) + return; + blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; +} + +static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) +{ + unsigned long now; + + /* if we control brightness via acpi video driver */ + if (priv->blightdev == NULL) { + read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); + return; + } + + backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); +} + +/* + * module init/exit + */ +static void ideapad_sync_touchpad_state(struct ideapad_private *priv) +{ + unsigned long value; + + /* Without reading from EC touchpad LED doesn't switch state */ + if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { + /* Some IdeaPads don't really turn off touchpad - they only + * switch the LED state. We (de)activate KBC AUX port to turn + * touchpad off and on. We send KEY_TOUCHPAD_OFF and + * KEY_TOUCHPAD_ON to not to get out of sync with LED */ + unsigned char param; + i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : + I8042_CMD_AUX_DISABLE); + ideapad_input_report(priv, value ? 67 : 66); + } +} + +static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct ideapad_private *priv = data; + unsigned long vpc1, vpc2, vpc_bit; + + if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) + return; + if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) + return; + + vpc1 = (vpc2 << 8) | vpc1; + for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { + if (test_bit(vpc_bit, &vpc1)) { + switch (vpc_bit) { + case 9: + ideapad_sync_rfk_state(priv); + break; + case 13: + case 11: + case 7: + case 6: + ideapad_input_report(priv, vpc_bit); + break; + case 5: + ideapad_sync_touchpad_state(priv); + break; + case 4: + ideapad_backlight_notify_brightness(priv); + break; + case 3: + ideapad_input_novokey(priv); + break; + case 2: + ideapad_backlight_notify_power(priv); + break; + case 0: + ideapad_check_special_buttons(priv); + break; + default: + pr_info("Unknown event: %lu\n", vpc_bit); + } + } + } +} + +/* Blacklist for devices where the ideapad rfkill interface does not work */ +static struct dmi_system_id rfkill_blacklist[] = { + /* The Lenovo Yoga 2 11 always reports everything as blocked */ + { + .ident = "Lenovo Yoga 2 11", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 2 11"), + }, + }, + {} +}; + +static int ideapad_acpi_add(struct platform_device *pdev) +{ + int ret, i; + int cfg; + struct ideapad_private *priv; + struct acpi_device *adev; + + ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); + if (ret) + return -ENODEV; + + if (read_method_int(adev->handle, "_CFG", &cfg)) + return -ENODEV; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, priv); + priv->cfg = cfg; + priv->adev = adev; + priv->platform_device = pdev; + + ret = ideapad_sysfs_init(priv); + if (ret) + return ret; + + ret = ideapad_debugfs_init(priv); + if (ret) + goto debugfs_failed; + + ret = ideapad_input_init(priv); + if (ret) + goto input_failed; + + if (!dmi_check_system(rfkill_blacklist)) { + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) + if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) + ideapad_register_rfkill(priv, i); + } + ideapad_sync_rfk_state(priv); + ideapad_sync_touchpad_state(priv); + + if (!acpi_video_backlight_support()) { + ret = ideapad_backlight_init(priv); + if (ret && ret != -ENODEV) + goto backlight_failed; + } + ret = acpi_install_notify_handler(adev->handle, + ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv); + if (ret) + goto notification_failed; + + return 0; +notification_failed: + ideapad_backlight_exit(priv); +backlight_failed: + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) + ideapad_unregister_rfkill(priv, i); + ideapad_input_exit(priv); +input_failed: + ideapad_debugfs_exit(priv); +debugfs_failed: + ideapad_sysfs_exit(priv); + return ret; +} + +static int ideapad_acpi_remove(struct platform_device *pdev) +{ + struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); + int i; + + acpi_remove_notify_handler(priv->adev->handle, + ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); + ideapad_backlight_exit(priv); + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) + ideapad_unregister_rfkill(priv, i); + ideapad_input_exit(priv); + ideapad_debugfs_exit(priv); + ideapad_sysfs_exit(priv); + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ideapad_acpi_resume(struct device *device) +{ + struct ideapad_private *priv; + + if (!device) + return -EINVAL; + priv = dev_get_drvdata(device); + + ideapad_sync_rfk_state(priv); + ideapad_sync_touchpad_state(priv); + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); + +static const struct acpi_device_id ideapad_device_ids[] = { + { "VPC2004", 0}, + { "", 0}, +}; +MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); + +static struct platform_driver ideapad_acpi_driver = { + .probe = ideapad_acpi_add, + .remove = ideapad_acpi_remove, + .driver = { + .name = "ideapad_acpi", + .owner = THIS_MODULE, + .pm = &ideapad_pm, + .acpi_match_table = ACPI_PTR(ideapad_device_ids), + }, +}; + +module_platform_driver(ideapad_acpi_driver); + +MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); +MODULE_DESCRIPTION("IdeaPad ACPI Extras"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel-rst.c b/drivers/platform/x86/intel-rst.c new file mode 100644 index 00000000000..d45bca34bf1 --- /dev/null +++ b/drivers/platform/x86/intel-rst.c @@ -0,0 +1,166 @@ +/* + * Copyright 2013 Matthew Garrett <mjg59@srcf.ucam.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/acpi.h> + +MODULE_LICENSE("GPL"); + +static ssize_t irst_show_wakeup_events(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi; + unsigned long long value; + acpi_status status; + + acpi = to_acpi_device(dev); + + status = acpi_evaluate_integer(acpi->handle, "GFFS", NULL, &value); + if (!ACPI_SUCCESS(status)) + return -EINVAL; + + return sprintf(buf, "%lld\n", value); +} + +static ssize_t irst_store_wakeup_events(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi; + acpi_status status; + unsigned long value; + int error; + + acpi = to_acpi_device(dev); + + error = kstrtoul(buf, 0, &value); + + if (error) + return error; + + status = acpi_execute_simple_method(acpi->handle, "SFFS", value); + + if (!ACPI_SUCCESS(status)) + return -EINVAL; + + return count; +} + +static struct device_attribute irst_wakeup_attr = { + .attr = { .name = "wakeup_events", .mode = 0600 }, + .show = irst_show_wakeup_events, + .store = irst_store_wakeup_events +}; + +static ssize_t irst_show_wakeup_time(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_device *acpi; + unsigned long long value; + acpi_status status; + + acpi = to_acpi_device(dev); + + status = acpi_evaluate_integer(acpi->handle, "GFTV", NULL, &value); + if (!ACPI_SUCCESS(status)) + return -EINVAL; + + return sprintf(buf, "%lld\n", value); +} + +static ssize_t irst_store_wakeup_time(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi; + acpi_status status; + unsigned long value; + int error; + + acpi = to_acpi_device(dev); + + error = kstrtoul(buf, 0, &value); + + if (error) + return error; + + status = acpi_execute_simple_method(acpi->handle, "SFTV", value); + + if (!ACPI_SUCCESS(status)) + return -EINVAL; + + return count; +} + +static struct device_attribute irst_timeout_attr = { + .attr = { .name = "wakeup_time", .mode = 0600 }, + .show = irst_show_wakeup_time, + .store = irst_store_wakeup_time +}; + +static int irst_add(struct acpi_device *acpi) +{ + int error = 0; + + error = device_create_file(&acpi->dev, &irst_timeout_attr); + if (error) + goto out; + + error = device_create_file(&acpi->dev, &irst_wakeup_attr); + if (error) + goto out_timeout; + + return 0; + +out_timeout: + device_remove_file(&acpi->dev, &irst_timeout_attr); +out: + return error; +} + +static int irst_remove(struct acpi_device *acpi) +{ + device_remove_file(&acpi->dev, &irst_wakeup_attr); + device_remove_file(&acpi->dev, &irst_timeout_attr); + + return 0; +} + +static const struct acpi_device_id irst_ids[] = { + {"INT3392", 0}, + {"", 0} +}; + +static struct acpi_driver irst_driver = { + .owner = THIS_MODULE, + .name = "intel_rapid_start", + .class = "intel_rapid_start", + .ids = irst_ids, + .ops = { + .add = irst_add, + .remove = irst_remove, + }, +}; + +module_acpi_driver(irst_driver); + +MODULE_DEVICE_TABLE(acpi, irst_ids); diff --git a/drivers/platform/x86/intel-smartconnect.c b/drivers/platform/x86/intel-smartconnect.c new file mode 100644 index 00000000000..04cf5dffdfd --- /dev/null +++ b/drivers/platform/x86/intel-smartconnect.c @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Matthew Garrett <mjg59@srcf.ucam.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/acpi.h> + +MODULE_LICENSE("GPL"); + +static int smartconnect_acpi_init(struct acpi_device *acpi) +{ + unsigned long long value; + acpi_status status; + + status = acpi_evaluate_integer(acpi->handle, "GAOS", NULL, &value); + if (!ACPI_SUCCESS(status)) + return -EINVAL; + + if (value & 0x1) { + dev_info(&acpi->dev, "Disabling Intel Smart Connect\n"); + status = acpi_execute_simple_method(acpi->handle, "SAOS", 0); + } + + return 0; +} + +static const struct acpi_device_id smartconnect_ids[] = { + {"INT33A0", 0}, + {"", 0} +}; + +static struct acpi_driver smartconnect_driver = { + .owner = THIS_MODULE, + .name = "intel_smart_connect", + .class = "intel_smart_connect", + .ids = smartconnect_ids, + .ops = { + .add = smartconnect_acpi_init, + }, +}; + +module_acpi_driver(smartconnect_driver); + +MODULE_DEVICE_TABLE(acpi, smartconnect_ids); diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c new file mode 100644 index 00000000000..18dcb58ba96 --- /dev/null +++ b/drivers/platform/x86/intel_ips.c @@ -0,0 +1,1738 @@ +/* + * Copyright (c) 2009-2010 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Authors: + * Jesse Barnes <jbarnes@virtuousgeek.org> + */ + +/* + * Some Intel Ibex Peak based platforms support so-called "intelligent + * power sharing", which allows the CPU and GPU to cooperate to maximize + * performance within a given TDP (thermal design point). This driver + * performs the coordination between the CPU and GPU, monitors thermal and + * power statistics in the platform, and initializes power monitoring + * hardware. It also provides a few tunables to control behavior. Its + * primary purpose is to safely allow CPU and GPU turbo modes to be enabled + * by tracking power and thermal budget; secondarily it can boost turbo + * performance by allocating more power or thermal budget to the CPU or GPU + * based on available headroom and activity. + * + * The basic algorithm is driven by a 5s moving average of tempurature. If + * thermal headroom is available, the CPU and/or GPU power clamps may be + * adjusted upwards. If we hit the thermal ceiling or a thermal trigger, + * we scale back the clamp. Aside from trigger events (when we're critically + * close or over our TDP) we don't adjust the clamps more than once every + * five seconds. + * + * The thermal device (device 31, function 6) has a set of registers that + * are updated by the ME firmware. The ME should also take the clamp values + * written to those registers and write them to the CPU, but we currently + * bypass that functionality and write the CPU MSR directly. + * + * UNSUPPORTED: + * - dual MCP configs + * + * TODO: + * - handle CPU hotplug + * - provide turbo enable/disable api + * + * Related documents: + * - CDI 403777, 403778 - Auburndale EDS vol 1 & 2 + * - CDI 401376 - Ibex Peak EDS + * - ref 26037, 26641 - IPS BIOS spec + * - ref 26489 - Nehalem BIOS writer's guide + * - ref 26921 - Ibex Peak BIOS Specification + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/string.h> +#include <linux/tick.h> +#include <linux/timer.h> +#include <linux/dmi.h> +#include <drm/i915_drm.h> +#include <asm/msr.h> +#include <asm/processor.h> +#include "intel_ips.h" + +#include <asm-generic/io-64-nonatomic-lo-hi.h> + +#define PCI_DEVICE_ID_INTEL_THERMAL_SENSOR 0x3b32 + +/* + * Package level MSRs for monitor/control + */ +#define PLATFORM_INFO 0xce +#define PLATFORM_TDP (1<<29) +#define PLATFORM_RATIO (1<<28) + +#define IA32_MISC_ENABLE 0x1a0 +#define IA32_MISC_TURBO_EN (1ULL<<38) + +#define TURBO_POWER_CURRENT_LIMIT 0x1ac +#define TURBO_TDC_OVR_EN (1UL<<31) +#define TURBO_TDC_MASK (0x000000007fff0000UL) +#define TURBO_TDC_SHIFT (16) +#define TURBO_TDP_OVR_EN (1UL<<15) +#define TURBO_TDP_MASK (0x0000000000003fffUL) + +/* + * Core/thread MSRs for monitoring + */ +#define IA32_PERF_CTL 0x199 +#define IA32_PERF_TURBO_DIS (1ULL<<32) + +/* + * Thermal PCI device regs + */ +#define THM_CFG_TBAR 0x10 +#define THM_CFG_TBAR_HI 0x14 + +#define THM_TSIU 0x00 +#define THM_TSE 0x01 +#define TSE_EN 0xb8 +#define THM_TSS 0x02 +#define THM_TSTR 0x03 +#define THM_TSTTP 0x04 +#define THM_TSCO 0x08 +#define THM_TSES 0x0c +#define THM_TSGPEN 0x0d +#define TSGPEN_HOT_LOHI (1<<1) +#define TSGPEN_CRIT_LOHI (1<<2) +#define THM_TSPC 0x0e +#define THM_PPEC 0x10 +#define THM_CTA 0x12 +#define THM_PTA 0x14 +#define PTA_SLOPE_MASK (0xff00) +#define PTA_SLOPE_SHIFT 8 +#define PTA_OFFSET_MASK (0x00ff) +#define THM_MGTA 0x16 +#define MGTA_SLOPE_MASK (0xff00) +#define MGTA_SLOPE_SHIFT 8 +#define MGTA_OFFSET_MASK (0x00ff) +#define THM_TRC 0x1a +#define TRC_CORE2_EN (1<<15) +#define TRC_THM_EN (1<<12) +#define TRC_C6_WAR (1<<8) +#define TRC_CORE1_EN (1<<7) +#define TRC_CORE_PWR (1<<6) +#define TRC_PCH_EN (1<<5) +#define TRC_MCH_EN (1<<4) +#define TRC_DIMM4 (1<<3) +#define TRC_DIMM3 (1<<2) +#define TRC_DIMM2 (1<<1) +#define TRC_DIMM1 (1<<0) +#define THM_TES 0x20 +#define THM_TEN 0x21 +#define TEN_UPDATE_EN 1 +#define THM_PSC 0x24 +#define PSC_NTG (1<<0) /* No GFX turbo support */ +#define PSC_NTPC (1<<1) /* No CPU turbo support */ +#define PSC_PP_DEF (0<<2) /* Perf policy up to driver */ +#define PSP_PP_PC (1<<2) /* BIOS prefers CPU perf */ +#define PSP_PP_BAL (2<<2) /* BIOS wants balanced perf */ +#define PSP_PP_GFX (3<<2) /* BIOS prefers GFX perf */ +#define PSP_PBRT (1<<4) /* BIOS run time support */ +#define THM_CTV1 0x30 +#define CTV_TEMP_ERROR (1<<15) +#define CTV_TEMP_MASK 0x3f +#define CTV_ +#define THM_CTV2 0x32 +#define THM_CEC 0x34 /* undocumented power accumulator in joules */ +#define THM_AE 0x3f +#define THM_HTS 0x50 /* 32 bits */ +#define HTS_PCPL_MASK (0x7fe00000) +#define HTS_PCPL_SHIFT 21 +#define HTS_GPL_MASK (0x001ff000) +#define HTS_GPL_SHIFT 12 +#define HTS_PP_MASK (0x00000c00) +#define HTS_PP_SHIFT 10 +#define HTS_PP_DEF 0 +#define HTS_PP_PROC 1 +#define HTS_PP_BAL 2 +#define HTS_PP_GFX 3 +#define HTS_PCTD_DIS (1<<9) +#define HTS_GTD_DIS (1<<8) +#define HTS_PTL_MASK (0x000000fe) +#define HTS_PTL_SHIFT 1 +#define HTS_NVV (1<<0) +#define THM_HTSHI 0x54 /* 16 bits */ +#define HTS2_PPL_MASK (0x03ff) +#define HTS2_PRST_MASK (0x3c00) +#define HTS2_PRST_SHIFT 10 +#define HTS2_PRST_UNLOADED 0 +#define HTS2_PRST_RUNNING 1 +#define HTS2_PRST_TDISOP 2 /* turbo disabled due to power */ +#define HTS2_PRST_TDISHT 3 /* turbo disabled due to high temp */ +#define HTS2_PRST_TDISUSR 4 /* user disabled turbo */ +#define HTS2_PRST_TDISPLAT 5 /* platform disabled turbo */ +#define HTS2_PRST_TDISPM 6 /* power management disabled turbo */ +#define HTS2_PRST_TDISERR 7 /* some kind of error disabled turbo */ +#define THM_PTL 0x56 +#define THM_MGTV 0x58 +#define TV_MASK 0x000000000000ff00 +#define TV_SHIFT 8 +#define THM_PTV 0x60 +#define PTV_MASK 0x00ff +#define THM_MMGPC 0x64 +#define THM_MPPC 0x66 +#define THM_MPCPC 0x68 +#define THM_TSPIEN 0x82 +#define TSPIEN_AUX_LOHI (1<<0) +#define TSPIEN_HOT_LOHI (1<<1) +#define TSPIEN_CRIT_LOHI (1<<2) +#define TSPIEN_AUX2_LOHI (1<<3) +#define THM_TSLOCK 0x83 +#define THM_ATR 0x84 +#define THM_TOF 0x87 +#define THM_STS 0x98 +#define STS_PCPL_MASK (0x7fe00000) +#define STS_PCPL_SHIFT 21 +#define STS_GPL_MASK (0x001ff000) +#define STS_GPL_SHIFT 12 +#define STS_PP_MASK (0x00000c00) +#define STS_PP_SHIFT 10 +#define STS_PP_DEF 0 +#define STS_PP_PROC 1 +#define STS_PP_BAL 2 +#define STS_PP_GFX 3 +#define STS_PCTD_DIS (1<<9) +#define STS_GTD_DIS (1<<8) +#define STS_PTL_MASK (0x000000fe) +#define STS_PTL_SHIFT 1 +#define STS_NVV (1<<0) +#define THM_SEC 0x9c +#define SEC_ACK (1<<0) +#define THM_TC3 0xa4 +#define THM_TC1 0xa8 +#define STS_PPL_MASK (0x0003ff00) +#define STS_PPL_SHIFT 16 +#define THM_TC2 0xac +#define THM_DTV 0xb0 +#define THM_ITV 0xd8 +#define ITV_ME_SEQNO_MASK 0x00ff0000 /* ME should update every ~200ms */ +#define ITV_ME_SEQNO_SHIFT (16) +#define ITV_MCH_TEMP_MASK 0x0000ff00 +#define ITV_MCH_TEMP_SHIFT (8) +#define ITV_PCH_TEMP_MASK 0x000000ff + +#define thm_readb(off) readb(ips->regmap + (off)) +#define thm_readw(off) readw(ips->regmap + (off)) +#define thm_readl(off) readl(ips->regmap + (off)) +#define thm_readq(off) readq(ips->regmap + (off)) + +#define thm_writeb(off, val) writeb((val), ips->regmap + (off)) +#define thm_writew(off, val) writew((val), ips->regmap + (off)) +#define thm_writel(off, val) writel((val), ips->regmap + (off)) + +static const int IPS_ADJUST_PERIOD = 5000; /* ms */ +static bool late_i915_load = false; + +/* For initial average collection */ +static const int IPS_SAMPLE_PERIOD = 200; /* ms */ +static const int IPS_SAMPLE_WINDOW = 5000; /* 5s moving window of samples */ +#define IPS_SAMPLE_COUNT (IPS_SAMPLE_WINDOW / IPS_SAMPLE_PERIOD) + +/* Per-SKU limits */ +struct ips_mcp_limits { + int cpu_family; + int cpu_model; /* includes extended model... */ + int mcp_power_limit; /* mW units */ + int core_power_limit; + int mch_power_limit; + int core_temp_limit; /* degrees C */ + int mch_temp_limit; +}; + +/* Max temps are -10 degrees C to avoid PROCHOT# */ + +struct ips_mcp_limits ips_sv_limits = { + .mcp_power_limit = 35000, + .core_power_limit = 29000, + .mch_power_limit = 20000, + .core_temp_limit = 95, + .mch_temp_limit = 90 +}; + +struct ips_mcp_limits ips_lv_limits = { + .mcp_power_limit = 25000, + .core_power_limit = 21000, + .mch_power_limit = 13000, + .core_temp_limit = 95, + .mch_temp_limit = 90 +}; + +struct ips_mcp_limits ips_ulv_limits = { + .mcp_power_limit = 18000, + .core_power_limit = 14000, + .mch_power_limit = 11000, + .core_temp_limit = 95, + .mch_temp_limit = 90 +}; + +struct ips_driver { + struct pci_dev *dev; + void *regmap; + struct task_struct *monitor; + struct task_struct *adjust; + struct dentry *debug_root; + + /* Average CPU core temps (all averages in .01 degrees C for precision) */ + u16 ctv1_avg_temp; + u16 ctv2_avg_temp; + /* GMCH average */ + u16 mch_avg_temp; + /* Average for the CPU (both cores?) */ + u16 mcp_avg_temp; + /* Average power consumption (in mW) */ + u32 cpu_avg_power; + u32 mch_avg_power; + + /* Offset values */ + u16 cta_val; + u16 pta_val; + u16 mgta_val; + + /* Maximums & prefs, protected by turbo status lock */ + spinlock_t turbo_status_lock; + u16 mcp_temp_limit; + u16 mcp_power_limit; + u16 core_power_limit; + u16 mch_power_limit; + bool cpu_turbo_enabled; + bool __cpu_turbo_on; + bool gpu_turbo_enabled; + bool __gpu_turbo_on; + bool gpu_preferred; + bool poll_turbo_status; + bool second_cpu; + bool turbo_toggle_allowed; + struct ips_mcp_limits *limits; + + /* Optional MCH interfaces for if i915 is in use */ + unsigned long (*read_mch_val)(void); + bool (*gpu_raise)(void); + bool (*gpu_lower)(void); + bool (*gpu_busy)(void); + bool (*gpu_turbo_disable)(void); + + /* For restoration at unload */ + u64 orig_turbo_limit; + u64 orig_turbo_ratios; +}; + +static bool +ips_gpu_turbo_enabled(struct ips_driver *ips); + +/** + * ips_cpu_busy - is CPU busy? + * @ips: IPS driver struct + * + * Check CPU for load to see whether we should increase its thermal budget. + * + * RETURNS: + * True if the CPU could use more power, false otherwise. + */ +static bool ips_cpu_busy(struct ips_driver *ips) +{ + if ((avenrun[0] >> FSHIFT) > 1) + return true; + + return false; +} + +/** + * ips_cpu_raise - raise CPU power clamp + * @ips: IPS driver struct + * + * Raise the CPU power clamp by %IPS_CPU_STEP, in accordance with TDP for + * this platform. + * + * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR upwards (as + * long as we haven't hit the TDP limit for the SKU). + */ +static void ips_cpu_raise(struct ips_driver *ips) +{ + u64 turbo_override; + u16 cur_tdp_limit, new_tdp_limit; + + if (!ips->cpu_turbo_enabled) + return; + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + cur_tdp_limit = turbo_override & TURBO_TDP_MASK; + new_tdp_limit = cur_tdp_limit + 8; /* 1W increase */ + + /* Clamp to SKU TDP limit */ + if (((new_tdp_limit * 10) / 8) > ips->core_power_limit) + new_tdp_limit = cur_tdp_limit; + + thm_writew(THM_MPCPC, (new_tdp_limit * 10) / 8); + + turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN; + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + turbo_override &= ~TURBO_TDP_MASK; + turbo_override |= new_tdp_limit; + + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); +} + +/** + * ips_cpu_lower - lower CPU power clamp + * @ips: IPS driver struct + * + * Lower CPU power clamp b %IPS_CPU_STEP if possible. + * + * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR down, going + * as low as the platform limits will allow (though we could go lower there + * wouldn't be much point). + */ +static void ips_cpu_lower(struct ips_driver *ips) +{ + u64 turbo_override; + u16 cur_limit, new_limit; + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + cur_limit = turbo_override & TURBO_TDP_MASK; + new_limit = cur_limit - 8; /* 1W decrease */ + + /* Clamp to SKU TDP limit */ + if (new_limit < (ips->orig_turbo_limit & TURBO_TDP_MASK)) + new_limit = ips->orig_turbo_limit & TURBO_TDP_MASK; + + thm_writew(THM_MPCPC, (new_limit * 10) / 8); + + turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN; + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + turbo_override &= ~TURBO_TDP_MASK; + turbo_override |= new_limit; + + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); +} + +/** + * do_enable_cpu_turbo - internal turbo enable function + * @data: unused + * + * Internal function for actually updating MSRs. When we enable/disable + * turbo, we need to do it on each CPU; this function is the one called + * by on_each_cpu() when needed. + */ +static void do_enable_cpu_turbo(void *data) +{ + u64 perf_ctl; + + rdmsrl(IA32_PERF_CTL, perf_ctl); + if (perf_ctl & IA32_PERF_TURBO_DIS) { + perf_ctl &= ~IA32_PERF_TURBO_DIS; + wrmsrl(IA32_PERF_CTL, perf_ctl); + } +} + +/** + * ips_enable_cpu_turbo - enable turbo mode on all CPUs + * @ips: IPS driver struct + * + * Enable turbo mode by clearing the disable bit in IA32_PERF_CTL on + * all logical threads. + */ +static void ips_enable_cpu_turbo(struct ips_driver *ips) +{ + /* Already on, no need to mess with MSRs */ + if (ips->__cpu_turbo_on) + return; + + if (ips->turbo_toggle_allowed) + on_each_cpu(do_enable_cpu_turbo, ips, 1); + + ips->__cpu_turbo_on = true; +} + +/** + * do_disable_cpu_turbo - internal turbo disable function + * @data: unused + * + * Internal function for actually updating MSRs. When we enable/disable + * turbo, we need to do it on each CPU; this function is the one called + * by on_each_cpu() when needed. + */ +static void do_disable_cpu_turbo(void *data) +{ + u64 perf_ctl; + + rdmsrl(IA32_PERF_CTL, perf_ctl); + if (!(perf_ctl & IA32_PERF_TURBO_DIS)) { + perf_ctl |= IA32_PERF_TURBO_DIS; + wrmsrl(IA32_PERF_CTL, perf_ctl); + } +} + +/** + * ips_disable_cpu_turbo - disable turbo mode on all CPUs + * @ips: IPS driver struct + * + * Disable turbo mode by setting the disable bit in IA32_PERF_CTL on + * all logical threads. + */ +static void ips_disable_cpu_turbo(struct ips_driver *ips) +{ + /* Already off, leave it */ + if (!ips->__cpu_turbo_on) + return; + + if (ips->turbo_toggle_allowed) + on_each_cpu(do_disable_cpu_turbo, ips, 1); + + ips->__cpu_turbo_on = false; +} + +/** + * ips_gpu_busy - is GPU busy? + * @ips: IPS driver struct + * + * Check GPU for load to see whether we should increase its thermal budget. + * We need to call into the i915 driver in this case. + * + * RETURNS: + * True if the GPU could use more power, false otherwise. + */ +static bool ips_gpu_busy(struct ips_driver *ips) +{ + if (!ips_gpu_turbo_enabled(ips)) + return false; + + return ips->gpu_busy(); +} + +/** + * ips_gpu_raise - raise GPU power clamp + * @ips: IPS driver struct + * + * Raise the GPU frequency/power if possible. We need to call into the + * i915 driver in this case. + */ +static void ips_gpu_raise(struct ips_driver *ips) +{ + if (!ips_gpu_turbo_enabled(ips)) + return; + + if (!ips->gpu_raise()) + ips->gpu_turbo_enabled = false; + + return; +} + +/** + * ips_gpu_lower - lower GPU power clamp + * @ips: IPS driver struct + * + * Lower GPU frequency/power if possible. Need to call i915. + */ +static void ips_gpu_lower(struct ips_driver *ips) +{ + if (!ips_gpu_turbo_enabled(ips)) + return; + + if (!ips->gpu_lower()) + ips->gpu_turbo_enabled = false; + + return; +} + +/** + * ips_enable_gpu_turbo - notify the gfx driver turbo is available + * @ips: IPS driver struct + * + * Call into the graphics driver indicating that it can safely use + * turbo mode. + */ +static void ips_enable_gpu_turbo(struct ips_driver *ips) +{ + if (ips->__gpu_turbo_on) + return; + ips->__gpu_turbo_on = true; +} + +/** + * ips_disable_gpu_turbo - notify the gfx driver to disable turbo mode + * @ips: IPS driver struct + * + * Request that the graphics driver disable turbo mode. + */ +static void ips_disable_gpu_turbo(struct ips_driver *ips) +{ + /* Avoid calling i915 if turbo is already disabled */ + if (!ips->__gpu_turbo_on) + return; + + if (!ips->gpu_turbo_disable()) + dev_err(&ips->dev->dev, "failed to disable graphis turbo\n"); + else + ips->__gpu_turbo_on = false; +} + +/** + * mcp_exceeded - check whether we're outside our thermal & power limits + * @ips: IPS driver struct + * + * Check whether the MCP is over its thermal or power budget. + */ +static bool mcp_exceeded(struct ips_driver *ips) +{ + unsigned long flags; + bool ret = false; + u32 temp_limit; + u32 avg_power; + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + + temp_limit = ips->mcp_temp_limit * 100; + if (ips->mcp_avg_temp > temp_limit) + ret = true; + + avg_power = ips->cpu_avg_power + ips->mch_avg_power; + if (avg_power > ips->mcp_power_limit) + ret = true; + + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + return ret; +} + +/** + * cpu_exceeded - check whether a CPU core is outside its limits + * @ips: IPS driver struct + * @cpu: CPU number to check + * + * Check a given CPU's average temp or power is over its limit. + */ +static bool cpu_exceeded(struct ips_driver *ips, int cpu) +{ + unsigned long flags; + int avg; + bool ret = false; + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + avg = cpu ? ips->ctv2_avg_temp : ips->ctv1_avg_temp; + if (avg > (ips->limits->core_temp_limit * 100)) + ret = true; + if (ips->cpu_avg_power > ips->core_power_limit * 100) + ret = true; + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + if (ret) + dev_info(&ips->dev->dev, + "CPU power or thermal limit exceeded\n"); + + return ret; +} + +/** + * mch_exceeded - check whether the GPU is over budget + * @ips: IPS driver struct + * + * Check the MCH temp & power against their maximums. + */ +static bool mch_exceeded(struct ips_driver *ips) +{ + unsigned long flags; + bool ret = false; + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + if (ips->mch_avg_temp > (ips->limits->mch_temp_limit * 100)) + ret = true; + if (ips->mch_avg_power > ips->mch_power_limit) + ret = true; + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + return ret; +} + +/** + * verify_limits - verify BIOS provided limits + * @ips: IPS structure + * + * BIOS can optionally provide non-default limits for power and temp. Check + * them here and use the defaults if the BIOS values are not provided or + * are otherwise unusable. + */ +static void verify_limits(struct ips_driver *ips) +{ + if (ips->mcp_power_limit < ips->limits->mcp_power_limit || + ips->mcp_power_limit > 35000) + ips->mcp_power_limit = ips->limits->mcp_power_limit; + + if (ips->mcp_temp_limit < ips->limits->core_temp_limit || + ips->mcp_temp_limit < ips->limits->mch_temp_limit || + ips->mcp_temp_limit > 150) + ips->mcp_temp_limit = min(ips->limits->core_temp_limit, + ips->limits->mch_temp_limit); +} + +/** + * update_turbo_limits - get various limits & settings from regs + * @ips: IPS driver struct + * + * Update the IPS power & temp limits, along with turbo enable flags, + * based on latest register contents. + * + * Used at init time and for runtime BIOS support, which requires polling + * the regs for updates (as a result of AC->DC transition for example). + * + * LOCKING: + * Caller must hold turbo_status_lock (outside of init) + */ +static void update_turbo_limits(struct ips_driver *ips) +{ + u32 hts = thm_readl(THM_HTS); + + ips->cpu_turbo_enabled = !(hts & HTS_PCTD_DIS); + /* + * Disable turbo for now, until we can figure out why the power figures + * are wrong + */ + ips->cpu_turbo_enabled = false; + + if (ips->gpu_busy) + ips->gpu_turbo_enabled = !(hts & HTS_GTD_DIS); + + ips->core_power_limit = thm_readw(THM_MPCPC); + ips->mch_power_limit = thm_readw(THM_MMGPC); + ips->mcp_temp_limit = thm_readw(THM_PTL); + ips->mcp_power_limit = thm_readw(THM_MPPC); + + verify_limits(ips); + /* Ignore BIOS CPU vs GPU pref */ +} + +/** + * ips_adjust - adjust power clamp based on thermal state + * @data: ips driver structure + * + * Wake up every 5s or so and check whether we should adjust the power clamp. + * Check CPU and GPU load to determine which needs adjustment. There are + * several things to consider here: + * - do we need to adjust up or down? + * - is CPU busy? + * - is GPU busy? + * - is CPU in turbo? + * - is GPU in turbo? + * - is CPU or GPU preferred? (CPU is default) + * + * So, given the above, we do the following: + * - up (TDP available) + * - CPU not busy, GPU not busy - nothing + * - CPU busy, GPU not busy - adjust CPU up + * - CPU not busy, GPU busy - adjust GPU up + * - CPU busy, GPU busy - adjust preferred unit up, taking headroom from + * non-preferred unit if necessary + * - down (at TDP limit) + * - adjust both CPU and GPU down if possible + * + cpu+ gpu+ cpu+gpu- cpu-gpu+ cpu-gpu- +cpu < gpu < cpu+gpu+ cpu+ gpu+ nothing +cpu < gpu >= cpu+gpu-(mcp<) cpu+gpu-(mcp<) gpu- gpu- +cpu >= gpu < cpu-gpu+(mcp<) cpu- cpu-gpu+(mcp<) cpu- +cpu >= gpu >= cpu-gpu- cpu-gpu- cpu-gpu- cpu-gpu- + * + */ +static int ips_adjust(void *data) +{ + struct ips_driver *ips = data; + unsigned long flags; + + dev_dbg(&ips->dev->dev, "starting ips-adjust thread\n"); + + /* + * Adjust CPU and GPU clamps every 5s if needed. Doing it more + * often isn't recommended due to ME interaction. + */ + do { + bool cpu_busy = ips_cpu_busy(ips); + bool gpu_busy = ips_gpu_busy(ips); + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + if (ips->poll_turbo_status) + update_turbo_limits(ips); + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + /* Update turbo status if necessary */ + if (ips->cpu_turbo_enabled) + ips_enable_cpu_turbo(ips); + else + ips_disable_cpu_turbo(ips); + + if (ips->gpu_turbo_enabled) + ips_enable_gpu_turbo(ips); + else + ips_disable_gpu_turbo(ips); + + /* We're outside our comfort zone, crank them down */ + if (mcp_exceeded(ips)) { + ips_cpu_lower(ips); + ips_gpu_lower(ips); + goto sleep; + } + + if (!cpu_exceeded(ips, 0) && cpu_busy) + ips_cpu_raise(ips); + else + ips_cpu_lower(ips); + + if (!mch_exceeded(ips) && gpu_busy) + ips_gpu_raise(ips); + else + ips_gpu_lower(ips); + +sleep: + schedule_timeout_interruptible(msecs_to_jiffies(IPS_ADJUST_PERIOD)); + } while (!kthread_should_stop()); + + dev_dbg(&ips->dev->dev, "ips-adjust thread stopped\n"); + + return 0; +} + +/* + * Helpers for reading out temp/power values and calculating their + * averages for the decision making and monitoring functions. + */ + +static u16 calc_avg_temp(struct ips_driver *ips, u16 *array) +{ + u64 total = 0; + int i; + u16 avg; + + for (i = 0; i < IPS_SAMPLE_COUNT; i++) + total += (u64)(array[i] * 100); + + do_div(total, IPS_SAMPLE_COUNT); + + avg = (u16)total; + + return avg; +} + +static u16 read_mgtv(struct ips_driver *ips) +{ + u16 ret; + u64 slope, offset; + u64 val; + + val = thm_readq(THM_MGTV); + val = (val & TV_MASK) >> TV_SHIFT; + + slope = offset = thm_readw(THM_MGTA); + slope = (slope & MGTA_SLOPE_MASK) >> MGTA_SLOPE_SHIFT; + offset = offset & MGTA_OFFSET_MASK; + + ret = ((val * slope + 0x40) >> 7) + offset; + + return 0; /* MCH temp reporting buggy */ +} + +static u16 read_ptv(struct ips_driver *ips) +{ + u16 val, slope, offset; + + slope = (ips->pta_val & PTA_SLOPE_MASK) >> PTA_SLOPE_SHIFT; + offset = ips->pta_val & PTA_OFFSET_MASK; + + val = thm_readw(THM_PTV) & PTV_MASK; + + return val; +} + +static u16 read_ctv(struct ips_driver *ips, int cpu) +{ + int reg = cpu ? THM_CTV2 : THM_CTV1; + u16 val; + + val = thm_readw(reg); + if (!(val & CTV_TEMP_ERROR)) + val = (val) >> 6; /* discard fractional component */ + else + val = 0; + + return val; +} + +static u32 get_cpu_power(struct ips_driver *ips, u32 *last, int period) +{ + u32 val; + u32 ret; + + /* + * CEC is in joules/65535. Take difference over time to + * get watts. + */ + val = thm_readl(THM_CEC); + + /* period is in ms and we want mW */ + ret = (((val - *last) * 1000) / period); + ret = (ret * 1000) / 65535; + *last = val; + + return 0; +} + +static const u16 temp_decay_factor = 2; +static u16 update_average_temp(u16 avg, u16 val) +{ + u16 ret; + + /* Multiply by 100 for extra precision */ + ret = (val * 100 / temp_decay_factor) + + (((temp_decay_factor - 1) * avg) / temp_decay_factor); + return ret; +} + +static const u16 power_decay_factor = 2; +static u16 update_average_power(u32 avg, u32 val) +{ + u32 ret; + + ret = (val / power_decay_factor) + + (((power_decay_factor - 1) * avg) / power_decay_factor); + + return ret; +} + +static u32 calc_avg_power(struct ips_driver *ips, u32 *array) +{ + u64 total = 0; + u32 avg; + int i; + + for (i = 0; i < IPS_SAMPLE_COUNT; i++) + total += array[i]; + + do_div(total, IPS_SAMPLE_COUNT); + avg = (u32)total; + + return avg; +} + +static void monitor_timeout(unsigned long arg) +{ + wake_up_process((struct task_struct *)arg); +} + +/** + * ips_monitor - temp/power monitoring thread + * @data: ips driver structure + * + * This is the main function for the IPS driver. It monitors power and + * tempurature in the MCP and adjusts CPU and GPU power clams accordingly. + * + * We keep a 5s moving average of power consumption and tempurature. Using + * that data, along with CPU vs GPU preference, we adjust the power clamps + * up or down. + */ +static int ips_monitor(void *data) +{ + struct ips_driver *ips = data; + struct timer_list timer; + unsigned long seqno_timestamp, expire, last_msecs, last_sample_period; + int i; + u32 *cpu_samples, *mchp_samples, old_cpu_power; + u16 *mcp_samples, *ctv1_samples, *ctv2_samples, *mch_samples; + u8 cur_seqno, last_seqno; + + mcp_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + ctv1_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + ctv2_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + mch_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + cpu_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL); + mchp_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL); + if (!mcp_samples || !ctv1_samples || !ctv2_samples || !mch_samples || + !cpu_samples || !mchp_samples) { + dev_err(&ips->dev->dev, + "failed to allocate sample array, ips disabled\n"); + kfree(mcp_samples); + kfree(ctv1_samples); + kfree(ctv2_samples); + kfree(mch_samples); + kfree(cpu_samples); + kfree(mchp_samples); + return -ENOMEM; + } + + last_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >> + ITV_ME_SEQNO_SHIFT; + seqno_timestamp = get_jiffies_64(); + + old_cpu_power = thm_readl(THM_CEC); + schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); + + /* Collect an initial average */ + for (i = 0; i < IPS_SAMPLE_COUNT; i++) { + u32 mchp, cpu_power; + u16 val; + + mcp_samples[i] = read_ptv(ips); + + val = read_ctv(ips, 0); + ctv1_samples[i] = val; + + val = read_ctv(ips, 1); + ctv2_samples[i] = val; + + val = read_mgtv(ips); + mch_samples[i] = val; + + cpu_power = get_cpu_power(ips, &old_cpu_power, + IPS_SAMPLE_PERIOD); + cpu_samples[i] = cpu_power; + + if (ips->read_mch_val) { + mchp = ips->read_mch_val(); + mchp_samples[i] = mchp; + } + + schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); + if (kthread_should_stop()) + break; + } + + ips->mcp_avg_temp = calc_avg_temp(ips, mcp_samples); + ips->ctv1_avg_temp = calc_avg_temp(ips, ctv1_samples); + ips->ctv2_avg_temp = calc_avg_temp(ips, ctv2_samples); + ips->mch_avg_temp = calc_avg_temp(ips, mch_samples); + ips->cpu_avg_power = calc_avg_power(ips, cpu_samples); + ips->mch_avg_power = calc_avg_power(ips, mchp_samples); + kfree(mcp_samples); + kfree(ctv1_samples); + kfree(ctv2_samples); + kfree(mch_samples); + kfree(cpu_samples); + kfree(mchp_samples); + + /* Start the adjustment thread now that we have data */ + wake_up_process(ips->adjust); + + /* + * Ok, now we have an initial avg. From here on out, we track the + * running avg using a decaying average calculation. This allows + * us to reduce the sample frequency if the CPU and GPU are idle. + */ + old_cpu_power = thm_readl(THM_CEC); + schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); + last_sample_period = IPS_SAMPLE_PERIOD; + + setup_deferrable_timer_on_stack(&timer, monitor_timeout, + (unsigned long)current); + do { + u32 cpu_val, mch_val; + u16 val; + + /* MCP itself */ + val = read_ptv(ips); + ips->mcp_avg_temp = update_average_temp(ips->mcp_avg_temp, val); + + /* Processor 0 */ + val = read_ctv(ips, 0); + ips->ctv1_avg_temp = + update_average_temp(ips->ctv1_avg_temp, val); + /* Power */ + cpu_val = get_cpu_power(ips, &old_cpu_power, + last_sample_period); + ips->cpu_avg_power = + update_average_power(ips->cpu_avg_power, cpu_val); + + if (ips->second_cpu) { + /* Processor 1 */ + val = read_ctv(ips, 1); + ips->ctv2_avg_temp = + update_average_temp(ips->ctv2_avg_temp, val); + } + + /* MCH */ + val = read_mgtv(ips); + ips->mch_avg_temp = update_average_temp(ips->mch_avg_temp, val); + /* Power */ + if (ips->read_mch_val) { + mch_val = ips->read_mch_val(); + ips->mch_avg_power = + update_average_power(ips->mch_avg_power, + mch_val); + } + + /* + * Make sure ME is updating thermal regs. + * Note: + * If it's been more than a second since the last update, + * the ME is probably hung. + */ + cur_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >> + ITV_ME_SEQNO_SHIFT; + if (cur_seqno == last_seqno && + time_after(jiffies, seqno_timestamp + HZ)) { + dev_warn(&ips->dev->dev, "ME failed to update for more than 1s, likely hung\n"); + } else { + seqno_timestamp = get_jiffies_64(); + last_seqno = cur_seqno; + } + + last_msecs = jiffies_to_msecs(jiffies); + expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD); + + __set_current_state(TASK_INTERRUPTIBLE); + mod_timer(&timer, expire); + schedule(); + + /* Calculate actual sample period for power averaging */ + last_sample_period = jiffies_to_msecs(jiffies) - last_msecs; + if (!last_sample_period) + last_sample_period = 1; + } while (!kthread_should_stop()); + + del_timer_sync(&timer); + destroy_timer_on_stack(&timer); + + dev_dbg(&ips->dev->dev, "ips-monitor thread stopped\n"); + + return 0; +} + +#if 0 +#define THM_DUMPW(reg) \ + { \ + u16 val = thm_readw(reg); \ + dev_dbg(&ips->dev->dev, #reg ": 0x%04x\n", val); \ + } +#define THM_DUMPL(reg) \ + { \ + u32 val = thm_readl(reg); \ + dev_dbg(&ips->dev->dev, #reg ": 0x%08x\n", val); \ + } +#define THM_DUMPQ(reg) \ + { \ + u64 val = thm_readq(reg); \ + dev_dbg(&ips->dev->dev, #reg ": 0x%016x\n", val); \ + } + +static void dump_thermal_info(struct ips_driver *ips) +{ + u16 ptl; + + ptl = thm_readw(THM_PTL); + dev_dbg(&ips->dev->dev, "Processor temp limit: %d\n", ptl); + + THM_DUMPW(THM_CTA); + THM_DUMPW(THM_TRC); + THM_DUMPW(THM_CTV1); + THM_DUMPL(THM_STS); + THM_DUMPW(THM_PTV); + THM_DUMPQ(THM_MGTV); +} +#endif + +/** + * ips_irq_handler - handle temperature triggers and other IPS events + * @irq: irq number + * @arg: unused + * + * Handle temperature limit trigger events, generally by lowering the clamps. + * If we're at a critical limit, we clamp back to the lowest possible value + * to prevent emergency shutdown. + */ +static irqreturn_t ips_irq_handler(int irq, void *arg) +{ + struct ips_driver *ips = arg; + u8 tses = thm_readb(THM_TSES); + u8 tes = thm_readb(THM_TES); + + if (!tses && !tes) + return IRQ_NONE; + + dev_info(&ips->dev->dev, "TSES: 0x%02x\n", tses); + dev_info(&ips->dev->dev, "TES: 0x%02x\n", tes); + + /* STS update from EC? */ + if (tes & 1) { + u32 sts, tc1; + + sts = thm_readl(THM_STS); + tc1 = thm_readl(THM_TC1); + + if (sts & STS_NVV) { + spin_lock(&ips->turbo_status_lock); + ips->core_power_limit = (sts & STS_PCPL_MASK) >> + STS_PCPL_SHIFT; + ips->mch_power_limit = (sts & STS_GPL_MASK) >> + STS_GPL_SHIFT; + /* ignore EC CPU vs GPU pref */ + ips->cpu_turbo_enabled = !(sts & STS_PCTD_DIS); + /* + * Disable turbo for now, until we can figure + * out why the power figures are wrong + */ + ips->cpu_turbo_enabled = false; + if (ips->gpu_busy) + ips->gpu_turbo_enabled = !(sts & STS_GTD_DIS); + ips->mcp_temp_limit = (sts & STS_PTL_MASK) >> + STS_PTL_SHIFT; + ips->mcp_power_limit = (tc1 & STS_PPL_MASK) >> + STS_PPL_SHIFT; + verify_limits(ips); + spin_unlock(&ips->turbo_status_lock); + + thm_writeb(THM_SEC, SEC_ACK); + } + thm_writeb(THM_TES, tes); + } + + /* Thermal trip */ + if (tses) { + dev_warn(&ips->dev->dev, + "thermal trip occurred, tses: 0x%04x\n", tses); + thm_writeb(THM_TSES, tses); + } + + return IRQ_HANDLED; +} + +#ifndef CONFIG_DEBUG_FS +static void ips_debugfs_init(struct ips_driver *ips) { return; } +static void ips_debugfs_cleanup(struct ips_driver *ips) { return; } +#else + +/* Expose current state and limits in debugfs if possible */ + +struct ips_debugfs_node { + struct ips_driver *ips; + char *name; + int (*show)(struct seq_file *m, void *data); +}; + +static int show_cpu_temp(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%d.%02d\n", ips->ctv1_avg_temp / 100, + ips->ctv1_avg_temp % 100); + + return 0; +} + +static int show_cpu_power(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%dmW\n", ips->cpu_avg_power); + + return 0; +} + +static int show_cpu_clamp(struct seq_file *m, void *data) +{ + u64 turbo_override; + int tdp, tdc; + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + tdp = (int)(turbo_override & TURBO_TDP_MASK); + tdc = (int)((turbo_override & TURBO_TDC_MASK) >> TURBO_TDC_SHIFT); + + /* Convert to .1W/A units */ + tdp = tdp * 10 / 8; + tdc = tdc * 10 / 8; + + /* Watts Amperes */ + seq_printf(m, "%d.%dW %d.%dA\n", tdp / 10, tdp % 10, + tdc / 10, tdc % 10); + + return 0; +} + +static int show_mch_temp(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%d.%02d\n", ips->mch_avg_temp / 100, + ips->mch_avg_temp % 100); + + return 0; +} + +static int show_mch_power(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%dmW\n", ips->mch_avg_power); + + return 0; +} + +static struct ips_debugfs_node ips_debug_files[] = { + { NULL, "cpu_temp", show_cpu_temp }, + { NULL, "cpu_power", show_cpu_power }, + { NULL, "cpu_clamp", show_cpu_clamp }, + { NULL, "mch_temp", show_mch_temp }, + { NULL, "mch_power", show_mch_power }, +}; + +static int ips_debugfs_open(struct inode *inode, struct file *file) +{ + struct ips_debugfs_node *node = inode->i_private; + + return single_open(file, node->show, node->ips); +} + +static const struct file_operations ips_debugfs_ops = { + .owner = THIS_MODULE, + .open = ips_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void ips_debugfs_cleanup(struct ips_driver *ips) +{ + if (ips->debug_root) + debugfs_remove_recursive(ips->debug_root); + return; +} + +static void ips_debugfs_init(struct ips_driver *ips) +{ + int i; + + ips->debug_root = debugfs_create_dir("ips", NULL); + if (!ips->debug_root) { + dev_err(&ips->dev->dev, + "failed to create debugfs entries: %ld\n", + PTR_ERR(ips->debug_root)); + return; + } + + for (i = 0; i < ARRAY_SIZE(ips_debug_files); i++) { + struct dentry *ent; + struct ips_debugfs_node *node = &ips_debug_files[i]; + + node->ips = ips; + ent = debugfs_create_file(node->name, S_IFREG | S_IRUGO, + ips->debug_root, node, + &ips_debugfs_ops); + if (!ent) { + dev_err(&ips->dev->dev, + "failed to create debug file: %ld\n", + PTR_ERR(ent)); + goto err_cleanup; + } + } + + return; + +err_cleanup: + ips_debugfs_cleanup(ips); + return; +} +#endif /* CONFIG_DEBUG_FS */ + +/** + * ips_detect_cpu - detect whether CPU supports IPS + * + * Walk our list and see if we're on a supported CPU. If we find one, + * return the limits for it. + */ +static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) +{ + u64 turbo_power, misc_en; + struct ips_mcp_limits *limits = NULL; + u16 tdp; + + if (!(boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 37)) { + dev_info(&ips->dev->dev, "Non-IPS CPU detected.\n"); + goto out; + } + + rdmsrl(IA32_MISC_ENABLE, misc_en); + /* + * If the turbo enable bit isn't set, we shouldn't try to enable/disable + * turbo manually or we'll get an illegal MSR access, even though + * turbo will still be available. + */ + if (misc_en & IA32_MISC_TURBO_EN) + ips->turbo_toggle_allowed = true; + else + ips->turbo_toggle_allowed = false; + + if (strstr(boot_cpu_data.x86_model_id, "CPU M")) + limits = &ips_sv_limits; + else if (strstr(boot_cpu_data.x86_model_id, "CPU L")) + limits = &ips_lv_limits; + else if (strstr(boot_cpu_data.x86_model_id, "CPU U")) + limits = &ips_ulv_limits; + else { + dev_info(&ips->dev->dev, "No CPUID match found.\n"); + goto out; + } + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_power); + tdp = turbo_power & TURBO_TDP_MASK; + + /* Sanity check TDP against CPU */ + if (limits->core_power_limit != (tdp / 8) * 1000) { + dev_info(&ips->dev->dev, "CPU TDP doesn't match expected value (found %d, expected %d)\n", + tdp / 8, limits->core_power_limit / 1000); + limits->core_power_limit = (tdp / 8) * 1000; + } + +out: + return limits; +} + +/** + * ips_get_i915_syms - try to get GPU control methods from i915 driver + * @ips: IPS driver + * + * The i915 driver exports several interfaces to allow the IPS driver to + * monitor and control graphics turbo mode. If we can find them, we can + * enable graphics turbo, otherwise we must disable it to avoid exceeding + * thermal and power limits in the MCP. + */ +static bool ips_get_i915_syms(struct ips_driver *ips) +{ + ips->read_mch_val = symbol_get(i915_read_mch_val); + if (!ips->read_mch_val) + goto out_err; + ips->gpu_raise = symbol_get(i915_gpu_raise); + if (!ips->gpu_raise) + goto out_put_mch; + ips->gpu_lower = symbol_get(i915_gpu_lower); + if (!ips->gpu_lower) + goto out_put_raise; + ips->gpu_busy = symbol_get(i915_gpu_busy); + if (!ips->gpu_busy) + goto out_put_lower; + ips->gpu_turbo_disable = symbol_get(i915_gpu_turbo_disable); + if (!ips->gpu_turbo_disable) + goto out_put_busy; + + return true; + +out_put_busy: + symbol_put(i915_gpu_busy); +out_put_lower: + symbol_put(i915_gpu_lower); +out_put_raise: + symbol_put(i915_gpu_raise); +out_put_mch: + symbol_put(i915_read_mch_val); +out_err: + return false; +} + +static bool +ips_gpu_turbo_enabled(struct ips_driver *ips) +{ + if (!ips->gpu_busy && late_i915_load) { + if (ips_get_i915_syms(ips)) { + dev_info(&ips->dev->dev, + "i915 driver attached, reenabling gpu turbo\n"); + ips->gpu_turbo_enabled = !(thm_readl(THM_HTS) & HTS_GTD_DIS); + } + } + + return ips->gpu_turbo_enabled; +} + +void +ips_link_to_i915_driver(void) +{ + /* We can't cleanly get at the various ips_driver structs from + * this caller (the i915 driver), so just set a flag saying + * that it's time to try getting the symbols again. + */ + late_i915_load = true; +} +EXPORT_SYMBOL_GPL(ips_link_to_i915_driver); + +static DEFINE_PCI_DEVICE_TABLE(ips_id_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_THERMAL_SENSOR), }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, ips_id_table); + +static int ips_blacklist_callback(const struct dmi_system_id *id) +{ + pr_info("Blacklisted intel_ips for %s\n", id->ident); + return 1; +} + +static const struct dmi_system_id ips_blacklist[] = { + { + .callback = ips_blacklist_callback, + .ident = "HP ProBook", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook"), + }, + }, + { } /* terminating entry */ +}; + +static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + u64 platform_info; + struct ips_driver *ips; + u32 hts; + int ret = 0; + u16 htshi, trc, trc_required_mask; + u8 tse; + + if (dmi_check_system(ips_blacklist)) + return -ENODEV; + + ips = kzalloc(sizeof(struct ips_driver), GFP_KERNEL); + if (!ips) + return -ENOMEM; + + pci_set_drvdata(dev, ips); + ips->dev = dev; + + ips->limits = ips_detect_cpu(ips); + if (!ips->limits) { + dev_info(&dev->dev, "IPS not supported on this CPU\n"); + ret = -ENXIO; + goto error_free; + } + + spin_lock_init(&ips->turbo_status_lock); + + ret = pci_enable_device(dev); + if (ret) { + dev_err(&dev->dev, "can't enable PCI device, aborting\n"); + goto error_free; + } + + if (!pci_resource_start(dev, 0)) { + dev_err(&dev->dev, "TBAR not assigned, aborting\n"); + ret = -ENXIO; + goto error_free; + } + + ret = pci_request_regions(dev, "ips thermal sensor"); + if (ret) { + dev_err(&dev->dev, "thermal resource busy, aborting\n"); + goto error_free; + } + + + ips->regmap = ioremap(pci_resource_start(dev, 0), + pci_resource_len(dev, 0)); + if (!ips->regmap) { + dev_err(&dev->dev, "failed to map thermal regs, aborting\n"); + ret = -EBUSY; + goto error_release; + } + + tse = thm_readb(THM_TSE); + if (tse != TSE_EN) { + dev_err(&dev->dev, "thermal device not enabled (0x%02x), aborting\n", tse); + ret = -ENXIO; + goto error_unmap; + } + + trc = thm_readw(THM_TRC); + trc_required_mask = TRC_CORE1_EN | TRC_CORE_PWR | TRC_MCH_EN; + if ((trc & trc_required_mask) != trc_required_mask) { + dev_err(&dev->dev, "thermal reporting for required devices not enabled, aborting\n"); + ret = -ENXIO; + goto error_unmap; + } + + if (trc & TRC_CORE2_EN) + ips->second_cpu = true; + + update_turbo_limits(ips); + dev_dbg(&dev->dev, "max cpu power clamp: %dW\n", + ips->mcp_power_limit / 10); + dev_dbg(&dev->dev, "max core power clamp: %dW\n", + ips->core_power_limit / 10); + /* BIOS may update limits at runtime */ + if (thm_readl(THM_PSC) & PSP_PBRT) + ips->poll_turbo_status = true; + + if (!ips_get_i915_syms(ips)) { + dev_info(&dev->dev, "failed to get i915 symbols, graphics turbo disabled until i915 loads\n"); + ips->gpu_turbo_enabled = false; + } else { + dev_dbg(&dev->dev, "graphics turbo enabled\n"); + ips->gpu_turbo_enabled = true; + } + + /* + * Check PLATFORM_INFO MSR to make sure this chip is + * turbo capable. + */ + rdmsrl(PLATFORM_INFO, platform_info); + if (!(platform_info & PLATFORM_TDP)) { + dev_err(&dev->dev, "platform indicates TDP override unavailable, aborting\n"); + ret = -ENODEV; + goto error_unmap; + } + + /* + * IRQ handler for ME interaction + * Note: don't use MSI here as the PCH has bugs. + */ + pci_disable_msi(dev); + ret = request_irq(dev->irq, ips_irq_handler, IRQF_SHARED, "ips", + ips); + if (ret) { + dev_err(&dev->dev, "request irq failed, aborting\n"); + goto error_unmap; + } + + /* Enable aux, hot & critical interrupts */ + thm_writeb(THM_TSPIEN, TSPIEN_AUX2_LOHI | TSPIEN_CRIT_LOHI | + TSPIEN_HOT_LOHI | TSPIEN_AUX_LOHI); + thm_writeb(THM_TEN, TEN_UPDATE_EN); + + /* Collect adjustment values */ + ips->cta_val = thm_readw(THM_CTA); + ips->pta_val = thm_readw(THM_PTA); + ips->mgta_val = thm_readw(THM_MGTA); + + /* Save turbo limits & ratios */ + rdmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit); + + ips_disable_cpu_turbo(ips); + ips->cpu_turbo_enabled = false; + + /* Create thermal adjust thread */ + ips->adjust = kthread_create(ips_adjust, ips, "ips-adjust"); + if (IS_ERR(ips->adjust)) { + dev_err(&dev->dev, + "failed to create thermal adjust thread, aborting\n"); + ret = -ENOMEM; + goto error_free_irq; + + } + + /* + * Set up the work queue and monitor thread. The monitor thread + * will wake up ips_adjust thread. + */ + ips->monitor = kthread_run(ips_monitor, ips, "ips-monitor"); + if (IS_ERR(ips->monitor)) { + dev_err(&dev->dev, + "failed to create thermal monitor thread, aborting\n"); + ret = -ENOMEM; + goto error_thread_cleanup; + } + + hts = (ips->core_power_limit << HTS_PCPL_SHIFT) | + (ips->mcp_temp_limit << HTS_PTL_SHIFT) | HTS_NVV; + htshi = HTS2_PRST_RUNNING << HTS2_PRST_SHIFT; + + thm_writew(THM_HTSHI, htshi); + thm_writel(THM_HTS, hts); + + ips_debugfs_init(ips); + + dev_info(&dev->dev, "IPS driver initialized, MCP temp limit %d\n", + ips->mcp_temp_limit); + return ret; + +error_thread_cleanup: + kthread_stop(ips->adjust); +error_free_irq: + free_irq(ips->dev->irq, ips); +error_unmap: + iounmap(ips->regmap); +error_release: + pci_release_regions(dev); +error_free: + kfree(ips); + return ret; +} + +static void ips_remove(struct pci_dev *dev) +{ + struct ips_driver *ips = pci_get_drvdata(dev); + u64 turbo_override; + + if (!ips) + return; + + ips_debugfs_cleanup(ips); + + /* Release i915 driver */ + if (ips->read_mch_val) + symbol_put(i915_read_mch_val); + if (ips->gpu_raise) + symbol_put(i915_gpu_raise); + if (ips->gpu_lower) + symbol_put(i915_gpu_lower); + if (ips->gpu_busy) + symbol_put(i915_gpu_busy); + if (ips->gpu_turbo_disable) + symbol_put(i915_gpu_turbo_disable); + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + turbo_override &= ~(TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN); + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + wrmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit); + + free_irq(ips->dev->irq, ips); + if (ips->adjust) + kthread_stop(ips->adjust); + if (ips->monitor) + kthread_stop(ips->monitor); + iounmap(ips->regmap); + pci_release_regions(dev); + kfree(ips); + dev_dbg(&dev->dev, "IPS driver removed\n"); +} + +static void ips_shutdown(struct pci_dev *dev) +{ +} + +static struct pci_driver ips_pci_driver = { + .name = "intel ips", + .id_table = ips_id_table, + .probe = ips_probe, + .remove = ips_remove, + .shutdown = ips_shutdown, +}; + +module_pci_driver(ips_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jesse Barnes <jbarnes@virtuousgeek.org>"); +MODULE_DESCRIPTION("Intelligent Power Sharing Driver"); diff --git a/drivers/platform/x86/intel_ips.h b/drivers/platform/x86/intel_ips.h new file mode 100644 index 00000000000..73299beff5b --- /dev/null +++ b/drivers/platform/x86/intel_ips.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + */ + +void ips_link_to_i915_driver(void); diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c index f0a90a6bf39..e8b46d2c468 100644 --- a/drivers/platform/x86/intel_menlow.c +++ b/drivers/platform/x86/intel_menlow.c @@ -27,16 +27,17 @@ * to get/set bandwidth. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> +#include <linux/slab.h> #include <linux/types.h> #include <linux/pci.h> #include <linux/pm.h> - #include <linux/thermal.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/acpi.h> MODULE_AUTHOR("Thomas Sujith"); MODULE_AUTHOR("Zhang Rui"); @@ -52,6 +53,8 @@ MODULE_LICENSE("GPL"); #define MEMORY_ARG_CUR_BANDWIDTH 1 #define MEMORY_ARG_MAX_BANDWIDTH 0 +static void intel_menlow_unregister_sensor(void); + /* * GTHS returning 'n' would mean that [0,n-1] states are supported * In that case max_cstate would be n-1 @@ -132,8 +135,7 @@ static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list, &temp); - printk(KERN_INFO - "Bandwidth value was %ld: status is %d\n", state, status); + pr_info("Bandwidth value was %ld: status is %d\n", state, status); if (ACPI_FAILURE(status)) return -EFAULT; @@ -152,19 +154,15 @@ static struct thermal_cooling_device_ops memory_cooling_ops = { static int intel_menlow_memory_add(struct acpi_device *device) { int result = -ENODEV; - acpi_status status = AE_OK; - acpi_handle dummy; struct thermal_cooling_device *cdev; if (!device) return -EINVAL; - status = acpi_get_handle(device->handle, MEMORY_GET_BANDWIDTH, &dummy); - if (ACPI_FAILURE(status)) + if (!acpi_has_method(device->handle, MEMORY_GET_BANDWIDTH)) goto end; - status = acpi_get_handle(device->handle, MEMORY_SET_BANDWIDTH, &dummy); - if (ACPI_FAILURE(status)) + if (!acpi_has_method(device->handle, MEMORY_SET_BANDWIDTH)) goto end; cdev = thermal_cooling_device_register("Memory controller", device, @@ -196,7 +194,7 @@ static int intel_menlow_memory_add(struct acpi_device *device) } -static int intel_menlow_memory_remove(struct acpi_device *device, int type) +static int intel_menlow_memory_remove(struct acpi_device *device) { struct thermal_cooling_device *cdev = acpi_driver_data(device); @@ -385,7 +383,7 @@ static ssize_t bios_enabled_show(struct device *dev, return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled"); } -static int intel_menlow_add_one_attribute(char *name, int mode, void *show, +static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show, void *store, struct device *dev, acpi_handle handle) { @@ -396,6 +394,7 @@ static int intel_menlow_add_one_attribute(char *name, int mode, void *show, if (!attr) return -ENOMEM; + sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */ attr->attr.attr.name = name; attr->attr.attr.mode = mode; attr->attr.show = show; @@ -404,8 +403,10 @@ static int intel_menlow_add_one_attribute(char *name, int mode, void *show, attr->handle = handle; result = device_create_file(dev, &attr->attr); - if (result) + if (result) { + kfree(attr); return result; + } mutex_lock(&intel_menlow_attr_lock); list_add_tail(&attr->node, &intel_menlow_attr_list); @@ -429,11 +430,11 @@ static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, /* _TZ must have the AUX0/1 methods */ status = acpi_get_handle(handle, GET_AUX0, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + return (status == AE_NOT_FOUND) ? AE_OK : status; status = acpi_get_handle(handle, SET_AUX0, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + return (status == AE_NOT_FOUND) ? AE_OK : status; result = intel_menlow_add_one_attribute("aux0", 0644, aux0_show, aux0_store, @@ -443,17 +444,19 @@ static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, status = acpi_get_handle(handle, GET_AUX1, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + goto aux1_not_found; status = acpi_get_handle(handle, SET_AUX1, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + goto aux1_not_found; result = intel_menlow_add_one_attribute("aux1", 0644, aux1_show, aux1_store, &thermal->device, handle); - if (result) + if (result) { + intel_menlow_unregister_sensor(); return AE_ERROR; + } /* * create the "dabney_enabled" attribute which means the user app @@ -463,14 +466,19 @@ static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, result = intel_menlow_add_one_attribute("bios_enabled", 0444, bios_enabled_show, NULL, &thermal->device, handle); - if (result) + if (result) { + intel_menlow_unregister_sensor(); return AE_ERROR; + } - not_found: + return AE_OK; + + aux1_not_found: if (status == AE_NOT_FOUND) return AE_OK; - else - return status; + + intel_menlow_unregister_sensor(); + return status; } static void intel_menlow_unregister_sensor(void) @@ -511,8 +519,10 @@ static int __init intel_menlow_module_init(void) status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, intel_menlow_register_sensor, NULL, NULL, NULL); - if (ACPI_FAILURE(status)) + if (ACPI_FAILURE(status)) { + acpi_bus_unregister_driver(&intel_menlow_memory_driver); return -ENODEV; + } return 0; } diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c new file mode 100644 index 00000000000..8d6775266d6 --- /dev/null +++ b/drivers/platform/x86/intel_mid_powerbtn.c @@ -0,0 +1,147 @@ +/* + * Power button driver for Medfield. + * + * Copyright (C) 2010 Intel Corp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/mfd/intel_msic.h> + +#define DRIVER_NAME "msic_power_btn" + +#define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */ + +/* + * MSIC document ti_datasheet defines the 1st bit reg 0x21 is used to mask + * power button interrupt + */ +#define MSIC_PWRBTNM (1 << 0) + +static irqreturn_t mfld_pb_isr(int irq, void *dev_id) +{ + struct input_dev *input = dev_id; + int ret; + u8 pbstat; + + ret = intel_msic_reg_read(INTEL_MSIC_PBSTATUS, &pbstat); + dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat); + + if (ret < 0) { + dev_err(input->dev.parent, "Read error %d while reading" + " MSIC_PB_STATUS\n", ret); + } else { + input_event(input, EV_KEY, KEY_POWER, + !(pbstat & MSIC_PB_LEVEL)); + input_sync(input); + } + + return IRQ_HANDLED; +} + +static int mfld_pb_probe(struct platform_device *pdev) +{ + struct input_dev *input; + int irq = platform_get_irq(pdev, 0); + int error; + + if (irq < 0) + return -EINVAL; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + input->name = pdev->name; + input->phys = "power-button/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + + input_set_capability(input, EV_KEY, KEY_POWER); + + error = request_threaded_irq(irq, NULL, mfld_pb_isr, IRQF_NO_SUSPEND, + DRIVER_NAME, input); + if (error) { + dev_err(&pdev->dev, "Unable to request irq %d for mfld power" + "button\n", irq); + goto err_free_input; + } + + error = input_register_device(input); + if (error) { + dev_err(&pdev->dev, "Unable to register input dev, error " + "%d\n", error); + goto err_free_irq; + } + + platform_set_drvdata(pdev, input); + + /* + * SCU firmware might send power button interrupts to IA core before + * kernel boots and doesn't get EOI from IA core. The first bit of + * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new + * power interrupt to Android kernel. Unmask the bit when probing + * power button in kernel. + * There is a very narrow race between irq handler and power button + * initialization. The race happens rarely. So we needn't worry + * about it. + */ + error = intel_msic_reg_update(INTEL_MSIC_IRQLVL1MSK, 0, MSIC_PWRBTNM); + if (error) { + dev_err(&pdev->dev, "Unable to clear power button interrupt, " + "error: %d\n", error); + goto err_free_irq; + } + + return 0; + +err_free_irq: + free_irq(irq, input); +err_free_input: + input_free_device(input); + return error; +} + +static int mfld_pb_remove(struct platform_device *pdev) +{ + struct input_dev *input = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + free_irq(irq, input); + input_unregister_device(input); + + return 0; +} + +static struct platform_driver mfld_pb_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = mfld_pb_probe, + .remove = mfld_pb_remove, +}; + +module_platform_driver(mfld_pb_driver); + +MODULE_AUTHOR("Hong Liu <hong.liu@intel.com>"); +MODULE_DESCRIPTION("Intel Medfield Power Button Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c new file mode 100644 index 00000000000..ab7860a21a2 --- /dev/null +++ b/drivers/platform/x86/intel_mid_thermal.c @@ -0,0 +1,570 @@ +/* + * intel_mid_thermal.c - Intel MID platform thermal driver + * + * Copyright (C) 2011 Intel Corporation + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Author: Durgadoss R <durgadoss.r@intel.com> + */ + +#define pr_fmt(fmt) "intel_mid_thermal: " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/param.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/thermal.h> +#include <linux/mfd/intel_msic.h> + +/* Number of thermal sensors */ +#define MSIC_THERMAL_SENSORS 4 + +/* ADC1 - thermal registers */ +#define MSIC_ADC_ENBL 0x10 +#define MSIC_ADC_START 0x08 + +#define MSIC_ADCTHERM_ENBL 0x04 +#define MSIC_ADCRRDATA_ENBL 0x05 +#define MSIC_CHANL_MASK_VAL 0x0F + +#define MSIC_STOPBIT_MASK 16 +#define MSIC_ADCTHERM_MASK 4 +/* Number of ADC channels */ +#define ADC_CHANLS_MAX 15 +#define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) + +/* ADC channel code values */ +#define SKIN_SENSOR0_CODE 0x08 +#define SKIN_SENSOR1_CODE 0x09 +#define SYS_SENSOR_CODE 0x0A +#define MSIC_DIE_SENSOR_CODE 0x03 + +#define SKIN_THERM_SENSOR0 0 +#define SKIN_THERM_SENSOR1 1 +#define SYS_THERM_SENSOR2 2 +#define MSIC_DIE_THERM_SENSOR3 3 + +/* ADC code range */ +#define ADC_MAX 977 +#define ADC_MIN 162 +#define ADC_VAL0C 887 +#define ADC_VAL20C 720 +#define ADC_VAL40C 508 +#define ADC_VAL60C 315 + +/* ADC base addresses */ +#define ADC_CHNL_START_ADDR INTEL_MSIC_ADC1ADDR0 /* increments by 1 */ +#define ADC_DATA_START_ADDR INTEL_MSIC_ADC1SNS0H /* increments by 2 */ + +/* MSIC die attributes */ +#define MSIC_DIE_ADC_MIN 488 +#define MSIC_DIE_ADC_MAX 1004 + +/* This holds the address of the first free ADC channel, + * among the 15 channels + */ +static int channel_index; + +struct platform_info { + struct platform_device *pdev; + struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; +}; + +struct thermal_device_info { + unsigned int chnl_addr; + int direct; + /* This holds the current temperature in millidegree celsius */ + long curr_temp; +}; + +/** + * to_msic_die_temp - converts adc_val to msic_die temperature + * @adc_val: ADC value to be converted + * + * Can sleep + */ +static int to_msic_die_temp(uint16_t adc_val) +{ + return (368 * (adc_val) / 1000) - 220; +} + +/** + * is_valid_adc - checks whether the adc code is within the defined range + * @min: minimum value for the sensor + * @max: maximum value for the sensor + * + * Can sleep + */ +static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) +{ + return (adc_val >= min) && (adc_val <= max); +} + +/** + * adc_to_temp - converts the ADC code to temperature in C + * @direct: true if ths channel is direct index + * @adc_val: the adc_val that needs to be converted + * @tp: temperature return value + * + * Linear approximation is used to covert the skin adc value into temperature. + * This technique is used to avoid very long look-up table to get + * the appropriate temp value from ADC value. + * The adc code vs sensor temp curve is split into five parts + * to achieve very close approximate temp value with less than + * 0.5C error + */ +static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp) +{ + int temp; + + /* Direct conversion for die temperature */ + if (direct) { + if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { + *tp = to_msic_die_temp(adc_val) * 1000; + return 0; + } + return -ERANGE; + } + + if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) + return -ERANGE; + + /* Linear approximation for skin temperature */ + if (adc_val > ADC_VAL0C) + temp = 177 - (adc_val/5); + else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) + temp = 111 - (adc_val/8); + else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) + temp = 92 - (adc_val/10); + else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) + temp = 91 - (adc_val/10); + else + temp = 112 - (adc_val/6); + + /* Convert temperature in celsius to milli degree celsius */ + *tp = temp * 1000; + return 0; +} + +/** + * mid_read_temp - read sensors for temperature + * @temp: holds the current temperature for the sensor after reading + * + * reads the adc_code from the channel and converts it to real + * temperature. The converted value is stored in temp. + * + * Can sleep + */ +static int mid_read_temp(struct thermal_zone_device *tzd, unsigned long *temp) +{ + struct thermal_device_info *td_info = tzd->devdata; + uint16_t adc_val, addr; + uint8_t data = 0; + int ret; + unsigned long curr_temp; + + + addr = td_info->chnl_addr; + + /* Enable the msic for conversion before reading */ + ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); + if (ret) + return ret; + + /* Re-toggle the RRDATARD bit (temporary workaround) */ + ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCTHERM_ENBL); + if (ret) + return ret; + + /* Read the higher bits of data */ + ret = intel_msic_reg_read(addr, &data); + if (ret) + return ret; + + /* Shift bits to accommodate the lower two data bits */ + adc_val = (data << 2); + addr++; + + ret = intel_msic_reg_read(addr, &data);/* Read lower bits */ + if (ret) + return ret; + + /* Adding lower two bits to the higher bits */ + data &= 03; + adc_val += data; + + /* Convert ADC value to temperature */ + ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); + if (ret == 0) + *temp = td_info->curr_temp = curr_temp; + return ret; +} + +/** + * configure_adc - enables/disables the ADC for conversion + * @val: zero: disables the ADC non-zero:enables the ADC + * + * Enable/Disable the ADC depending on the argument + * + * Can sleep + */ +static int configure_adc(int val) +{ + int ret; + uint8_t data; + + ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); + if (ret) + return ret; + + if (val) { + /* Enable and start the ADC */ + data |= (MSIC_ADC_ENBL | MSIC_ADC_START); + } else { + /* Just stop the ADC */ + data &= (~MSIC_ADC_START); + } + return intel_msic_reg_write(INTEL_MSIC_ADC1CNTL1, data); +} + +/** + * set_up_therm_channel - enable thermal channel for conversion + * @base_addr: index of free msic ADC channel + * + * Enable all the three channels for conversion + * + * Can sleep + */ +static int set_up_therm_channel(u16 base_addr) +{ + int ret; + + /* Enable all the sensor channels */ + ret = intel_msic_reg_write(base_addr, SKIN_SENSOR0_CODE); + if (ret) + return ret; + + ret = intel_msic_reg_write(base_addr + 1, SKIN_SENSOR1_CODE); + if (ret) + return ret; + + ret = intel_msic_reg_write(base_addr + 2, SYS_SENSOR_CODE); + if (ret) + return ret; + + /* Since this is the last channel, set the stop bit + * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ + ret = intel_msic_reg_write(base_addr + 3, + (MSIC_DIE_SENSOR_CODE | 0x10)); + if (ret) + return ret; + + /* Enable ADC and start it */ + return configure_adc(1); +} + +/** + * reset_stopbit - sets the stop bit to 0 on the given channel + * @addr: address of the channel + * + * Can sleep + */ +static int reset_stopbit(uint16_t addr) +{ + int ret; + uint8_t data; + ret = intel_msic_reg_read(addr, &data); + if (ret) + return ret; + /* Set the stop bit to zero */ + return intel_msic_reg_write(addr, (data & 0xEF)); +} + +/** + * find_free_channel - finds an empty channel for conversion + * + * If the ADC is not enabled then start using 0th channel + * itself. Otherwise find an empty channel by looking for a + * channel in which the stopbit is set to 1. returns the index + * of the first free channel if succeeds or an error code. + * + * Context: can sleep + * + * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc + * code. + */ +static int find_free_channel(void) +{ + int ret; + int i; + uint8_t data; + + /* check whether ADC is enabled */ + ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); + if (ret) + return ret; + + if ((data & MSIC_ADC_ENBL) == 0) + return 0; + + /* ADC is already enabled; Looking for an empty channel */ + for (i = 0; i < ADC_CHANLS_MAX; i++) { + ret = intel_msic_reg_read(ADC_CHNL_START_ADDR + i, &data); + if (ret) + return ret; + + if (data & MSIC_STOPBIT_MASK) { + ret = i; + break; + } + } + return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret; +} + +/** + * mid_initialize_adc - initializing the ADC + * @dev: our device structure + * + * Initialize the ADC for reading thermistor values. Can sleep. + */ +static int mid_initialize_adc(struct device *dev) +{ + u8 data; + u16 base_addr; + int ret; + + /* + * Ensure that adctherm is disabled before we + * initialize the ADC + */ + ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL3, &data); + if (ret) + return ret; + + data &= ~MSIC_ADCTHERM_MASK; + ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, data); + if (ret) + return ret; + + /* Index of the first channel in which the stop bit is set */ + channel_index = find_free_channel(); + if (channel_index < 0) { + dev_err(dev, "No free ADC channels"); + return channel_index; + } + + base_addr = ADC_CHNL_START_ADDR + channel_index; + + if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) { + /* Reset stop bit for channels other than 0 and 12 */ + ret = reset_stopbit(base_addr); + if (ret) + return ret; + + /* Index of the first free channel */ + base_addr++; + channel_index++; + } + + ret = set_up_therm_channel(base_addr); + if (ret) { + dev_err(dev, "unable to enable ADC"); + return ret; + } + dev_dbg(dev, "ADC initialization successful"); + return ret; +} + +/** + * initialize_sensor - sets default temp and timer ranges + * @index: index of the sensor + * + * Context: can sleep + */ +static struct thermal_device_info *initialize_sensor(int index) +{ + struct thermal_device_info *td_info = + kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); + + if (!td_info) + return NULL; + + /* Set the base addr of the channel for this sensor */ + td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index); + /* Sensor 3 is direct conversion */ + if (index == 3) + td_info->direct = 1; + return td_info; +} + +/** + * mid_thermal_resume - resume routine + * @dev: device structure + * + * mid thermal resume: re-initializes the adc. Can sleep. + */ +static int mid_thermal_resume(struct device *dev) +{ + return mid_initialize_adc(dev); +} + +/** + * mid_thermal_suspend - suspend routine + * @dev: device structure + * + * mid thermal suspend implements the suspend functionality + * by stopping the ADC. Can sleep. + */ +static int mid_thermal_suspend(struct device *dev) +{ + /* + * This just stops the ADC and does not disable it. + * temporary workaround until we have a generic ADC driver. + * If 0 is passed, it disables the ADC. + */ + return configure_adc(0); +} + +static SIMPLE_DEV_PM_OPS(mid_thermal_pm, + mid_thermal_suspend, mid_thermal_resume); + +/** + * read_curr_temp - reads the current temperature and stores in temp + * @temp: holds the current temperature value after reading + * + * Can sleep + */ +static int read_curr_temp(struct thermal_zone_device *tzd, unsigned long *temp) +{ + WARN_ON(tzd == NULL); + return mid_read_temp(tzd, temp); +} + +/* Can't be const */ +static struct thermal_zone_device_ops tzd_ops = { + .get_temp = read_curr_temp, +}; + +/** + * mid_thermal_probe - mfld thermal initialize + * @pdev: platform device structure + * + * mid thermal probe initializes the hardware and registers + * all the sensors with the generic thermal framework. Can sleep. + */ +static int mid_thermal_probe(struct platform_device *pdev) +{ + static char *name[MSIC_THERMAL_SENSORS] = { + "skin0", "skin1", "sys", "msicdie" + }; + + int ret; + int i; + struct platform_info *pinfo; + + pinfo = devm_kzalloc(&pdev->dev, sizeof(struct platform_info), + GFP_KERNEL); + if (!pinfo) + return -ENOMEM; + + /* Initializing the hardware */ + ret = mid_initialize_adc(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "ADC init failed"); + return ret; + } + + /* Register each sensor with the generic thermal framework*/ + for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { + struct thermal_device_info *td_info = initialize_sensor(i); + + if (!td_info) { + ret = -ENOMEM; + goto err; + } + pinfo->tzd[i] = thermal_zone_device_register(name[i], + 0, 0, td_info, &tzd_ops, NULL, 0, 0); + if (IS_ERR(pinfo->tzd[i])) { + kfree(td_info); + ret = PTR_ERR(pinfo->tzd[i]); + goto err; + } + } + + pinfo->pdev = pdev; + platform_set_drvdata(pdev, pinfo); + return 0; + +err: + while (--i >= 0) { + kfree(pinfo->tzd[i]->devdata); + thermal_zone_device_unregister(pinfo->tzd[i]); + } + configure_adc(0); + return ret; +} + +/** + * mid_thermal_remove - mfld thermal finalize + * @dev: platform device structure + * + * MLFD thermal remove unregisters all the sensors from the generic + * thermal framework. Can sleep. + */ +static int mid_thermal_remove(struct platform_device *pdev) +{ + int i; + struct platform_info *pinfo = platform_get_drvdata(pdev); + + for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { + kfree(pinfo->tzd[i]->devdata); + thermal_zone_device_unregister(pinfo->tzd[i]); + } + + /* Stop the ADC */ + return configure_adc(0); +} + +#define DRIVER_NAME "msic_thermal" + +static const struct platform_device_id therm_id_table[] = { + { DRIVER_NAME, 1 }, + { "msic_thermal", 1 }, + { } +}; + +static struct platform_driver mid_thermal_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &mid_thermal_pm, + }, + .probe = mid_thermal_probe, + .remove = mid_thermal_remove, + .id_table = therm_id_table, +}; + +module_platform_driver(mid_thermal_driver); + +MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); +MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c new file mode 100644 index 00000000000..4bc96041678 --- /dev/null +++ b/drivers/platform/x86/intel_oaktrail.c @@ -0,0 +1,394 @@ +/* + * intel_oaktrail.c - Intel OakTrail Platform support. + * + * Copyright (C) 2010-2011 Intel Corporation + * Author: Yin Kangkai (kangkai.yin@intel.com) + * + * based on Compal driver, Copyright (C) 2008 Cezary Jackiewicz + * <cezary.jackiewicz (at) gmail.com>, based on MSI driver + * Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * This driver does below things: + * 1. registers itself in the Linux backlight control in + * /sys/class/backlight/intel_oaktrail/ + * + * 2. registers in the rfkill subsystem here: /sys/class/rfkill/rfkillX/ + * for these components: wifi, bluetooth, wwan (3g), gps + * + * This driver might work on other products based on Oaktrail. If you + * want to try it you can pass force=1 as argument to the module which + * will force it to load even when the DMI data doesn't identify the + * product as compatible. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <linux/fb.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/backlight.h> +#include <linux/platform_device.h> +#include <linux/dmi.h> +#include <linux/rfkill.h> + +#define DRIVER_NAME "intel_oaktrail" +#define DRIVER_VERSION "0.4ac1" + +/* + * This is the devices status address in EC space, and the control bits + * definition: + * + * (1 << 0): Camera enable/disable, RW. + * (1 << 1): Bluetooth enable/disable, RW. + * (1 << 2): GPS enable/disable, RW. + * (1 << 3): WiFi enable/disable, RW. + * (1 << 4): WWAN (3G) enable/disalbe, RW. + * (1 << 5): Touchscreen enable/disable, Read Only. + */ +#define OT_EC_DEVICE_STATE_ADDRESS 0xD6 + +#define OT_EC_CAMERA_MASK (1 << 0) +#define OT_EC_BT_MASK (1 << 1) +#define OT_EC_GPS_MASK (1 << 2) +#define OT_EC_WIFI_MASK (1 << 3) +#define OT_EC_WWAN_MASK (1 << 4) +#define OT_EC_TS_MASK (1 << 5) + +/* + * This is the address in EC space and commands used to control LCD backlight: + * + * Two steps needed to change the LCD backlight: + * 1. write the backlight percentage into OT_EC_BL_BRIGHTNESS_ADDRESS; + * 2. write OT_EC_BL_CONTROL_ON_DATA into OT_EC_BL_CONTROL_ADDRESS. + * + * To read the LCD back light, just read out the value from + * OT_EC_BL_BRIGHTNESS_ADDRESS. + * + * LCD backlight brightness range: 0 - 100 (OT_EC_BL_BRIGHTNESS_MAX) + */ +#define OT_EC_BL_BRIGHTNESS_ADDRESS 0x44 +#define OT_EC_BL_BRIGHTNESS_MAX 100 +#define OT_EC_BL_CONTROL_ADDRESS 0x3A +#define OT_EC_BL_CONTROL_ON_DATA 0x1A + + +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); + +static struct platform_device *oaktrail_device; +static struct backlight_device *oaktrail_bl_device; +static struct rfkill *bt_rfkill; +static struct rfkill *gps_rfkill; +static struct rfkill *wifi_rfkill; +static struct rfkill *wwan_rfkill; + + +/* rfkill */ +static int oaktrail_rfkill_set(void *data, bool blocked) +{ + u8 value; + u8 result; + unsigned long radio = (unsigned long) data; + + ec_read(OT_EC_DEVICE_STATE_ADDRESS, &result); + + if (!blocked) + value = (u8) (result | radio); + else + value = (u8) (result & ~radio); + + ec_write(OT_EC_DEVICE_STATE_ADDRESS, value); + + return 0; +} + +static const struct rfkill_ops oaktrail_rfkill_ops = { + .set_block = oaktrail_rfkill_set, +}; + +static struct rfkill *oaktrail_rfkill_new(char *name, enum rfkill_type type, + unsigned long mask) +{ + struct rfkill *rfkill_dev; + u8 value; + int err; + + rfkill_dev = rfkill_alloc(name, &oaktrail_device->dev, type, + &oaktrail_rfkill_ops, (void *)mask); + if (!rfkill_dev) + return ERR_PTR(-ENOMEM); + + ec_read(OT_EC_DEVICE_STATE_ADDRESS, &value); + rfkill_init_sw_state(rfkill_dev, (value & mask) != 1); + + err = rfkill_register(rfkill_dev); + if (err) { + rfkill_destroy(rfkill_dev); + return ERR_PTR(err); + } + + return rfkill_dev; +} + +static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf) +{ + if (rf) { + rfkill_unregister(rf); + rfkill_destroy(rf); + } +} + +static void oaktrail_rfkill_cleanup(void) +{ + __oaktrail_rfkill_cleanup(wifi_rfkill); + __oaktrail_rfkill_cleanup(bt_rfkill); + __oaktrail_rfkill_cleanup(gps_rfkill); + __oaktrail_rfkill_cleanup(wwan_rfkill); +} + +static int oaktrail_rfkill_init(void) +{ + int ret; + + wifi_rfkill = oaktrail_rfkill_new("oaktrail-wifi", + RFKILL_TYPE_WLAN, + OT_EC_WIFI_MASK); + if (IS_ERR(wifi_rfkill)) { + ret = PTR_ERR(wifi_rfkill); + wifi_rfkill = NULL; + goto cleanup; + } + + bt_rfkill = oaktrail_rfkill_new("oaktrail-bluetooth", + RFKILL_TYPE_BLUETOOTH, + OT_EC_BT_MASK); + if (IS_ERR(bt_rfkill)) { + ret = PTR_ERR(bt_rfkill); + bt_rfkill = NULL; + goto cleanup; + } + + gps_rfkill = oaktrail_rfkill_new("oaktrail-gps", + RFKILL_TYPE_GPS, + OT_EC_GPS_MASK); + if (IS_ERR(gps_rfkill)) { + ret = PTR_ERR(gps_rfkill); + gps_rfkill = NULL; + goto cleanup; + } + + wwan_rfkill = oaktrail_rfkill_new("oaktrail-wwan", + RFKILL_TYPE_WWAN, + OT_EC_WWAN_MASK); + if (IS_ERR(wwan_rfkill)) { + ret = PTR_ERR(wwan_rfkill); + wwan_rfkill = NULL; + goto cleanup; + } + + return 0; + +cleanup: + oaktrail_rfkill_cleanup(); + return ret; +} + + +/* backlight */ +static int get_backlight_brightness(struct backlight_device *b) +{ + u8 value; + ec_read(OT_EC_BL_BRIGHTNESS_ADDRESS, &value); + + return value; +} + +static int set_backlight_brightness(struct backlight_device *b) +{ + u8 percent = (u8) b->props.brightness; + if (percent < 0 || percent > OT_EC_BL_BRIGHTNESS_MAX) + return -EINVAL; + + ec_write(OT_EC_BL_BRIGHTNESS_ADDRESS, percent); + ec_write(OT_EC_BL_CONTROL_ADDRESS, OT_EC_BL_CONTROL_ON_DATA); + + return 0; +} + +static const struct backlight_ops oaktrail_bl_ops = { + .get_brightness = get_backlight_brightness, + .update_status = set_backlight_brightness, +}; + +static int oaktrail_backlight_init(void) +{ + struct backlight_device *bd; + struct backlight_properties props; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = OT_EC_BL_BRIGHTNESS_MAX; + bd = backlight_device_register(DRIVER_NAME, + &oaktrail_device->dev, NULL, + &oaktrail_bl_ops, + &props); + + if (IS_ERR(bd)) { + oaktrail_bl_device = NULL; + pr_warning("Unable to register backlight device\n"); + return PTR_ERR(bd); + } + + oaktrail_bl_device = bd; + + bd->props.brightness = get_backlight_brightness(bd); + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + return 0; +} + +static void oaktrail_backlight_exit(void) +{ + if (oaktrail_bl_device) + backlight_device_unregister(oaktrail_bl_device); +} + +static int oaktrail_probe(struct platform_device *pdev) +{ + return 0; +} + +static int oaktrail_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver oaktrail_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = oaktrail_probe, + .remove = oaktrail_remove, +}; + +static int dmi_check_cb(const struct dmi_system_id *id) +{ + pr_info("Identified model '%s'\n", id->ident); + return 0; +} + +static struct dmi_system_id __initdata oaktrail_dmi_table[] = { + { + .ident = "OakTrail platform", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "OakTrail platform"), + }, + .callback = dmi_check_cb + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table); + +static int __init oaktrail_init(void) +{ + int ret; + + if (acpi_disabled) { + pr_err("ACPI needs to be enabled for this driver to work!\n"); + return -ENODEV; + } + + if (!force && !dmi_check_system(oaktrail_dmi_table)) { + pr_err("Platform not recognized (You could try the module's force-parameter)"); + return -ENODEV; + } + + ret = platform_driver_register(&oaktrail_driver); + if (ret) { + pr_warning("Unable to register platform driver\n"); + goto err_driver_reg; + } + + oaktrail_device = platform_device_alloc(DRIVER_NAME, -1); + if (!oaktrail_device) { + pr_warning("Unable to allocate platform device\n"); + ret = -ENOMEM; + goto err_device_alloc; + } + + ret = platform_device_add(oaktrail_device); + if (ret) { + pr_warning("Unable to add platform device\n"); + goto err_device_add; + } + + if (!acpi_video_backlight_support()) { + ret = oaktrail_backlight_init(); + if (ret) + goto err_backlight; + + } else + pr_info("Backlight controlled by ACPI video driver\n"); + + ret = oaktrail_rfkill_init(); + if (ret) { + pr_warning("Setup rfkill failed\n"); + goto err_rfkill; + } + + pr_info("Driver "DRIVER_VERSION" successfully loaded\n"); + return 0; + +err_rfkill: + oaktrail_backlight_exit(); +err_backlight: + platform_device_del(oaktrail_device); +err_device_add: + platform_device_put(oaktrail_device); +err_device_alloc: + platform_driver_unregister(&oaktrail_driver); +err_driver_reg: + + return ret; +} + +static void __exit oaktrail_cleanup(void) +{ + oaktrail_backlight_exit(); + oaktrail_rfkill_cleanup(); + platform_device_unregister(oaktrail_device); + platform_driver_unregister(&oaktrail_driver); + + pr_info("Driver unloaded\n"); +} + +module_init(oaktrail_init); +module_exit(oaktrail_cleanup); + +MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)"); +MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c new file mode 100644 index 00000000000..40929e4f7ad --- /dev/null +++ b/drivers/platform/x86/intel_pmic_gpio.c @@ -0,0 +1,332 @@ +/* Moorestown PMIC GPIO (access through IPC) driver + * Copyright (c) 2008 - 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Moorestown platform PMIC chip + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <asm/intel_scu_ipc.h> +#include <linux/device.h> +#include <linux/intel_pmic_gpio.h> +#include <linux/platform_device.h> + +#define DRIVER_NAME "pmic_gpio" + +/* register offset that IPC driver should use + * 8 GPIO + 8 GPOSW (6 controllable) + 8GPO + */ +enum pmic_gpio_register { + GPIO0 = 0xE0, + GPIO7 = 0xE7, + GPIOINT = 0xE8, + GPOSWCTL0 = 0xEC, + GPOSWCTL5 = 0xF1, + GPO = 0xF4, +}; + +/* bits definition for GPIO & GPOSW */ +#define GPIO_DRV 0x01 +#define GPIO_DIR 0x02 +#define GPIO_DIN 0x04 +#define GPIO_DOU 0x08 +#define GPIO_INTCTL 0x30 +#define GPIO_DBC 0xc0 + +#define GPOSW_DRV 0x01 +#define GPOSW_DOU 0x08 +#define GPOSW_RDRV 0x30 + +#define GPIO_UPDATE_TYPE 0x80000000 + +#define NUM_GPIO 24 + +struct pmic_gpio { + struct mutex buslock; + struct gpio_chip chip; + void *gpiointr; + int irq; + unsigned irq_base; + unsigned int update_type; + u32 trigger_type; +}; + +static void pmic_program_irqtype(int gpio, int type) +{ + if (type & IRQ_TYPE_EDGE_RISING) + intel_scu_ipc_update_register(GPIO0 + gpio, 0x20, 0x20); + else + intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x20); + + if (type & IRQ_TYPE_EDGE_FALLING) + intel_scu_ipc_update_register(GPIO0 + gpio, 0x10, 0x10); + else + intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x10); +}; + +static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + if (offset >= 8) { + pr_err("only pin 0-7 support input\n"); + return -1;/* we only have 8 GPIO can use as input */ + } + return intel_scu_ipc_update_register(GPIO0 + offset, + GPIO_DIR, GPIO_DIR); +} + +static int pmic_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + int rc = 0; + + if (offset < 8)/* it is GPIO */ + rc = intel_scu_ipc_update_register(GPIO0 + offset, + GPIO_DRV | (value ? GPIO_DOU : 0), + GPIO_DRV | GPIO_DOU | GPIO_DIR); + else if (offset < 16)/* it is GPOSW */ + rc = intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8, + GPOSW_DRV | (value ? GPOSW_DOU : 0), + GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV); + else if (offset > 15 && offset < 24)/* it is GPO */ + rc = intel_scu_ipc_update_register(GPO, + value ? 1 << (offset - 16) : 0, + 1 << (offset - 16)); + else { + pr_err("invalid PMIC GPIO pin %d!\n", offset); + WARN_ON(1); + } + + return rc; +} + +static int pmic_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + u8 r; + int ret; + + /* we only have 8 GPIO pins we can use as input */ + if (offset >= 8) + return -EOPNOTSUPP; + ret = intel_scu_ipc_ioread8(GPIO0 + offset, &r); + if (ret < 0) + return ret; + return r & GPIO_DIN; +} + +static void pmic_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (offset < 8)/* it is GPIO */ + intel_scu_ipc_update_register(GPIO0 + offset, + GPIO_DRV | (value ? GPIO_DOU : 0), + GPIO_DRV | GPIO_DOU); + else if (offset < 16)/* it is GPOSW */ + intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8, + GPOSW_DRV | (value ? GPOSW_DOU : 0), + GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV); + else if (offset > 15 && offset < 24) /* it is GPO */ + intel_scu_ipc_update_register(GPO, + value ? 1 << (offset - 16) : 0, + 1 << (offset - 16)); +} + +/* + * This is called from genirq with pg->buslock locked and + * irq_desc->lock held. We can not access the scu bus here, so we + * store the change and update in the bus_sync_unlock() function below + */ +static int pmic_irq_type(struct irq_data *data, unsigned type) +{ + struct pmic_gpio *pg = irq_data_get_irq_chip_data(data); + u32 gpio = data->irq - pg->irq_base; + + if (gpio >= pg->chip.ngpio) + return -EINVAL; + + pg->trigger_type = type; + pg->update_type = gpio | GPIO_UPDATE_TYPE; + return 0; +} + +static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pmic_gpio *pg = container_of(chip, struct pmic_gpio, chip); + + return pg->irq_base + offset; +} + +static void pmic_bus_lock(struct irq_data *data) +{ + struct pmic_gpio *pg = irq_data_get_irq_chip_data(data); + + mutex_lock(&pg->buslock); +} + +static void pmic_bus_sync_unlock(struct irq_data *data) +{ + struct pmic_gpio *pg = irq_data_get_irq_chip_data(data); + + if (pg->update_type) { + unsigned int gpio = pg->update_type & ~GPIO_UPDATE_TYPE; + + pmic_program_irqtype(gpio, pg->trigger_type); + pg->update_type = 0; + } + mutex_unlock(&pg->buslock); +} + +/* the gpiointr register is read-clear, so just do nothing. */ +static void pmic_irq_unmask(struct irq_data *data) { } + +static void pmic_irq_mask(struct irq_data *data) { } + +static struct irq_chip pmic_irqchip = { + .name = "PMIC-GPIO", + .irq_mask = pmic_irq_mask, + .irq_unmask = pmic_irq_unmask, + .irq_set_type = pmic_irq_type, + .irq_bus_lock = pmic_bus_lock, + .irq_bus_sync_unlock = pmic_bus_sync_unlock, +}; + +static irqreturn_t pmic_irq_handler(int irq, void *data) +{ + struct pmic_gpio *pg = data; + u8 intsts = *((u8 *)pg->gpiointr + 4); + int gpio; + irqreturn_t ret = IRQ_NONE; + + for (gpio = 0; gpio < 8; gpio++) { + if (intsts & (1 << gpio)) { + pr_debug("pmic pin %d triggered\n", gpio); + generic_handle_irq(pg->irq_base + gpio); + ret = IRQ_HANDLED; + } + } + return ret; +} + +static int platform_pmic_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int irq = platform_get_irq(pdev, 0); + struct intel_pmic_gpio_platform_data *pdata = dev->platform_data; + + struct pmic_gpio *pg; + int retval; + int i; + + if (irq < 0) { + dev_dbg(dev, "no IRQ line\n"); + return -EINVAL; + } + + if (!pdata || !pdata->gpio_base || !pdata->irq_base) { + dev_dbg(dev, "incorrect or missing platform data\n"); + return -EINVAL; + } + + pg = kzalloc(sizeof(*pg), GFP_KERNEL); + if (!pg) + return -ENOMEM; + + dev_set_drvdata(dev, pg); + + pg->irq = irq; + /* setting up SRAM mapping for GPIOINT register */ + pg->gpiointr = ioremap_nocache(pdata->gpiointr, 8); + if (!pg->gpiointr) { + pr_err("Can not map GPIOINT\n"); + retval = -EINVAL; + goto err2; + } + pg->irq_base = pdata->irq_base; + pg->chip.label = "intel_pmic"; + pg->chip.direction_input = pmic_gpio_direction_input; + pg->chip.direction_output = pmic_gpio_direction_output; + pg->chip.get = pmic_gpio_get; + pg->chip.set = pmic_gpio_set; + pg->chip.to_irq = pmic_gpio_to_irq; + pg->chip.base = pdata->gpio_base; + pg->chip.ngpio = NUM_GPIO; + pg->chip.can_sleep = 1; + pg->chip.dev = dev; + + mutex_init(&pg->buslock); + + pg->chip.dev = dev; + retval = gpiochip_add(&pg->chip); + if (retval) { + pr_err("Can not add pmic gpio chip\n"); + goto err; + } + + retval = request_irq(pg->irq, pmic_irq_handler, 0, "pmic", pg); + if (retval) { + pr_warn("Interrupt request failed\n"); + goto fail_request_irq; + } + + for (i = 0; i < 8; i++) { + irq_set_chip_and_handler_name(i + pg->irq_base, + &pmic_irqchip, + handle_simple_irq, + "demux"); + irq_set_chip_data(i + pg->irq_base, pg); + } + return 0; + +fail_request_irq: + if (gpiochip_remove(&pg->chip)) + pr_err("gpiochip_remove failed\n"); +err: + iounmap(pg->gpiointr); +err2: + kfree(pg); + return retval; +} + +/* at the same time, register a platform driver + * this supports the sfi 0.81 fw */ +static struct platform_driver platform_pmic_gpio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = platform_pmic_gpio_probe, +}; + +static int __init platform_pmic_gpio_init(void) +{ + return platform_driver_register(&platform_pmic_gpio_driver); +} + +subsys_initcall(platform_pmic_gpio_init); + +MODULE_AUTHOR("Alek Du <alek.du@intel.com>"); +MODULE_DESCRIPTION("Intel Moorestown PMIC GPIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c new file mode 100644 index 00000000000..76ca094ed01 --- /dev/null +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -0,0 +1,688 @@ +/* + * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism + * + * (C) Copyright 2008-2010 Intel Corporation + * Author: Sreedhara DS (sreedhara.ds@intel.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * SCU running in ARC processor communicates with other entity running in IA + * core through IPC mechanism which in turn messaging between IA core ad SCU. + * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and + * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with + * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC) + * along with other APIs. + */ +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/pm.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/sfi.h> +#include <linux/module.h> +#include <asm/intel-mid.h> +#include <asm/intel_scu_ipc.h> + +/* IPC defines the following message types */ +#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */ +#define IPCMSG_BATTERY 0xEF /* Coulomb Counter Accumulator */ +#define IPCMSG_FW_UPDATE 0xFE /* Firmware update */ +#define IPCMSG_PCNTRL 0xFF /* Power controller unit read/write */ +#define IPCMSG_FW_REVISION 0xF4 /* Get firmware revision */ + +/* Command id associated with message IPCMSG_PCNTRL */ +#define IPC_CMD_PCNTRL_W 0 /* Register write */ +#define IPC_CMD_PCNTRL_R 1 /* Register read */ +#define IPC_CMD_PCNTRL_M 2 /* Register read-modify-write */ + +/* + * IPC register summary + * + * IPC register blocks are memory mapped at fixed address of 0xFF11C000 + * To read or write information to the SCU, driver writes to IPC-1 memory + * mapped registers (base address 0xFF11C000). The following is the IPC + * mechanism + * + * 1. IA core cDMI interface claims this transaction and converts it to a + * Transaction Layer Packet (TLP) message which is sent across the cDMI. + * + * 2. South Complex cDMI block receives this message and writes it to + * the IPC-1 register block, causing an interrupt to the SCU + * + * 3. SCU firmware decodes this interrupt and IPC message and the appropriate + * message handler is called within firmware. + */ + +#define IPC_WWBUF_SIZE 20 /* IPC Write buffer Size */ +#define IPC_RWBUF_SIZE 20 /* IPC Read buffer Size */ +#define IPC_IOC 0x100 /* IPC command register IOC bit */ + +#define PCI_DEVICE_ID_LINCROFT 0x082a +#define PCI_DEVICE_ID_PENWELL 0x080e +#define PCI_DEVICE_ID_CLOVERVIEW 0x08ea +#define PCI_DEVICE_ID_TANGIER 0x11a0 + +/* intel scu ipc driver data*/ +struct intel_scu_ipc_pdata_t { + u32 ipc_base; + u32 i2c_base; + u32 ipc_len; + u32 i2c_len; + u8 irq_mode; +}; + +static struct intel_scu_ipc_pdata_t intel_scu_ipc_lincroft_pdata = { + .ipc_base = 0xff11c000, + .i2c_base = 0xff12b000, + .ipc_len = 0x100, + .i2c_len = 0x10, + .irq_mode = 0, +}; + +/* Penwell and Cloverview */ +static struct intel_scu_ipc_pdata_t intel_scu_ipc_penwell_pdata = { + .ipc_base = 0xff11c000, + .i2c_base = 0xff12b000, + .ipc_len = 0x100, + .i2c_len = 0x10, + .irq_mode = 1, +}; + +static struct intel_scu_ipc_pdata_t intel_scu_ipc_tangier_pdata = { + .ipc_base = 0xff009000, + .i2c_base = 0xff00d000, + .ipc_len = 0x100, + .i2c_len = 0x10, + .irq_mode = 0, +}; + +static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id); +static void ipc_remove(struct pci_dev *pdev); + +struct intel_scu_ipc_dev { + struct pci_dev *pdev; + void __iomem *ipc_base; + void __iomem *i2c_base; + struct completion cmd_complete; + u8 irq_mode; +}; + +static struct intel_scu_ipc_dev ipcdev; /* Only one for now */ + +static int platform; /* Platform type */ + +/* + * IPC Read Buffer (Read Only): + * 16 byte buffer for receiving data from SCU, if IPC command + * processing results in response data + */ +#define IPC_READ_BUFFER 0x90 + +#define IPC_I2C_CNTRL_ADDR 0 +#define I2C_DATA_ADDR 0x04 + +static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */ + +/* + * Command Register (Write Only): + * A write to this register results in an interrupt to the SCU core processor + * Format: + * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)| + */ +static inline void ipc_command(u32 cmd) /* Send ipc command */ +{ + if (ipcdev.irq_mode) { + reinit_completion(&ipcdev.cmd_complete); + writel(cmd | IPC_IOC, ipcdev.ipc_base); + } + writel(cmd, ipcdev.ipc_base); +} + +/* + * IPC Write Buffer (Write Only): + * 16-byte buffer for sending data associated with IPC command to + * SCU. Size of the data is specified in the IPC_COMMAND_REG register + */ +static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */ +{ + writel(data, ipcdev.ipc_base + 0x80 + offset); +} + +/* + * Status Register (Read Only): + * Driver will read this register to get the ready/busy status of the IPC + * block and error status of the IPC command that was just processed by SCU + * Format: + * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)| + */ + +static inline u8 ipc_read_status(void) +{ + return __raw_readl(ipcdev.ipc_base + 0x04); +} + +static inline u8 ipc_data_readb(u32 offset) /* Read ipc byte data */ +{ + return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset); +} + +static inline u32 ipc_data_readl(u32 offset) /* Read ipc u32 data */ +{ + return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset); +} + +static inline int busy_loop(void) /* Wait till scu status is busy */ +{ + u32 status = 0; + u32 loop_count = 0; + + status = ipc_read_status(); + while (status & 1) { + udelay(1); /* scu processing time is in few u secods */ + status = ipc_read_status(); + loop_count++; + /* break if scu doesn't reset busy bit after huge retry */ + if (loop_count > 100000) { + dev_err(&ipcdev.pdev->dev, "IPC timed out"); + return -ETIMEDOUT; + } + } + if ((status >> 1) & 1) + return -EIO; + + return 0; +} + +/* Wait till ipc ioc interrupt is received or timeout in 3 HZ */ +static inline int ipc_wait_for_interrupt(void) +{ + int status; + + if (!wait_for_completion_timeout(&ipcdev.cmd_complete, 3 * HZ)) { + struct device *dev = &ipcdev.pdev->dev; + dev_err(dev, "IPC timed out\n"); + return -ETIMEDOUT; + } + + status = ipc_read_status(); + + if ((status >> 1) & 1) + return -EIO; + + return 0; +} + +int intel_scu_ipc_check_status(void) +{ + return ipcdev.irq_mode ? ipc_wait_for_interrupt() : busy_loop(); +} + +/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */ +static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id) +{ + int nc; + u32 offset = 0; + int err; + u8 cbuf[IPC_WWBUF_SIZE] = { }; + u32 *wbuf = (u32 *)&cbuf; + + mutex_lock(&ipclock); + + memset(cbuf, 0, sizeof(cbuf)); + + if (ipcdev.pdev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } + + for (nc = 0; nc < count; nc++, offset += 2) { + cbuf[offset] = addr[nc]; + cbuf[offset + 1] = addr[nc] >> 8; + } + + if (id == IPC_CMD_PCNTRL_R) { + for (nc = 0, offset = 0; nc < count; nc++, offset += 4) + ipc_data_writel(wbuf[nc], offset); + ipc_command((count*2) << 16 | id << 12 | 0 << 8 | op); + } else if (id == IPC_CMD_PCNTRL_W) { + for (nc = 0; nc < count; nc++, offset += 1) + cbuf[offset] = data[nc]; + for (nc = 0, offset = 0; nc < count; nc++, offset += 4) + ipc_data_writel(wbuf[nc], offset); + ipc_command((count*3) << 16 | id << 12 | 0 << 8 | op); + } else if (id == IPC_CMD_PCNTRL_M) { + cbuf[offset] = data[0]; + cbuf[offset + 1] = data[1]; + ipc_data_writel(wbuf[0], 0); /* Write wbuff */ + ipc_command(4 << 16 | id << 12 | 0 << 8 | op); + } + + err = intel_scu_ipc_check_status(); + if (!err && id == IPC_CMD_PCNTRL_R) { /* Read rbuf */ + /* Workaround: values are read as 0 without memcpy_fromio */ + memcpy_fromio(cbuf, ipcdev.ipc_base + 0x90, 16); + for (nc = 0; nc < count; nc++) + data[nc] = ipc_data_readb(nc); + } + mutex_unlock(&ipclock); + return err; +} + +/** + * intel_scu_ipc_ioread8 - read a word via the SCU + * @addr: register on SCU + * @data: return pointer for read byte + * + * Read a single register. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * This function may sleep. + */ +int intel_scu_ipc_ioread8(u16 addr, u8 *data) +{ + return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R); +} +EXPORT_SYMBOL(intel_scu_ipc_ioread8); + +/** + * intel_scu_ipc_ioread16 - read a word via the SCU + * @addr: register on SCU + * @data: return pointer for read word + * + * Read a register pair. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * This function may sleep. + */ +int intel_scu_ipc_ioread16(u16 addr, u16 *data) +{ + u16 x[2] = {addr, addr + 1 }; + return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R); +} +EXPORT_SYMBOL(intel_scu_ipc_ioread16); + +/** + * intel_scu_ipc_ioread32 - read a dword via the SCU + * @addr: register on SCU + * @data: return pointer for read dword + * + * Read four registers. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * This function may sleep. + */ +int intel_scu_ipc_ioread32(u16 addr, u32 *data) +{ + u16 x[4] = {addr, addr + 1, addr + 2, addr + 3}; + return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R); +} +EXPORT_SYMBOL(intel_scu_ipc_ioread32); + +/** + * intel_scu_ipc_iowrite8 - write a byte via the SCU + * @addr: register on SCU + * @data: byte to write + * + * Write a single register. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * This function may sleep. + */ +int intel_scu_ipc_iowrite8(u16 addr, u8 data) +{ + return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W); +} +EXPORT_SYMBOL(intel_scu_ipc_iowrite8); + +/** + * intel_scu_ipc_iowrite16 - write a word via the SCU + * @addr: register on SCU + * @data: word to write + * + * Write two registers. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * This function may sleep. + */ +int intel_scu_ipc_iowrite16(u16 addr, u16 data) +{ + u16 x[2] = {addr, addr + 1 }; + return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W); +} +EXPORT_SYMBOL(intel_scu_ipc_iowrite16); + +/** + * intel_scu_ipc_iowrite32 - write a dword via the SCU + * @addr: register on SCU + * @data: dword to write + * + * Write four registers. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * This function may sleep. + */ +int intel_scu_ipc_iowrite32(u16 addr, u32 data) +{ + u16 x[4] = {addr, addr + 1, addr + 2, addr + 3}; + return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W); +} +EXPORT_SYMBOL(intel_scu_ipc_iowrite32); + +/** + * intel_scu_ipc_readvv - read a set of registers + * @addr: register list + * @data: bytes to return + * @len: length of array + * + * Read registers. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * The largest array length permitted by the hardware is 5 items. + * + * This function may sleep. + */ +int intel_scu_ipc_readv(u16 *addr, u8 *data, int len) +{ + return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R); +} +EXPORT_SYMBOL(intel_scu_ipc_readv); + +/** + * intel_scu_ipc_writev - write a set of registers + * @addr: register list + * @data: bytes to write + * @len: length of array + * + * Write registers. Returns 0 on success or an error code. All + * locking between SCU accesses is handled for the caller. + * + * The largest array length permitted by the hardware is 5 items. + * + * This function may sleep. + * + */ +int intel_scu_ipc_writev(u16 *addr, u8 *data, int len) +{ + return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W); +} +EXPORT_SYMBOL(intel_scu_ipc_writev); + + +/** + * intel_scu_ipc_update_register - r/m/w a register + * @addr: register address + * @bits: bits to update + * @mask: mask of bits to update + * + * Read-modify-write power control unit register. The first data argument + * must be register value and second is mask value + * mask is a bitmap that indicates which bits to update. + * 0 = masked. Don't modify this bit, 1 = modify this bit. + * returns 0 on success or an error code. + * + * This function may sleep. Locking between SCU accesses is handled + * for the caller. + */ +int intel_scu_ipc_update_register(u16 addr, u8 bits, u8 mask) +{ + u8 data[2] = { bits, mask }; + return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M); +} +EXPORT_SYMBOL(intel_scu_ipc_update_register); + +/** + * intel_scu_ipc_simple_command - send a simple command + * @cmd: command + * @sub: sub type + * + * Issue a simple command to the SCU. Do not use this interface if + * you must then access data as any data values may be overwritten + * by another SCU access by the time this function returns. + * + * This function may sleep. Locking for SCU accesses is handled for + * the caller. + */ +int intel_scu_ipc_simple_command(int cmd, int sub) +{ + int err; + + mutex_lock(&ipclock); + if (ipcdev.pdev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } + ipc_command(sub << 12 | cmd); + err = intel_scu_ipc_check_status(); + mutex_unlock(&ipclock); + return err; +} +EXPORT_SYMBOL(intel_scu_ipc_simple_command); + +/** + * intel_scu_ipc_command - command with data + * @cmd: command + * @sub: sub type + * @in: input data + * @inlen: input length in dwords + * @out: output data + * @outlein: output length in dwords + * + * Issue a command to the SCU which involves data transfers. Do the + * data copies under the lock but leave it for the caller to interpret + */ + +int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen, + u32 *out, int outlen) +{ + int i, err; + + mutex_lock(&ipclock); + if (ipcdev.pdev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } + + for (i = 0; i < inlen; i++) + ipc_data_writel(*in++, 4 * i); + + ipc_command((inlen << 16) | (sub << 12) | cmd); + err = intel_scu_ipc_check_status(); + + if (!err) { + for (i = 0; i < outlen; i++) + *out++ = ipc_data_readl(4 * i); + } + + mutex_unlock(&ipclock); + return err; +} +EXPORT_SYMBOL(intel_scu_ipc_command); + +/*I2C commands */ +#define IPC_I2C_WRITE 1 /* I2C Write command */ +#define IPC_I2C_READ 2 /* I2C Read command */ + +/** + * intel_scu_ipc_i2c_cntrl - I2C read/write operations + * @addr: I2C address + command bits + * @data: data to read/write + * + * Perform an an I2C read/write operation via the SCU. All locking is + * handled for the caller. This function may sleep. + * + * Returns an error code or 0 on success. + * + * This has to be in the IPC driver for the locking. + */ +int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data) +{ + u32 cmd = 0; + + mutex_lock(&ipclock); + if (ipcdev.pdev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } + cmd = (addr >> 24) & 0xFF; + if (cmd == IPC_I2C_READ) { + writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR); + /* Write not getting updated without delay */ + mdelay(1); + *data = readl(ipcdev.i2c_base + I2C_DATA_ADDR); + } else if (cmd == IPC_I2C_WRITE) { + writel(*data, ipcdev.i2c_base + I2C_DATA_ADDR); + mdelay(1); + writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR); + } else { + dev_err(&ipcdev.pdev->dev, + "intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd); + + mutex_unlock(&ipclock); + return -EIO; + } + mutex_unlock(&ipclock); + return 0; +} +EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl); + +/* + * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1 + * When ioc bit is set to 1, caller api must wait for interrupt handler called + * which in turn unlocks the caller api. Currently this is not used + * + * This is edge triggered so we need take no action to clear anything + */ +static irqreturn_t ioc(int irq, void *dev_id) +{ + if (ipcdev.irq_mode) + complete(&ipcdev.cmd_complete); + + return IRQ_HANDLED; +} + +/** + * ipc_probe - probe an Intel SCU IPC + * @dev: the PCI device matching + * @id: entry in the match table + * + * Enable and install an intel SCU IPC. This appears in the PCI space + * but uses some hard coded addresses as well. + */ +static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int err; + struct intel_scu_ipc_pdata_t *pdata; + resource_size_t pci_resource; + + if (ipcdev.pdev) /* We support only one SCU */ + return -EBUSY; + + pdata = (struct intel_scu_ipc_pdata_t *)id->driver_data; + + ipcdev.pdev = pci_dev_get(dev); + ipcdev.irq_mode = pdata->irq_mode; + + err = pci_enable_device(dev); + if (err) + return err; + + err = pci_request_regions(dev, "intel_scu_ipc"); + if (err) + return err; + + pci_resource = pci_resource_start(dev, 0); + if (!pci_resource) + return -ENOMEM; + + init_completion(&ipcdev.cmd_complete); + + if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev)) + return -EBUSY; + + ipcdev.ipc_base = ioremap_nocache(pdata->ipc_base, pdata->ipc_len); + if (!ipcdev.ipc_base) + return -ENOMEM; + + ipcdev.i2c_base = ioremap_nocache(pdata->i2c_base, pdata->i2c_len); + if (!ipcdev.i2c_base) { + iounmap(ipcdev.ipc_base); + return -ENOMEM; + } + + intel_scu_devices_create(); + + return 0; +} + +/** + * ipc_remove - remove a bound IPC device + * @pdev: PCI device + * + * In practice the SCU is not removable but this function is also + * called for each device on a module unload or cleanup which is the + * path that will get used. + * + * Free up the mappings and release the PCI resources + */ +static void ipc_remove(struct pci_dev *pdev) +{ + free_irq(pdev->irq, &ipcdev); + pci_release_regions(pdev); + pci_dev_put(ipcdev.pdev); + iounmap(ipcdev.ipc_base); + iounmap(ipcdev.i2c_base); + ipcdev.pdev = NULL; + intel_scu_devices_destroy(); +} + +static DEFINE_PCI_DEVICE_TABLE(pci_ids) = { + { + PCI_VDEVICE(INTEL, PCI_DEVICE_ID_LINCROFT), + (kernel_ulong_t)&intel_scu_ipc_lincroft_pdata, + }, { + PCI_VDEVICE(INTEL, PCI_DEVICE_ID_PENWELL), + (kernel_ulong_t)&intel_scu_ipc_penwell_pdata, + }, { + PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CLOVERVIEW), + (kernel_ulong_t)&intel_scu_ipc_penwell_pdata, + }, { + PCI_VDEVICE(INTEL, PCI_DEVICE_ID_TANGIER), + (kernel_ulong_t)&intel_scu_ipc_tangier_pdata, + }, { + 0, + } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver ipc_driver = { + .name = "intel_scu_ipc", + .id_table = pci_ids, + .probe = ipc_probe, + .remove = ipc_remove, +}; + + +static int __init intel_scu_ipc_init(void) +{ + platform = intel_mid_identify_cpu(); + if (platform == 0) + return -ENODEV; + return pci_register_driver(&ipc_driver); +} + +static void __exit intel_scu_ipc_exit(void) +{ + pci_unregister_driver(&ipc_driver); +} + +MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>"); +MODULE_DESCRIPTION("Intel SCU IPC driver"); +MODULE_LICENSE("GPL"); + +module_init(intel_scu_ipc_init); +module_exit(intel_scu_ipc_exit); diff --git a/drivers/platform/x86/intel_scu_ipcutil.c b/drivers/platform/x86/intel_scu_ipcutil.c new file mode 100644 index 00000000000..02bc5a6343c --- /dev/null +++ b/drivers/platform/x86/intel_scu_ipcutil.c @@ -0,0 +1,121 @@ +/* + * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism + * + * (C) Copyright 2008-2010 Intel Corporation + * Author: Sreedhara DS (sreedhara.ds@intel.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This driver provides ioctl interfaces to call intel scu ipc driver api + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <asm/intel_scu_ipc.h> + +static int major; + +/* ioctl commnds */ +#define INTE_SCU_IPC_REGISTER_READ 0 +#define INTE_SCU_IPC_REGISTER_WRITE 1 +#define INTE_SCU_IPC_REGISTER_UPDATE 2 + +struct scu_ipc_data { + u32 count; /* No. of registers */ + u16 addr[5]; /* Register addresses */ + u8 data[5]; /* Register data */ + u8 mask; /* Valid for read-modify-write */ +}; + +/** + * scu_reg_access - implement register access ioctls + * @cmd: command we are doing (read/write/update) + * @data: kernel copy of ioctl data + * + * Allow the user to perform register accesses on the SCU via the + * kernel interface + */ + +static int scu_reg_access(u32 cmd, struct scu_ipc_data *data) +{ + int count = data->count; + + if (count == 0 || count == 3 || count > 4) + return -EINVAL; + + switch (cmd) { + case INTE_SCU_IPC_REGISTER_READ: + return intel_scu_ipc_readv(data->addr, data->data, count); + case INTE_SCU_IPC_REGISTER_WRITE: + return intel_scu_ipc_writev(data->addr, data->data, count); + case INTE_SCU_IPC_REGISTER_UPDATE: + return intel_scu_ipc_update_register(data->addr[0], + data->data[0], data->mask); + default: + return -ENOTTY; + } +} + +/** + * scu_ipc_ioctl - control ioctls for the SCU + * @fp: file handle of the SCU device + * @cmd: ioctl coce + * @arg: pointer to user passed structure + * + * Support the I/O and firmware flashing interfaces of the SCU + */ +static long scu_ipc_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + int ret; + struct scu_ipc_data data; + void __user *argp = (void __user *)arg; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + if (copy_from_user(&data, argp, sizeof(struct scu_ipc_data))) + return -EFAULT; + ret = scu_reg_access(cmd, &data); + if (ret < 0) + return ret; + if (copy_to_user(argp, &data, sizeof(struct scu_ipc_data))) + return -EFAULT; + return 0; +} + +static const struct file_operations scu_ipc_fops = { + .unlocked_ioctl = scu_ipc_ioctl, +}; + +static int __init ipc_module_init(void) +{ + major = register_chrdev(0, "intel_mid_scu", &scu_ipc_fops); + if (major < 0) + return major; + + return 0; +} + +static void __exit ipc_module_exit(void) +{ + unregister_chrdev(major, "intel_mid_scu"); +} + +module_init(ipc_module_init); +module_exit(ipc_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Utility driver for intel scu ipc"); +MODULE_AUTHOR("Sreedhara <sreedhara.ds@intel.com>"); diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index 759763d18e4..62f8030b9e7 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -51,6 +51,8 @@ * laptop as MSI S270. YMMV. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> @@ -58,6 +60,10 @@ #include <linux/dmi.h> #include <linux/backlight.h> #include <linux/platform_device.h> +#include <linux/rfkill.h> +#include <linux/i8042.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> #define MSI_DRIVER_VERSION "0.5" @@ -66,7 +72,38 @@ #define MSI_EC_COMMAND_WIRELESS 0x10 #define MSI_EC_COMMAND_LCD_LEVEL 0x11 -static int force; +#define MSI_STANDARD_EC_COMMAND_ADDRESS 0x2e +#define MSI_STANDARD_EC_BLUETOOTH_MASK (1 << 0) +#define MSI_STANDARD_EC_WEBCAM_MASK (1 << 1) +#define MSI_STANDARD_EC_WLAN_MASK (1 << 3) +#define MSI_STANDARD_EC_3G_MASK (1 << 4) + +/* For set SCM load flag to disable BIOS fn key */ +#define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d +#define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0) + +#define MSI_STANDARD_EC_FUNCTIONS_ADDRESS 0xe4 +/* Power LED is orange - Turbo mode */ +#define MSI_STANDARD_EC_TURBO_MASK (1 << 1) +/* Power LED is green - ECO mode */ +#define MSI_STANDARD_EC_ECO_MASK (1 << 3) +/* Touchpad is turned on */ +#define MSI_STANDARD_EC_TOUCHPAD_MASK (1 << 4) +/* If this bit != bit 1, turbo mode can't be toggled */ +#define MSI_STANDARD_EC_TURBO_COOLDOWN_MASK (1 << 7) + +#define MSI_STANDARD_EC_FAN_ADDRESS 0x33 +/* If zero, fan rotates at maximal speed */ +#define MSI_STANDARD_EC_AUTOFAN_MASK (1 << 0) + +#ifdef CONFIG_PM_SLEEP +static int msi_laptop_resume(struct device *device); +#endif +static SIMPLE_DEV_PM_OPS(msi_laptop_pm, NULL, msi_laptop_resume); + +#define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f + +static bool force; module_param(force, bool, 0); MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); @@ -74,6 +111,46 @@ static int auto_brightness; module_param(auto_brightness, int, 0); MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)"); +static const struct key_entry msi_laptop_keymap[] = { + {KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} }, /* Touch Pad On */ + {KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },/* Touch Pad On */ + {KE_END, 0} +}; + +static struct input_dev *msi_laptop_input_dev; + +static int wlan_s, bluetooth_s, threeg_s; +static int threeg_exists; +static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg; + +/* MSI laptop quirks */ +struct quirk_entry { + bool old_ec_model; + + /* Some MSI 3G netbook only have one fn key to control + * Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to + * disable the original Wlan/Bluetooth control by BIOS when user press + * fn key, then control Wlan/Bluetooth/3G by SCM (software control by + * OS). Without SCM, user cann't on/off 3G module on those 3G netbook. + * On Linux, msi-laptop driver will do the same thing to disable the + * original BIOS control, then might need use HAL or other userland + * application to do the software control that simulate with SCM. + * e.g. MSI N034 netbook + */ + bool load_scm_model; + + /* Some MSI laptops need delay before reading from EC */ + bool ec_delay; + + /* Some MSI Wind netbooks (e.g. MSI Wind U100) need loading SCM to get + * some features working (e.g. ECO mode), but we cannot change + * Wlan/Bluetooth state in software and we can only read its state. + */ + bool ec_read_only; +}; + +static struct quirk_entry *quirks; + /* Hardware access */ static int set_lcd_level(int level) @@ -86,7 +163,8 @@ static int set_lcd_level(int level) buf[0] = 0x80; buf[1] = (u8) (level*31); - return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0, 1); + return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), + NULL, 0); } static int get_lcd_level(void) @@ -94,7 +172,8 @@ static int get_lcd_level(void) u8 wdata = 0, rdata; int result; - result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1); + result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, + &rdata, 1); if (result < 0) return result; @@ -106,7 +185,8 @@ static int get_auto_brightness(void) u8 wdata = 4, rdata; int result; - result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1); + result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, + &rdata, 1); if (result < 0) return result; @@ -120,14 +200,48 @@ static int set_auto_brightness(int enable) wdata[0] = 4; - result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1, 1); + result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, + &rdata, 1); if (result < 0) return result; wdata[0] = 0x84; wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0); - return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1); + return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, + NULL, 0); +} + +static ssize_t set_device_state(const char *buf, size_t count, u8 mask) +{ + int status; + u8 wdata = 0, rdata; + int result; + + if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1)) + return -EINVAL; + + if (quirks->ec_read_only) + return -EOPNOTSUPP; + + /* read current device state */ + result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata); + if (result < 0) + return result; + + if (!!(rdata & mask) != status) { + /* reverse device bit */ + if (rdata & mask) + wdata = rdata & ~mask; + else + wdata = rdata | mask; + + result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata); + if (result < 0) + return result; + } + + return count; } static int get_wireless_state(int *wlan, int *bluetooth) @@ -135,9 +249,9 @@ static int get_wireless_state(int *wlan, int *bluetooth) u8 wdata = 0, rdata; int result; - result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1, 1); + result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1); if (result < 0) - return -1; + return result; if (wlan) *wlan = !!(rdata & 8); @@ -148,6 +262,38 @@ static int get_wireless_state(int *wlan, int *bluetooth) return 0; } +static int get_wireless_state_ec_standard(void) +{ + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata); + if (result < 0) + return result; + + wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK); + + bluetooth_s = !!(rdata & MSI_STANDARD_EC_BLUETOOTH_MASK); + + threeg_s = !!(rdata & MSI_STANDARD_EC_3G_MASK); + + return 0; +} + +static int get_threeg_exists(void) +{ + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata); + if (result < 0) + return result; + + threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK); + + return 0; +} + /* Backlight device stuff */ static int bl_get_brightness(struct backlight_device *b) @@ -161,7 +307,7 @@ static int bl_update_status(struct backlight_device *b) return set_lcd_level(b->props.brightness); } -static struct backlight_ops msibl_ops = { +static const struct backlight_ops msibl_ops = { .get_brightness = bl_get_brightness, .update_status = bl_update_status, }; @@ -174,28 +320,73 @@ static ssize_t show_wlan(struct device *dev, struct device_attribute *attr, char *buf) { - int ret, enabled; + int ret, enabled = 0; - ret = get_wireless_state(&enabled, NULL); + if (quirks->old_ec_model) { + ret = get_wireless_state(&enabled, NULL); + } else { + ret = get_wireless_state_ec_standard(); + enabled = wlan_s; + } if (ret < 0) return ret; return sprintf(buf, "%i\n", enabled); } +static ssize_t store_wlan(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK); +} + static ssize_t show_bluetooth(struct device *dev, struct device_attribute *attr, char *buf) { - int ret, enabled; + int ret, enabled = 0; - ret = get_wireless_state(NULL, &enabled); + if (quirks->old_ec_model) { + ret = get_wireless_state(NULL, &enabled); + } else { + ret = get_wireless_state_ec_standard(); + enabled = bluetooth_s; + } if (ret < 0) return ret; return sprintf(buf, "%i\n", enabled); } +static ssize_t store_bluetooth(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK); +} + +static ssize_t show_threeg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + int ret; + + /* old msi ec not support 3G */ + if (quirks->old_ec_model) + return -ENODEV; + + ret = get_wireless_state_ec_standard(); + if (ret < 0) + return ret; + + return sprintf(buf, "%i\n", threeg_s); +} + +static ssize_t store_threeg(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK); +} + static ssize_t show_lcd_level(struct device *dev, struct device_attribute *attr, char *buf) { @@ -215,7 +406,8 @@ static ssize_t store_lcd_level(struct device *dev, int level, ret; - if (sscanf(buf, "%i", &level) != 1 || (level < 0 || level >= MSI_LCD_LEVEL_MAX)) + if (sscanf(buf, "%i", &level) != 1 || + (level < 0 || level >= MSI_LCD_LEVEL_MAX)) return -EINVAL; ret = set_lcd_level(level); @@ -254,16 +446,119 @@ static ssize_t store_auto_brightness(struct device *dev, return count; } +static ssize_t show_touchpad(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata); + if (result < 0) + return result; + + return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK)); +} + +static ssize_t show_turbo(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata); + if (result < 0) + return result; + + return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK)); +} + +static ssize_t show_eco(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata); + if (result < 0) + return result; + + return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK)); +} + +static ssize_t show_turbo_cooldown(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata); + if (result < 0) + return result; + + return sprintf(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) | + (!!(rdata & MSI_STANDARD_EC_TURBO_COOLDOWN_MASK) << 1)); +} + +static ssize_t show_auto_fan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_FAN_ADDRESS, &rdata); + if (result < 0) + return result; + + return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK)); +} + +static ssize_t store_auto_fan(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + + int enable, result; + + if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1))) + return -EINVAL; + + result = ec_write(MSI_STANDARD_EC_FAN_ADDRESS, enable); + if (result < 0) + return result; + + return count; +} + static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level); -static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, store_auto_brightness); +static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, + store_auto_brightness); static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL); static DEVICE_ATTR(wlan, 0444, show_wlan, NULL); +static DEVICE_ATTR(threeg, 0444, show_threeg, NULL); +static DEVICE_ATTR(touchpad, 0444, show_touchpad, NULL); +static DEVICE_ATTR(turbo_mode, 0444, show_turbo, NULL); +static DEVICE_ATTR(eco_mode, 0444, show_eco, NULL); +static DEVICE_ATTR(turbo_cooldown, 0444, show_turbo_cooldown, NULL); +static DEVICE_ATTR(auto_fan, 0644, show_auto_fan, store_auto_fan); static struct attribute *msipf_attributes[] = { - &dev_attr_lcd_level.attr, - &dev_attr_auto_brightness.attr, &dev_attr_bluetooth.attr, &dev_attr_wlan.attr, + &dev_attr_touchpad.attr, + &dev_attr_turbo_mode.attr, + &dev_attr_eco_mode.attr, + &dev_attr_turbo_cooldown.attr, + &dev_attr_auto_fan.attr, + NULL +}; + +static struct attribute *msipf_old_attributes[] = { + &dev_attr_lcd_level.attr, + &dev_attr_auto_brightness.attr, NULL }; @@ -271,21 +566,43 @@ static struct attribute_group msipf_attribute_group = { .attrs = msipf_attributes }; +static struct attribute_group msipf_old_attribute_group = { + .attrs = msipf_old_attributes +}; + static struct platform_driver msipf_driver = { .driver = { .name = "msi-laptop-pf", .owner = THIS_MODULE, - } + .pm = &msi_laptop_pm, + }, }; static struct platform_device *msipf_device; /* Initialization */ -static int dmi_check_cb(const struct dmi_system_id *id) +static struct quirk_entry quirk_old_ec_model = { + .old_ec_model = true, +}; + +static struct quirk_entry quirk_load_scm_model = { + .load_scm_model = true, + .ec_delay = true, +}; + +static struct quirk_entry quirk_load_scm_ro_model = { + .load_scm_model = true, + .ec_read_only = true, +}; + +static int dmi_check_cb(const struct dmi_system_id *dmi) { - printk("msi-laptop: Identified laptop model '%s'.\n", id->ident); - return 0; + pr_info("Identified laptop model '%s'\n", dmi->ident); + + quirks = dmi->driver_data; + + return 1; } static struct dmi_system_id __initdata msi_dmi_table[] = { @@ -295,8 +612,10 @@ static struct dmi_system_id __initdata msi_dmi_table[] = { DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"), DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"), DMI_MATCH(DMI_PRODUCT_VERSION, "0131"), - DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD") + DMI_MATCH(DMI_CHASSIS_VENDOR, + "MICRO-STAR INT'L CO.,LTD") }, + .driver_data = &quirk_old_ec_model, .callback = dmi_check_cb }, { @@ -307,6 +626,7 @@ static struct dmi_system_id __initdata msi_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_VERSION, "0581"), DMI_MATCH(DMI_BOARD_NAME, "MS-1058") }, + .driver_data = &quirk_old_ec_model, .callback = dmi_check_cb }, { @@ -317,6 +637,7 @@ static struct dmi_system_id __initdata msi_dmi_table[] = { DMI_MATCH(DMI_BOARD_VENDOR, "MSI"), DMI_MATCH(DMI_BOARD_NAME, "MS-1412") }, + .driver_data = &quirk_old_ec_model, .callback = dmi_check_cb }, { @@ -325,13 +646,408 @@ static struct dmi_system_id __initdata msi_dmi_table[] = { DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"), DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"), DMI_MATCH(DMI_PRODUCT_VERSION, "0131"), - DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD") + DMI_MATCH(DMI_CHASSIS_VENDOR, + "MICRO-STAR INT'L CO.,LTD") }, + .driver_data = &quirk_old_ec_model, + .callback = dmi_check_cb + }, + { + .ident = "MSI N034", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "MICRO-STAR INTERNATIONAL CO., LTD"), + DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034"), + DMI_MATCH(DMI_CHASSIS_VENDOR, + "MICRO-STAR INTERNATIONAL CO., LTD") + }, + .driver_data = &quirk_load_scm_model, + .callback = dmi_check_cb + }, + { + .ident = "MSI N051", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "MICRO-STAR INTERNATIONAL CO., LTD"), + DMI_MATCH(DMI_PRODUCT_NAME, "MS-N051"), + DMI_MATCH(DMI_CHASSIS_VENDOR, + "MICRO-STAR INTERNATIONAL CO., LTD") + }, + .driver_data = &quirk_load_scm_model, + .callback = dmi_check_cb + }, + { + .ident = "MSI N014", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "MICRO-STAR INTERNATIONAL CO., LTD"), + DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"), + }, + .driver_data = &quirk_load_scm_model, + .callback = dmi_check_cb + }, + { + .ident = "MSI CR620", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "Micro-Star International"), + DMI_MATCH(DMI_PRODUCT_NAME, "CR620"), + }, + .driver_data = &quirk_load_scm_model, + .callback = dmi_check_cb + }, + { + .ident = "MSI U270", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "U270 series"), + }, + .driver_data = &quirk_load_scm_model, + .callback = dmi_check_cb + }, + { + .ident = "MSI U90/U100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "MICRO-STAR INTERNATIONAL CO., LTD"), + DMI_MATCH(DMI_PRODUCT_NAME, "U90/U100"), + }, + .driver_data = &quirk_load_scm_ro_model, .callback = dmi_check_cb }, { } }; +static int rfkill_bluetooth_set(void *data, bool blocked) +{ + /* Do something with blocked...*/ + /* + * blocked == false is on + * blocked == true is off + */ + int result = set_device_state(blocked ? "0" : "1", 0, + MSI_STANDARD_EC_BLUETOOTH_MASK); + + return min(result, 0); +} + +static int rfkill_wlan_set(void *data, bool blocked) +{ + int result = set_device_state(blocked ? "0" : "1", 0, + MSI_STANDARD_EC_WLAN_MASK); + + return min(result, 0); +} + +static int rfkill_threeg_set(void *data, bool blocked) +{ + int result = set_device_state(blocked ? "0" : "1", 0, + MSI_STANDARD_EC_3G_MASK); + + return min(result, 0); +} + +static const struct rfkill_ops rfkill_bluetooth_ops = { + .set_block = rfkill_bluetooth_set +}; + +static const struct rfkill_ops rfkill_wlan_ops = { + .set_block = rfkill_wlan_set +}; + +static const struct rfkill_ops rfkill_threeg_ops = { + .set_block = rfkill_threeg_set +}; + +static void rfkill_cleanup(void) +{ + if (rfk_bluetooth) { + rfkill_unregister(rfk_bluetooth); + rfkill_destroy(rfk_bluetooth); + } + + if (rfk_threeg) { + rfkill_unregister(rfk_threeg); + rfkill_destroy(rfk_threeg); + } + + if (rfk_wlan) { + rfkill_unregister(rfk_wlan); + rfkill_destroy(rfk_wlan); + } +} + +static bool msi_rfkill_set_state(struct rfkill *rfkill, bool blocked) +{ + if (quirks->ec_read_only) + return rfkill_set_hw_state(rfkill, blocked); + else + return rfkill_set_sw_state(rfkill, blocked); +} + +static void msi_update_rfkill(struct work_struct *ignored) +{ + get_wireless_state_ec_standard(); + + if (rfk_wlan) + msi_rfkill_set_state(rfk_wlan, !wlan_s); + if (rfk_bluetooth) + msi_rfkill_set_state(rfk_bluetooth, !bluetooth_s); + if (rfk_threeg) + msi_rfkill_set_state(rfk_threeg, !threeg_s); +} +static DECLARE_DELAYED_WORK(msi_rfkill_dwork, msi_update_rfkill); +static DECLARE_WORK(msi_rfkill_work, msi_update_rfkill); + +static void msi_send_touchpad_key(struct work_struct *ignored) +{ + u8 rdata; + int result; + + result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata); + if (result < 0) + return; + + sparse_keymap_report_event(msi_laptop_input_dev, + (rdata & MSI_STANDARD_EC_TOUCHPAD_MASK) ? + KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF, 1, true); +} +static DECLARE_DELAYED_WORK(msi_touchpad_dwork, msi_send_touchpad_key); +static DECLARE_WORK(msi_touchpad_work, msi_send_touchpad_key); + +static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + static bool extended; + + if (str & 0x20) + return false; + + /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan, 0xE4 touchpad toggle*/ + if (unlikely(data == 0xe0)) { + extended = true; + return false; + } else if (unlikely(extended)) { + extended = false; + switch (data) { + case 0xE4: + if (quirks->ec_delay) { + schedule_delayed_work(&msi_touchpad_dwork, + round_jiffies_relative(0.5 * HZ)); + } else + schedule_work(&msi_touchpad_work); + break; + case 0x54: + case 0x62: + case 0x76: + if (quirks->ec_delay) { + schedule_delayed_work(&msi_rfkill_dwork, + round_jiffies_relative(0.5 * HZ)); + } else + schedule_work(&msi_rfkill_work); + break; + } + } + + return false; +} + +static void msi_init_rfkill(struct work_struct *ignored) +{ + if (rfk_wlan) { + rfkill_set_sw_state(rfk_wlan, !wlan_s); + rfkill_wlan_set(NULL, !wlan_s); + } + if (rfk_bluetooth) { + rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s); + rfkill_bluetooth_set(NULL, !bluetooth_s); + } + if (rfk_threeg) { + rfkill_set_sw_state(rfk_threeg, !threeg_s); + rfkill_threeg_set(NULL, !threeg_s); + } +} +static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill); + +static int rfkill_init(struct platform_device *sdev) +{ + /* add rfkill */ + int retval; + + /* keep the hardware wireless state */ + get_wireless_state_ec_standard(); + + rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev, + RFKILL_TYPE_BLUETOOTH, + &rfkill_bluetooth_ops, NULL); + if (!rfk_bluetooth) { + retval = -ENOMEM; + goto err_bluetooth; + } + retval = rfkill_register(rfk_bluetooth); + if (retval) + goto err_bluetooth; + + rfk_wlan = rfkill_alloc("msi-wlan", &sdev->dev, RFKILL_TYPE_WLAN, + &rfkill_wlan_ops, NULL); + if (!rfk_wlan) { + retval = -ENOMEM; + goto err_wlan; + } + retval = rfkill_register(rfk_wlan); + if (retval) + goto err_wlan; + + if (threeg_exists) { + rfk_threeg = rfkill_alloc("msi-threeg", &sdev->dev, + RFKILL_TYPE_WWAN, &rfkill_threeg_ops, NULL); + if (!rfk_threeg) { + retval = -ENOMEM; + goto err_threeg; + } + retval = rfkill_register(rfk_threeg); + if (retval) + goto err_threeg; + } + + /* schedule to run rfkill state initial */ + if (quirks->ec_delay) { + schedule_delayed_work(&msi_rfkill_init, + round_jiffies_relative(1 * HZ)); + } else + schedule_work(&msi_rfkill_work); + + return 0; + +err_threeg: + rfkill_destroy(rfk_threeg); + if (rfk_wlan) + rfkill_unregister(rfk_wlan); +err_wlan: + rfkill_destroy(rfk_wlan); + if (rfk_bluetooth) + rfkill_unregister(rfk_bluetooth); +err_bluetooth: + rfkill_destroy(rfk_bluetooth); + + return retval; +} + +#ifdef CONFIG_PM_SLEEP +static int msi_laptop_resume(struct device *device) +{ + u8 data; + int result; + + if (!quirks->load_scm_model) + return 0; + + /* set load SCM to disable hardware control by fn key */ + result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data); + if (result < 0) + return result; + + result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, + data | MSI_STANDARD_EC_SCM_LOAD_MASK); + if (result < 0) + return result; + + return 0; +} +#endif + +static int __init msi_laptop_input_setup(void) +{ + int err; + + msi_laptop_input_dev = input_allocate_device(); + if (!msi_laptop_input_dev) + return -ENOMEM; + + msi_laptop_input_dev->name = "MSI Laptop hotkeys"; + msi_laptop_input_dev->phys = "msi-laptop/input0"; + msi_laptop_input_dev->id.bustype = BUS_HOST; + + err = sparse_keymap_setup(msi_laptop_input_dev, + msi_laptop_keymap, NULL); + if (err) + goto err_free_dev; + + err = input_register_device(msi_laptop_input_dev); + if (err) + goto err_free_keymap; + + return 0; + +err_free_keymap: + sparse_keymap_free(msi_laptop_input_dev); +err_free_dev: + input_free_device(msi_laptop_input_dev); + return err; +} + +static void msi_laptop_input_destroy(void) +{ + sparse_keymap_free(msi_laptop_input_dev); + input_unregister_device(msi_laptop_input_dev); +} + +static int __init load_scm_model_init(struct platform_device *sdev) +{ + u8 data; + int result; + + if (!quirks->ec_read_only) { + /* allow userland write sysfs file */ + dev_attr_bluetooth.store = store_bluetooth; + dev_attr_wlan.store = store_wlan; + dev_attr_threeg.store = store_threeg; + dev_attr_bluetooth.attr.mode |= S_IWUSR; + dev_attr_wlan.attr.mode |= S_IWUSR; + dev_attr_threeg.attr.mode |= S_IWUSR; + } + + /* disable hardware control by fn key */ + result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data); + if (result < 0) + return result; + + result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, + data | MSI_STANDARD_EC_SCM_LOAD_MASK); + if (result < 0) + return result; + + /* initial rfkill */ + result = rfkill_init(sdev); + if (result < 0) + goto fail_rfkill; + + /* setup input device */ + result = msi_laptop_input_setup(); + if (result) + goto fail_input; + + result = i8042_install_filter(msi_laptop_i8042_filter); + if (result) { + pr_err("Unable to install key filter\n"); + goto fail_filter; + } + + return 0; + +fail_filter: + msi_laptop_input_destroy(); + +fail_input: + rfkill_cleanup(); + +fail_rfkill: + + return result; + +} + static int __init msi_init(void) { int ret; @@ -339,23 +1055,33 @@ static int __init msi_init(void) if (acpi_disabled) return -ENODEV; - if (!force && !dmi_check_system(msi_dmi_table)) - return -ENODEV; + dmi_check_system(msi_dmi_table); + if (!quirks) + /* quirks may be NULL if no match in DMI table */ + quirks = &quirk_load_scm_model; + if (force) + quirks = &quirk_old_ec_model; + + if (!quirks->old_ec_model) + get_threeg_exists(); if (auto_brightness < 0 || auto_brightness > 2) return -EINVAL; /* Register backlight stuff */ - if (acpi_video_backlight_support()) { - printk(KERN_INFO "MSI: Brightness ignored, must be controlled " - "by ACPI video driver\n"); + if (!quirks->old_ec_model || acpi_video_backlight_support()) { + pr_info("Brightness ignored, must be controlled by ACPI video driver\n"); } else { + struct backlight_properties props; + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = MSI_LCD_LEVEL_MAX - 1; msibl_device = backlight_device_register("msi-laptop-bl", NULL, - NULL, &msibl_ops); + NULL, &msibl_ops, + &props); if (IS_ERR(msibl_device)) return PTR_ERR(msibl_device); - msibl_device->props.max_brightness = MSI_LCD_LEVEL_MAX-1; } ret = platform_driver_register(&msipf_driver); @@ -372,37 +1098,58 @@ static int __init msi_init(void) ret = platform_device_add(msipf_device); if (ret) - goto fail_platform_device1; + goto fail_device_add; + + if (quirks->load_scm_model && (load_scm_model_init(msipf_device) < 0)) { + ret = -EINVAL; + goto fail_scm_model_init; + } - ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group); + ret = sysfs_create_group(&msipf_device->dev.kobj, + &msipf_attribute_group); if (ret) - goto fail_platform_device2; + goto fail_create_group; + + if (!quirks->old_ec_model) { + if (threeg_exists) + ret = device_create_file(&msipf_device->dev, + &dev_attr_threeg); + if (ret) + goto fail_create_attr; + } else { + ret = sysfs_create_group(&msipf_device->dev.kobj, + &msipf_old_attribute_group); + if (ret) + goto fail_create_attr; - /* Disable automatic brightness control by default because - * this module was probably loaded to do brightness control in - * software. */ + /* Disable automatic brightness control by default because + * this module was probably loaded to do brightness control in + * software. */ - if (auto_brightness != 2) - set_auto_brightness(auto_brightness); + if (auto_brightness != 2) + set_auto_brightness(auto_brightness); + } - printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n"); + pr_info("driver " MSI_DRIVER_VERSION " successfully loaded\n"); return 0; -fail_platform_device2: - +fail_create_attr: + sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group); +fail_create_group: + if (quirks->load_scm_model) { + i8042_remove_filter(msi_laptop_i8042_filter); + cancel_delayed_work_sync(&msi_rfkill_dwork); + cancel_work_sync(&msi_rfkill_work); + rfkill_cleanup(); + } +fail_scm_model_init: platform_device_del(msipf_device); - -fail_platform_device1: - +fail_device_add: platform_device_put(msipf_device); - fail_platform_driver: - platform_driver_unregister(&msipf_driver); - fail_backlight: - backlight_device_unregister(msibl_device); return ret; @@ -410,17 +1157,28 @@ fail_backlight: static void __exit msi_cleanup(void) { + if (quirks->load_scm_model) { + i8042_remove_filter(msi_laptop_i8042_filter); + msi_laptop_input_destroy(); + cancel_delayed_work_sync(&msi_rfkill_dwork); + cancel_work_sync(&msi_rfkill_work); + rfkill_cleanup(); + } sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group); + if (!quirks->old_ec_model && threeg_exists) + device_remove_file(&msipf_device->dev, &dev_attr_threeg); platform_device_unregister(msipf_device); platform_driver_unregister(&msipf_driver); backlight_device_unregister(msibl_device); - /* Enable automatic brightness control again */ - if (auto_brightness != 2) - set_auto_brightness(1); + if (quirks->old_ec_model) { + /* Enable automatic brightness control again */ + if (auto_brightness != 2) + set_auto_brightness(1); + } - printk(KERN_INFO "msi-laptop: driver unloaded.\n"); + pr_info("driver unloaded\n"); } module_init(msi_init); @@ -435,3 +1193,9 @@ MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARIN MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*"); MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); +MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*"); +MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*"); +MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*"); +MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*"); +MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnU270series:*"); +MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnU90/U100:*"); diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index f5f70d4c691..70222f265f6 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -20,43 +20,81 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/acpi.h> #include <linux/backlight.h> +#include <linux/slab.h> +#include <linux/module.h> MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>"); MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45"); -MODULE_ALIAS("wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"); - #define DRV_NAME "msi-wmi" -#define DRV_PFX DRV_NAME ": " #define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45" -#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" +#define MSIWMI_MSI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" +#define MSIWMI_WIND_EVENT_GUID "5B3CC38A-40D9-7245-8AE6-1145B751BE3F" + +MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID); +MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID); +MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID); + +enum msi_scancodes { + /* Generic MSI keys (not present on MSI Wind) */ + MSI_KEY_BRIGHTNESSUP = 0xD0, + MSI_KEY_BRIGHTNESSDOWN, + MSI_KEY_VOLUMEUP, + MSI_KEY_VOLUMEDOWN, + MSI_KEY_MUTE, + /* MSI Wind keys */ + WIND_KEY_TOUCHPAD = 0x08, /* Fn+F3 touchpad toggle */ + WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */ + WIND_KEY_CAMERA, /* Fn+F6 webcam toggle */ + WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ + WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ + WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ +}; +static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, + { KE_KEY, MSI_KEY_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} }, + { KE_KEY, MSI_KEY_VOLUMEUP, {KEY_VOLUMEUP} }, + { KE_KEY, MSI_KEY_VOLUMEDOWN, {KEY_VOLUMEDOWN} }, + { KE_KEY, MSI_KEY_MUTE, {KEY_MUTE} }, + + /* These keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, WIND_KEY_TOUCHPAD, {KEY_TOUCHPAD_TOGGLE} }, + { KE_IGNORE, WIND_KEY_BLUETOOTH, {KEY_BLUETOOTH} }, + { KE_IGNORE, WIND_KEY_CAMERA, {KEY_CAMERA} }, + { KE_IGNORE, WIND_KEY_WLAN, {KEY_WLAN} }, + + /* These are unknown WMI events found on MSI Wind */ + { KE_IGNORE, 0x00 }, + { KE_IGNORE, 0x62 }, + { KE_IGNORE, 0x63 }, + + /* These are MSI Wind keys that should be handled via WMI */ + { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, + { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + + { KE_END, 0 } +}; -#define dprintk(msg...) pr_debug(DRV_PFX msg) +static ktime_t last_pressed; -#define KEYCODE_BASE 0xD0 -#define MSI_WMI_BRIGHTNESSUP KEYCODE_BASE -#define MSI_WMI_BRIGHTNESSDOWN (KEYCODE_BASE + 1) -#define MSI_WMI_VOLUMEUP (KEYCODE_BASE + 2) -#define MSI_WMI_VOLUMEDOWN (KEYCODE_BASE + 3) -static struct key_entry msi_wmi_keymap[] = { - { KE_KEY, MSI_WMI_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, - { KE_KEY, MSI_WMI_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} }, - { KE_KEY, MSI_WMI_VOLUMEUP, {KEY_VOLUMEUP} }, - { KE_KEY, MSI_WMI_VOLUMEDOWN, {KEY_VOLUMEDOWN} }, - { KE_END, 0} +static const struct { + const char *guid; + bool quirk_last_pressed; +} *event_wmi, event_wmis[] = { + { MSIWMI_MSI_EVENT_GUID, true }, + { MSIWMI_WIND_EVENT_GUID, false }, }; -static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1]; -struct backlight_device *backlight; +static struct backlight_device *backlight; static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF }; @@ -75,7 +113,7 @@ static int msi_wmi_query_block(int instance, int *ret) if (!obj || obj->type != ACPI_TYPE_INTEGER) { if (obj) { - printk(KERN_ERR DRV_PFX "query block returned object " + pr_err("query block returned object " "type: %d - buffer length:%d\n", obj->type, obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0); @@ -94,8 +132,8 @@ static int msi_wmi_set_block(int instance, int value) struct acpi_buffer input = { sizeof(int), &value }; - dprintk("Going to set block of instance: %d - value: %d\n", - instance, value); + pr_debug("Going to set block of instance: %d - value: %d\n", + instance, value); status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input); @@ -109,20 +147,19 @@ static int bl_get(struct backlight_device *bd) /* Instance 1 is "get backlight", cmp with DSDT */ err = msi_wmi_query_block(1, &ret); if (err) { - printk(KERN_ERR DRV_PFX "Could not query backlight: %d\n", err); + pr_err("Could not query backlight: %d\n", err); return -EINVAL; } - dprintk("Get: Query block returned: %d\n", ret); + pr_debug("Get: Query block returned: %d\n", ret); for (level = 0; level < ARRAY_SIZE(backlight_map); level++) { if (backlight_map[level] == ret) { - dprintk("Current backlight level: 0x%X - index: %d\n", - backlight_map[level], level); + pr_debug("Current backlight level: 0x%X - index: %d\n", + backlight_map[level], level); break; } } if (level == ARRAY_SIZE(backlight_map)) { - printk(KERN_ERR DRV_PFX "get: Invalid brightness value: 0x%X\n", - ret); + pr_err("get: Invalid brightness value: 0x%X\n", ret); return -EINVAL; } return level; @@ -138,7 +175,7 @@ static int bl_set_status(struct backlight_device *bd) return msi_wmi_set_block(0, backlight_map[bright]); } -static struct backlight_ops msi_backlight_ops = { +static const struct backlight_ops msi_backlight_ops = { .get_brightness = bl_get, .update_status = bl_set_status, }; @@ -148,12 +185,11 @@ static void msi_wmi_notify(u32 value, void *context) struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; static struct key_entry *key; union acpi_object *obj; - ktime_t cur; acpi_status status; status = wmi_get_event_data(value, &response); if (status != AE_OK) { - printk(KERN_INFO DRV_PFX "bad event status 0x%x\n", status); + pr_info("bad event status 0x%x\n", status); return; } @@ -161,43 +197,70 @@ static void msi_wmi_notify(u32 value, void *context) if (obj && obj->type == ACPI_TYPE_INTEGER) { int eventcode = obj->integer.value; - dprintk("Eventcode: 0x%x\n", eventcode); + pr_debug("Eventcode: 0x%x\n", eventcode); key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); - if (key) { - ktime_t diff; - cur = ktime_get_real(); - diff = ktime_sub(cur, last_pressed[key->code - - KEYCODE_BASE]); - /* Ignore event if the same event happened in a 50 ms + if (!key) { + pr_info("Unknown key pressed - %x\n", eventcode); + goto msi_wmi_notify_exit; + } + + if (event_wmi->quirk_last_pressed) { + ktime_t cur = ktime_get_real(); + ktime_t diff = ktime_sub(cur, last_pressed); + /* Ignore event if any event happened in a 50 ms timeframe -> Key press may result in 10-20 GPEs */ if (ktime_to_us(diff) < 1000 * 50) { - dprintk("Suppressed key event 0x%X - " - "Last press was %lld us ago\n", + pr_debug("Suppressed key event 0x%X - " + "Last press was %lld us ago\n", key->code, ktime_to_us(diff)); - return; - } - last_pressed[key->code - KEYCODE_BASE] = cur; - - if (key->type == KE_KEY && - /* Brightness is served via acpi video driver */ - (!acpi_video_backlight_support() || - (key->code != MSI_WMI_BRIGHTNESSUP && - key->code != MSI_WMI_BRIGHTNESSDOWN))) { - dprintk("Send key: 0x%X - " - "Input layer keycode: %d\n", key->code, - key->keycode); - sparse_keymap_report_entry(msi_wmi_input_dev, - key, 1, true); + goto msi_wmi_notify_exit; } - } else - printk(KERN_INFO "Unknown key pressed - %x\n", - eventcode); + last_pressed = cur; + } + + if (key->type == KE_KEY && + /* Brightness is served via acpi video driver */ + (backlight || + (key->code != MSI_KEY_BRIGHTNESSUP && + key->code != MSI_KEY_BRIGHTNESSDOWN))) { + pr_debug("Send key: 0x%X - Input layer keycode: %d\n", + key->code, key->keycode); + sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, + true); + } } else - printk(KERN_INFO DRV_PFX "Unknown event received\n"); + pr_info("Unknown event received\n"); + +msi_wmi_notify_exit: kfree(response.pointer); } +static int __init msi_wmi_backlight_setup(void) +{ + int err; + struct backlight_properties props; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = ARRAY_SIZE(backlight_map) - 1; + backlight = backlight_device_register(DRV_NAME, NULL, NULL, + &msi_backlight_ops, + &props); + if (IS_ERR(backlight)) + return PTR_ERR(backlight); + + err = bl_get(NULL); + if (err < 0) { + backlight_device_unregister(backlight); + return err; + } + + backlight->props.brightness = err; + + return 0; +} + static int __init msi_wmi_input_setup(void) { int err; @@ -219,7 +282,7 @@ static int __init msi_wmi_input_setup(void) if (err) goto err_free_keymap; - memset(last_pressed, 0, sizeof(last_pressed)); + last_pressed = ktime_set(0, 0); return 0; @@ -233,55 +296,66 @@ err_free_dev: static int __init msi_wmi_init(void) { int err; + int i; - if (!wmi_has_guid(MSIWMI_EVENT_GUID)) { - printk(KERN_ERR - "This machine doesn't have MSI-hotkeys through WMI\n"); - return -ENODEV; - } - err = wmi_install_notify_handler(MSIWMI_EVENT_GUID, - msi_wmi_notify, NULL); - if (ACPI_FAILURE(err)) - return -EINVAL; + for (i = 0; i < ARRAY_SIZE(event_wmis); i++) { + if (!wmi_has_guid(event_wmis[i].guid)) + continue; - err = msi_wmi_input_setup(); - if (err) - goto err_uninstall_notifier; + err = msi_wmi_input_setup(); + if (err) { + pr_err("Unable to setup input device\n"); + return err; + } - if (!acpi_video_backlight_support()) { - backlight = backlight_device_register(DRV_NAME, - NULL, NULL, &msi_backlight_ops); - if (IS_ERR(backlight)) + err = wmi_install_notify_handler(event_wmis[i].guid, + msi_wmi_notify, NULL); + if (ACPI_FAILURE(err)) { + pr_err("Unable to setup WMI notify handler\n"); goto err_free_input; + } - backlight->props.max_brightness = ARRAY_SIZE(backlight_map) - 1; - err = bl_get(NULL); - if (err < 0) - goto err_free_backlight; + pr_debug("Event handler installed\n"); + event_wmi = &event_wmis[i]; + break; + } - backlight->props.brightness = err; + if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) { + err = msi_wmi_backlight_setup(); + if (err) { + pr_err("Unable to setup backlight device\n"); + goto err_uninstall_handler; + } + pr_debug("Backlight device created\n"); + } + + if (!event_wmi && !backlight) { + pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n"); + return -ENODEV; } - dprintk("Event handler installed\n"); return 0; -err_free_backlight: - backlight_device_unregister(backlight); +err_uninstall_handler: + if (event_wmi) + wmi_remove_notify_handler(event_wmi->guid); err_free_input: - input_unregister_device(msi_wmi_input_dev); -err_uninstall_notifier: - wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + if (event_wmi) { + sparse_keymap_free(msi_wmi_input_dev); + input_unregister_device(msi_wmi_input_dev); + } return err; } static void __exit msi_wmi_exit(void) { - if (wmi_has_guid(MSIWMI_EVENT_GUID)) { - wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + if (event_wmi) { + wmi_remove_notify_handler(event_wmi->guid); sparse_keymap_free(msi_wmi_input_dev); input_unregister_device(msi_wmi_input_dev); - backlight_device_unregister(backlight); } + if (backlight) + backlight_device_unregister(backlight); } module_init(msi_wmi_init); diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c new file mode 100644 index 00000000000..f4bad83053a --- /dev/null +++ b/drivers/platform/x86/mxm-wmi.c @@ -0,0 +1,111 @@ +/* + * MXM WMI driver + * + * Copyright(C) 2010 Red Hat. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/mxm-wmi.h> +#include <linux/acpi.h> + +MODULE_AUTHOR("Dave Airlie"); +MODULE_DESCRIPTION("MXM WMI Driver"); +MODULE_LICENSE("GPL"); + +#define MXM_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0" + +MODULE_ALIAS("wmi:"MXM_WMMX_GUID); + +#define MXM_WMMX_FUNC_MXDS 0x5344584D /* "MXDS" */ +#define MXM_WMMX_FUNC_MXMX 0x53445344 /* "MXMX" */ + +struct mxds_args { + u32 func; + u32 args; + u32 xarg; +}; + +int mxm_wmi_call_mxds(int adapter) +{ + struct mxds_args args = { + .func = MXM_WMMX_FUNC_MXDS, + .args = 0, + .xarg = 1, + }; + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + printk("calling mux switch %d\n", adapter); + + status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input, + &output); + + if (ACPI_FAILURE(status)) + return status; + + printk("mux switched %d\n", status); + return 0; + +} +EXPORT_SYMBOL_GPL(mxm_wmi_call_mxds); + +int mxm_wmi_call_mxmx(int adapter) +{ + struct mxds_args args = { + .func = MXM_WMMX_FUNC_MXMX, + .args = 0, + .xarg = 1, + }; + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + printk("calling mux switch %d\n", adapter); + + status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input, + &output); + + if (ACPI_FAILURE(status)) + return status; + + printk("mux mutex set switched %d\n", status); + return 0; + +} +EXPORT_SYMBOL_GPL(mxm_wmi_call_mxmx); + +bool mxm_wmi_supported(void) +{ + bool guid_valid; + guid_valid = wmi_has_guid(MXM_WMMX_GUID); + return guid_valid; +} +EXPORT_SYMBOL_GPL(mxm_wmi_supported); + +static int __init mxm_wmi_init(void) +{ + return 0; +} + +static void __exit mxm_wmi_exit(void) +{ +} + +module_init(mxm_wmi_init); +module_exit(mxm_wmi_exit); diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index fe7cf0188ac..3f870972247 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -124,10 +124,10 @@ #include <linux/ctype.h> #include <linux/seq_file.h> #include <linux/uaccess.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/slab.h> +#include <linux/acpi.h> #include <linux/input.h> - +#include <linux/input/sparse-keymap.h> #ifndef ACPI_HOTKEY_COMPONENT #define ACPI_HOTKEY_COMPONENT 0x10000000 @@ -174,8 +174,7 @@ enum SINF_BITS { SINF_NUM_BATTERIES = 0, /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */ static int acpi_pcc_hotkey_add(struct acpi_device *device); -static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type); -static int acpi_pcc_hotkey_resume(struct acpi_device *device); +static int acpi_pcc_hotkey_remove(struct acpi_device *device); static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event); static const struct acpi_device_id pcc_device_ids[] = { @@ -187,6 +186,11 @@ static const struct acpi_device_id pcc_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, pcc_device_ids); +#ifdef CONFIG_PM_SLEEP +static int acpi_pcc_hotkey_resume(struct device *dev); +#endif +static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume); + static struct acpi_driver acpi_pcc_driver = { .name = ACPI_PCC_DRIVER_NAME, .class = ACPI_PCC_CLASS, @@ -194,35 +198,34 @@ static struct acpi_driver acpi_pcc_driver = { .ops = { .add = acpi_pcc_hotkey_add, .remove = acpi_pcc_hotkey_remove, - .resume = acpi_pcc_hotkey_resume, .notify = acpi_pcc_hotkey_notify, }, + .drv.pm = &acpi_pcc_hotkey_pm, }; -#define KEYMAP_SIZE 11 -static const int initial_keymap[KEYMAP_SIZE] = { - /* 0 */ KEY_RESERVED, - /* 1 */ KEY_BRIGHTNESSDOWN, - /* 2 */ KEY_BRIGHTNESSUP, - /* 3 */ KEY_DISPLAYTOGGLE, - /* 4 */ KEY_MUTE, - /* 5 */ KEY_VOLUMEDOWN, - /* 6 */ KEY_VOLUMEUP, - /* 7 */ KEY_SLEEP, - /* 8 */ KEY_PROG1, /* Change CPU boost */ - /* 9 */ KEY_BATTERY, - /* 10 */ KEY_SUSPEND, +static const struct key_entry panasonic_keymap[] = { + { KE_KEY, 0, { KEY_RESERVED } }, + { KE_KEY, 1, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 2, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 3, { KEY_DISPLAYTOGGLE } }, + { KE_KEY, 4, { KEY_MUTE } }, + { KE_KEY, 5, { KEY_VOLUMEDOWN } }, + { KE_KEY, 6, { KEY_VOLUMEUP } }, + { KE_KEY, 7, { KEY_SLEEP } }, + { KE_KEY, 8, { KEY_PROG1 } }, /* Change CPU boost */ + { KE_KEY, 9, { KEY_BATTERY } }, + { KE_KEY, 10, { KEY_SUSPEND } }, + { KE_END, 0 } }; struct pcc_acpi { acpi_handle handle; unsigned long num_sifr; int sticky_mode; - u32 *sinf; + u32 *sinf; struct acpi_device *device; struct input_dev *input_dev; struct backlight_device *backlight; - int keymap[KEYMAP_SIZE]; }; struct pcc_keyinput { @@ -247,7 +250,7 @@ static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val) status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET, ¶ms, NULL); - return status == AE_OK; + return (status == AE_OK) ? 0 : -EIO; } static inline int acpi_pcc_get_sqty(struct acpi_device *device) @@ -266,7 +269,7 @@ static inline int acpi_pcc_get_sqty(struct acpi_device *device) } } -static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc, u32 *sinf) +static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) { acpi_status status; struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; @@ -284,6 +287,7 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc, u32 *sinf) hkey = buffer.pointer; if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n")); + status = AE_ERROR; goto end; } @@ -297,12 +301,12 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc, u32 *sinf) for (i = 0; i < hkey->package.count; i++) { union acpi_object *element = &(hkey->package.elements[i]); if (likely(element->type == ACPI_TYPE_INTEGER)) { - sinf[i] = element->integer.value; + pcc->sinf[i] = element->integer.value; } else ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF data\n")); } - sinf[hkey->package.count] = -1; + pcc->sinf[hkey->package.count] = -1; end: kfree(buffer.pointer); @@ -320,7 +324,7 @@ static int bl_get(struct backlight_device *bd) { struct pcc_acpi *pcc = bl_get_data(bd); - if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; return pcc->sinf[SINF_AC_CUR_BRIGHT]; @@ -332,7 +336,7 @@ static int bl_set_status(struct backlight_device *bd) int bright = bd->props.brightness; int rc; - if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT]) @@ -352,7 +356,7 @@ static int bl_set_status(struct backlight_device *bd) return acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, bright); } -static struct backlight_ops pcc_backlight_ops = { +static const struct backlight_ops pcc_backlight_ops = { .get_brightness = bl_get, .update_status = bl_set_status, }; @@ -366,7 +370,7 @@ static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr, struct acpi_device *acpi = to_acpi_device(dev); struct pcc_acpi *pcc = acpi_driver_data(acpi); - if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]); @@ -378,7 +382,7 @@ static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr, struct acpi_device *acpi = to_acpi_device(dev); struct pcc_acpi *pcc = acpi_driver_data(acpi); - if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]); @@ -390,7 +394,7 @@ static ssize_t show_mute(struct device *dev, struct device_attribute *attr, struct acpi_device *acpi = to_acpi_device(dev); struct pcc_acpi *pcc = acpi_driver_data(acpi); - if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]); @@ -402,7 +406,7 @@ static ssize_t show_sticky(struct device *dev, struct device_attribute *attr, struct acpi_device *acpi = to_acpi_device(dev); struct pcc_acpi *pcc = acpi_driver_data(acpi); - if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + if (!acpi_pcc_retrieve_biosdata(pcc)) return -EIO; return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]); @@ -445,57 +449,11 @@ static struct attribute_group pcc_attr_group = { /* hotkey input device driver */ -static int pcc_getkeycode(struct input_dev *dev, int scancode, int *keycode) -{ - struct pcc_acpi *pcc = input_get_drvdata(dev); - - if (scancode >= ARRAY_SIZE(pcc->keymap)) - return -EINVAL; - - *keycode = pcc->keymap[scancode]; - - return 0; -} - -static int keymap_get_by_keycode(struct pcc_acpi *pcc, int keycode) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++) { - if (pcc->keymap[i] == keycode) - return i+1; - } - - return 0; -} - -static int pcc_setkeycode(struct input_dev *dev, int scancode, int keycode) -{ - struct pcc_acpi *pcc = input_get_drvdata(dev); - int oldkeycode; - - if (scancode >= ARRAY_SIZE(pcc->keymap)) - return -EINVAL; - - if (keycode < 0 || keycode > KEY_MAX) - return -EINVAL; - - oldkeycode = pcc->keymap[scancode]; - pcc->keymap[scancode] = keycode; - - set_bit(keycode, dev->keybit); - - if (!keymap_get_by_keycode(pcc, oldkeycode)) - clear_bit(oldkeycode, dev->keybit); - - return 0; -} - +static int sleep_keydown_seen; static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) { struct input_dev *hotk_input_dev = pcc->input_dev; int rc; - int key_code, hkey_num; unsigned long long result; rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY, @@ -506,27 +464,19 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) return; } - acpi_bus_generate_proc_event(pcc->device, HKEY_NOTIFY, result); - - hkey_num = result & 0xf; - - if (hkey_num < 0 || hkey_num >= ARRAY_SIZE(pcc->keymap)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "hotkey number out of range: %d\n", - hkey_num)); - return; - } - - key_code = pcc->keymap[hkey_num]; - - if (key_code != KEY_RESERVED) { - int pushed = (result & 0x80) ? TRUE : FALSE; - - input_report_key(hotk_input_dev, key_code, pushed); - input_sync(hotk_input_dev); + /* hack: some firmware sends no key down for sleep / hibernate */ + if ((result & 0xf) == 0x7 || (result & 0xf) == 0xa) { + if (result & 0x80) + sleep_keydown_seen = 1; + if (!sleep_keydown_seen) + sparse_keymap_report_event(hotk_input_dev, + result & 0xf, 0x80, false); } - return; + if (!sparse_keymap_report_event(hotk_input_dev, + result & 0xf, result & 0x80, false)) + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Unknown hotkey event: %d\n", result)); } static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) @@ -545,62 +495,78 @@ static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) static int acpi_pcc_init_input(struct pcc_acpi *pcc) { - int i, rc; + struct input_dev *input_dev; + int error; - pcc->input_dev = input_allocate_device(); - if (!pcc->input_dev) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Couldn't allocate input device for hotkey")); + input_dev = input_allocate_device(); + if (!input_dev) return -ENOMEM; - } - - pcc->input_dev->evbit[0] = BIT(EV_KEY); - pcc->input_dev->name = ACPI_PCC_DRIVER_NAME; - pcc->input_dev->phys = ACPI_PCC_INPUT_PHYS; - pcc->input_dev->id.bustype = BUS_HOST; - pcc->input_dev->id.vendor = 0x0001; - pcc->input_dev->id.product = 0x0001; - pcc->input_dev->id.version = 0x0100; - pcc->input_dev->getkeycode = pcc_getkeycode; - pcc->input_dev->setkeycode = pcc_setkeycode; + input_dev->name = ACPI_PCC_DRIVER_NAME; + input_dev->phys = ACPI_PCC_INPUT_PHYS; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; - /* load initial keymap */ - memcpy(pcc->keymap, initial_keymap, sizeof(pcc->keymap)); + error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL); + if (error) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Unable to setup input device keymap\n")); + goto err_free_dev; + } - for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++) - __set_bit(pcc->keymap[i], pcc->input_dev->keybit); - __clear_bit(KEY_RESERVED, pcc->input_dev->keybit); + error = input_register_device(input_dev); + if (error) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Unable to register input device\n")); + goto err_free_keymap; + } - input_set_drvdata(pcc->input_dev, pcc); + pcc->input_dev = input_dev; + return 0; - rc = input_register_device(pcc->input_dev); - if (rc < 0) - input_free_device(pcc->input_dev); + err_free_keymap: + sparse_keymap_free(input_dev); + err_free_dev: + input_free_device(input_dev); + return error; +} - return rc; +static void acpi_pcc_destroy_input(struct pcc_acpi *pcc) +{ + sparse_keymap_free(pcc->input_dev); + input_unregister_device(pcc->input_dev); + /* + * No need to input_free_device() since core input API refcounts + * and free()s the device. + */ } /* kernel module interface */ -static int acpi_pcc_hotkey_resume(struct acpi_device *device) +#ifdef CONFIG_PM_SLEEP +static int acpi_pcc_hotkey_resume(struct device *dev) { - struct pcc_acpi *pcc = acpi_driver_data(device); - acpi_status status = AE_OK; + struct pcc_acpi *pcc; + + if (!dev) + return -EINVAL; - if (device == NULL || pcc == NULL) + pcc = acpi_driver_data(to_acpi_device(dev)); + if (!pcc) return -EINVAL; ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n", pcc->sticky_mode)); - status = acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode); - - return status == AE_OK ? 0 : -EINVAL; + return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode); } +#endif static int acpi_pcc_hotkey_add(struct acpi_device *device) { + struct backlight_properties props; struct pcc_acpi *pcc; int num_sifr, result; @@ -609,8 +575,8 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) num_sifr = acpi_pcc_get_sqty(device); - if (num_sifr > 255) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr too large")); + if (num_sifr < 0 || num_sifr > 255) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range")); return -ENODEV; } @@ -641,21 +607,24 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) goto out_sinf; } + if (!acpi_pcc_retrieve_biosdata(pcc)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Couldn't retrieve BIOS data\n")); + result = -EIO; + goto out_input; + } /* initialize backlight */ + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT]; pcc->backlight = backlight_device_register("panasonic", NULL, pcc, - &pcc_backlight_ops); - if (IS_ERR(pcc->backlight)) + &pcc_backlight_ops, &props); + if (IS_ERR(pcc->backlight)) { + result = PTR_ERR(pcc->backlight); goto out_input; - - if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Couldn't retrieve BIOS data\n")); - goto out_backlight; } /* read the initial brightness setting from the hardware */ - pcc->backlight->props.max_brightness = - pcc->sinf[SINF_AC_MAX_BRIGHT]; pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; /* read the initial sticky key mode from the hardware */ @@ -671,9 +640,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) out_backlight: backlight_device_unregister(pcc->backlight); out_input: - input_unregister_device(pcc->input_dev); - /* no need to input_free_device() since core input API refcount and - * free()s the device */ + acpi_pcc_destroy_input(pcc); out_sinf: kfree(pcc->sinf); out_hotkey: @@ -682,24 +649,7 @@ out_hotkey: return result; } -static int __init acpi_pcc_init(void) -{ - int result = 0; - - if (acpi_disabled) - return -ENODEV; - - result = acpi_bus_register_driver(&acpi_pcc_driver); - if (result < 0) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Error registering hotkey driver\n")); - return -ENODEV; - } - - return 0; -} - -static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type) +static int acpi_pcc_hotkey_remove(struct acpi_device *device) { struct pcc_acpi *pcc = acpi_driver_data(device); @@ -710,9 +660,7 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type) backlight_device_unregister(pcc->backlight); - input_unregister_device(pcc->input_dev); - /* no need to input_free_device() since core input API refcount and - * free()s the device */ + acpi_pcc_destroy_input(pcc); kfree(pcc->sinf); kfree(pcc); @@ -720,10 +668,4 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type) return 0; } -static void __exit acpi_pcc_exit(void) -{ - acpi_bus_unregister_driver(&acpi_pcc_driver); -} - -module_init(acpi_pcc_init); -module_exit(acpi_pcc_exit); +module_acpi_driver(acpi_pcc_driver); diff --git a/drivers/platform/x86/pvpanic.c b/drivers/platform/x86/pvpanic.c new file mode 100644 index 00000000000..073a90a63db --- /dev/null +++ b/drivers/platform/x86/pvpanic.c @@ -0,0 +1,124 @@ +/* + * pvpanic.c - pvpanic Device Support + * + * Copyright (C) 2013 Fujitsu. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/acpi.h> + +MODULE_AUTHOR("Hu Tao <hutao@cn.fujitsu.com>"); +MODULE_DESCRIPTION("pvpanic device driver"); +MODULE_LICENSE("GPL"); + +static int pvpanic_add(struct acpi_device *device); +static int pvpanic_remove(struct acpi_device *device); + +static const struct acpi_device_id pvpanic_device_ids[] = { + { "QEMU0001", 0 }, + { "", 0 }, +}; +MODULE_DEVICE_TABLE(acpi, pvpanic_device_ids); + +#define PVPANIC_PANICKED (1 << 0) + +static u16 port; + +static struct acpi_driver pvpanic_driver = { + .name = "pvpanic", + .class = "QEMU", + .ids = pvpanic_device_ids, + .ops = { + .add = pvpanic_add, + .remove = pvpanic_remove, + }, + .owner = THIS_MODULE, +}; + +static void +pvpanic_send_event(unsigned int event) +{ + outb(event, port); +} + +static int +pvpanic_panic_notify(struct notifier_block *nb, unsigned long code, + void *unused) +{ + pvpanic_send_event(PVPANIC_PANICKED); + return NOTIFY_DONE; +} + +static struct notifier_block pvpanic_panic_nb = { + .notifier_call = pvpanic_panic_notify, + .priority = 1, /* let this called before broken drm_fb_helper */ +}; + + +static acpi_status +pvpanic_walk_resources(struct acpi_resource *res, void *context) +{ + switch (res->type) { + case ACPI_RESOURCE_TYPE_END_TAG: + return AE_OK; + + case ACPI_RESOURCE_TYPE_IO: + port = res->data.io.minimum; + return AE_OK; + + default: + return AE_ERROR; + } +} + +static int pvpanic_add(struct acpi_device *device) +{ + acpi_status status; + u64 ret; + + status = acpi_evaluate_integer(device->handle, "_STA", NULL, + &ret); + + if (ACPI_FAILURE(status) || (ret & 0x0B) != 0x0B) + return -ENODEV; + + acpi_walk_resources(device->handle, METHOD_NAME__CRS, + pvpanic_walk_resources, NULL); + + if (!port) + return -ENODEV; + + atomic_notifier_chain_register(&panic_notifier_list, + &pvpanic_panic_nb); + + return 0; +} + +static int pvpanic_remove(struct acpi_device *device) +{ + + atomic_notifier_chain_unregister(&panic_notifier_list, + &pvpanic_panic_nb); + return 0; +} + +module_acpi_driver(pvpanic_driver); diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c new file mode 100644 index 00000000000..5a596651227 --- /dev/null +++ b/drivers/platform/x86/samsung-laptop.c @@ -0,0 +1,1692 @@ +/* + * Samsung Laptop driver + * + * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de) + * Copyright (C) 2009,2011 Novell Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/backlight.h> +#include <linux/leds.h> +#include <linux/fb.h> +#include <linux/dmi.h> +#include <linux/platform_device.h> +#include <linux/rfkill.h> +#include <linux/acpi.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/ctype.h> +#include <linux/efi.h> +#include <linux/suspend.h> +#include <acpi/video.h> + +/* + * This driver is needed because a number of Samsung laptops do not hook + * their control settings through ACPI. So we have to poke around in the + * BIOS to do things like brightness values, and "special" key controls. + */ + +/* + * We have 0 - 8 as valid brightness levels. The specs say that level 0 should + * be reserved by the BIOS (which really doesn't make much sense), we tell + * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8 + */ +#define MAX_BRIGHT 0x07 + + +#define SABI_IFACE_MAIN 0x00 +#define SABI_IFACE_SUB 0x02 +#define SABI_IFACE_COMPLETE 0x04 +#define SABI_IFACE_DATA 0x05 + +#define WL_STATUS_WLAN 0x0 +#define WL_STATUS_BT 0x2 + +/* Structure get/set data using sabi */ +struct sabi_data { + union { + struct { + u32 d0; + u32 d1; + u16 d2; + u8 d3; + }; + u8 data[11]; + }; +}; + +struct sabi_header_offsets { + u8 port; + u8 re_mem; + u8 iface_func; + u8 en_mem; + u8 data_offset; + u8 data_segment; +}; + +struct sabi_commands { + /* + * Brightness is 0 - 8, as described above. + * Value 0 is for the BIOS to use + */ + u16 get_brightness; + u16 set_brightness; + + /* + * first byte: + * 0x00 - wireless is off + * 0x01 - wireless is on + * second byte: + * 0x02 - 3G is off + * 0x03 - 3G is on + * TODO, verify 3G is correct, that doesn't seem right... + */ + u16 get_wireless_button; + u16 set_wireless_button; + + /* 0 is off, 1 is on */ + u16 get_backlight; + u16 set_backlight; + + /* + * 0x80 or 0x00 - no action + * 0x81 - recovery key pressed + */ + u16 get_recovery_mode; + u16 set_recovery_mode; + + /* + * on seclinux: 0 is low, 1 is high, + * on swsmi: 0 is normal, 1 is silent, 2 is turbo + */ + u16 get_performance_level; + u16 set_performance_level; + + /* 0x80 is off, 0x81 is on */ + u16 get_battery_life_extender; + u16 set_battery_life_extender; + + /* 0x80 is off, 0x81 is on */ + u16 get_usb_charge; + u16 set_usb_charge; + + /* the first byte is for bluetooth and the third one is for wlan */ + u16 get_wireless_status; + u16 set_wireless_status; + + /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */ + u16 kbd_backlight; + + /* + * Tell the BIOS that Linux is running on this machine. + * 81 is on, 80 is off + */ + u16 set_linux; +}; + +struct sabi_performance_level { + const char *name; + u16 value; +}; + +struct sabi_config { + int sabi_version; + const char *test_string; + u16 main_function; + const struct sabi_header_offsets header_offsets; + const struct sabi_commands commands; + const struct sabi_performance_level performance_levels[4]; + u8 min_brightness; + u8 max_brightness; +}; + +static const struct sabi_config sabi_configs[] = { + { + /* I don't know if it is really 2, but it it is + * less than 3 anyway */ + .sabi_version = 2, + + .test_string = "SECLINUX", + + .main_function = 0x4c49, + + .header_offsets = { + .port = 0x00, + .re_mem = 0x02, + .iface_func = 0x03, + .en_mem = 0x04, + .data_offset = 0x05, + .data_segment = 0x07, + }, + + .commands = { + .get_brightness = 0x00, + .set_brightness = 0x01, + + .get_wireless_button = 0x02, + .set_wireless_button = 0x03, + + .get_backlight = 0x04, + .set_backlight = 0x05, + + .get_recovery_mode = 0x06, + .set_recovery_mode = 0x07, + + .get_performance_level = 0x08, + .set_performance_level = 0x09, + + .get_battery_life_extender = 0xFFFF, + .set_battery_life_extender = 0xFFFF, + + .get_usb_charge = 0xFFFF, + .set_usb_charge = 0xFFFF, + + .get_wireless_status = 0xFFFF, + .set_wireless_status = 0xFFFF, + + .kbd_backlight = 0xFFFF, + + .set_linux = 0x0a, + }, + + .performance_levels = { + { + .name = "silent", + .value = 0, + }, + { + .name = "normal", + .value = 1, + }, + { }, + }, + .min_brightness = 1, + .max_brightness = 8, + }, + { + .sabi_version = 3, + + .test_string = "SwSmi@", + + .main_function = 0x5843, + + .header_offsets = { + .port = 0x00, + .re_mem = 0x04, + .iface_func = 0x02, + .en_mem = 0x03, + .data_offset = 0x05, + .data_segment = 0x07, + }, + + .commands = { + .get_brightness = 0x10, + .set_brightness = 0x11, + + .get_wireless_button = 0x12, + .set_wireless_button = 0x13, + + .get_backlight = 0x2d, + .set_backlight = 0x2e, + + .get_recovery_mode = 0xff, + .set_recovery_mode = 0xff, + + .get_performance_level = 0x31, + .set_performance_level = 0x32, + + .get_battery_life_extender = 0x65, + .set_battery_life_extender = 0x66, + + .get_usb_charge = 0x67, + .set_usb_charge = 0x68, + + .get_wireless_status = 0x69, + .set_wireless_status = 0x6a, + + .kbd_backlight = 0x78, + + .set_linux = 0xff, + }, + + .performance_levels = { + { + .name = "normal", + .value = 0, + }, + { + .name = "silent", + .value = 1, + }, + { + .name = "overclock", + .value = 2, + }, + { }, + }, + .min_brightness = 0, + .max_brightness = 8, + }, + { }, +}; + +/* + * samsung-laptop/ - debugfs root directory + * f0000_segment - dump f0000 segment + * command - current command + * data - current data + * d0, d1, d2, d3 - data fields + * call - call SABI using command and data + * + * This allow to call arbitrary sabi commands wihout + * modifying the driver at all. + * For example, setting the keyboard backlight brightness to 5 + * + * echo 0x78 > command + * echo 0x0582 > d0 + * echo 0 > d1 + * echo 0 > d2 + * echo 0 > d3 + * cat call + */ + +struct samsung_laptop_debug { + struct dentry *root; + struct sabi_data data; + u16 command; + + struct debugfs_blob_wrapper f0000_wrapper; + struct debugfs_blob_wrapper data_wrapper; + struct debugfs_blob_wrapper sdiag_wrapper; +}; + +struct samsung_laptop; + +struct samsung_rfkill { + struct samsung_laptop *samsung; + struct rfkill *rfkill; + enum rfkill_type type; +}; + +struct samsung_laptop { + const struct sabi_config *config; + + void __iomem *sabi; + void __iomem *sabi_iface; + void __iomem *f0000_segment; + + struct mutex sabi_mutex; + + struct platform_device *platform_device; + struct backlight_device *backlight_device; + + struct samsung_rfkill wlan; + struct samsung_rfkill bluetooth; + + struct led_classdev kbd_led; + int kbd_led_wk; + struct workqueue_struct *led_workqueue; + struct work_struct kbd_led_work; + + struct samsung_laptop_debug debug; + struct samsung_quirks *quirks; + + struct notifier_block pm_nb; + + bool handle_backlight; + bool has_stepping_quirk; + + char sdiag[64]; +}; + +struct samsung_quirks { + bool broken_acpi_video; + bool four_kbd_backlight_levels; + bool enable_kbd_backlight; +}; + +static struct samsung_quirks samsung_unknown = {}; + +static struct samsung_quirks samsung_broken_acpi_video = { + .broken_acpi_video = true, +}; + +static struct samsung_quirks samsung_np740u3e = { + .four_kbd_backlight_levels = true, + .enable_kbd_backlight = true, +}; + +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, + "Disable the DMI check and forces the driver to be loaded"); + +static bool debug; +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +static int sabi_command(struct samsung_laptop *samsung, u16 command, + struct sabi_data *in, + struct sabi_data *out) +{ + const struct sabi_config *config = samsung->config; + int ret = 0; + u16 port = readw(samsung->sabi + config->header_offsets.port); + u8 complete, iface_data; + + mutex_lock(&samsung->sabi_mutex); + + if (debug) { + if (in) + pr_info("SABI command:0x%04x " + "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", + command, in->d0, in->d1, in->d2, in->d3); + else + pr_info("SABI command:0x%04x", command); + } + + /* enable memory to be able to write to it */ + outb(readb(samsung->sabi + config->header_offsets.en_mem), port); + + /* write out the command */ + writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN); + writew(command, samsung->sabi_iface + SABI_IFACE_SUB); + writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE); + if (in) { + writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA); + writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4); + writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8); + writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10); + } + outb(readb(samsung->sabi + config->header_offsets.iface_func), port); + + /* write protect memory to make it safe */ + outb(readb(samsung->sabi + config->header_offsets.re_mem), port); + + /* see if the command actually succeeded */ + complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE); + iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA); + + /* iface_data = 0xFF happens when a command is not known + * so we only add a warning in debug mode since we will + * probably issue some unknown command at startup to find + * out which features are supported */ + if (complete != 0xaa || (iface_data == 0xff && debug)) + pr_warn("SABI command 0x%04x failed with" + " completion flag 0x%02x and interface data 0x%02x", + command, complete, iface_data); + + if (complete != 0xaa || iface_data == 0xff) { + ret = -EINVAL; + goto exit; + } + + if (out) { + out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA); + out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4); + out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2); + out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1); + } + + if (debug && out) { + pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", + out->d0, out->d1, out->d2, out->d3); + } + +exit: + mutex_unlock(&samsung->sabi_mutex); + return ret; +} + +/* simple wrappers usable with most commands */ +static int sabi_set_commandb(struct samsung_laptop *samsung, + u16 command, u8 data) +{ + struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } }; + + in.data[0] = data; + return sabi_command(samsung, command, &in, NULL); +} + +static int read_brightness(struct samsung_laptop *samsung) +{ + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data sretval; + int user_brightness = 0; + int retval; + + retval = sabi_command(samsung, commands->get_brightness, + NULL, &sretval); + if (retval) + return retval; + + user_brightness = sretval.data[0]; + if (user_brightness > config->min_brightness) + user_brightness -= config->min_brightness; + else + user_brightness = 0; + + return user_brightness; +} + +static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness) +{ + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &samsung->config->commands; + u8 user_level = user_brightness + config->min_brightness; + + if (samsung->has_stepping_quirk && user_level != 0) { + /* + * short circuit if the specified level is what's already set + * to prevent the screen from flickering needlessly + */ + if (user_brightness == read_brightness(samsung)) + return; + + sabi_set_commandb(samsung, commands->set_brightness, 0); + } + + sabi_set_commandb(samsung, commands->set_brightness, user_level); +} + +static int get_brightness(struct backlight_device *bd) +{ + struct samsung_laptop *samsung = bl_get_data(bd); + + return read_brightness(samsung); +} + +static void check_for_stepping_quirk(struct samsung_laptop *samsung) +{ + int initial_level; + int check_level; + int orig_level = read_brightness(samsung); + + /* + * Some laptops exhibit the strange behaviour of stepping toward + * (rather than setting) the brightness except when changing to/from + * brightness level 0. This behaviour is checked for here and worked + * around in set_brightness. + */ + + if (orig_level == 0) + set_brightness(samsung, 1); + + initial_level = read_brightness(samsung); + + if (initial_level <= 2) + check_level = initial_level + 2; + else + check_level = initial_level - 2; + + samsung->has_stepping_quirk = false; + set_brightness(samsung, check_level); + + if (read_brightness(samsung) != check_level) { + samsung->has_stepping_quirk = true; + pr_info("enabled workaround for brightness stepping quirk\n"); + } + + set_brightness(samsung, orig_level); +} + +static int update_status(struct backlight_device *bd) +{ + struct samsung_laptop *samsung = bl_get_data(bd); + const struct sabi_commands *commands = &samsung->config->commands; + + set_brightness(samsung, bd->props.brightness); + + if (bd->props.power == FB_BLANK_UNBLANK) + sabi_set_commandb(samsung, commands->set_backlight, 1); + else + sabi_set_commandb(samsung, commands->set_backlight, 0); + + return 0; +} + +static const struct backlight_ops backlight_ops = { + .get_brightness = get_brightness, + .update_status = update_status, +}; + +static int seclinux_rfkill_set(void *data, bool blocked) +{ + struct samsung_rfkill *srfkill = data; + struct samsung_laptop *samsung = srfkill->samsung; + const struct sabi_commands *commands = &samsung->config->commands; + + return sabi_set_commandb(samsung, commands->set_wireless_button, + !blocked); +} + +static struct rfkill_ops seclinux_rfkill_ops = { + .set_block = seclinux_rfkill_set, +}; + +static int swsmi_wireless_status(struct samsung_laptop *samsung, + struct sabi_data *data) +{ + const struct sabi_commands *commands = &samsung->config->commands; + + return sabi_command(samsung, commands->get_wireless_status, + NULL, data); +} + +static int swsmi_rfkill_set(void *priv, bool blocked) +{ + struct samsung_rfkill *srfkill = priv; + struct samsung_laptop *samsung = srfkill->samsung; + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int ret, i; + + ret = swsmi_wireless_status(samsung, &data); + if (ret) + return ret; + + /* Don't set the state for non-present devices */ + for (i = 0; i < 4; i++) + if (data.data[i] == 0x02) + data.data[1] = 0; + + if (srfkill->type == RFKILL_TYPE_WLAN) + data.data[WL_STATUS_WLAN] = !blocked; + else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) + data.data[WL_STATUS_BT] = !blocked; + + return sabi_command(samsung, commands->set_wireless_status, + &data, &data); +} + +static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv) +{ + struct samsung_rfkill *srfkill = priv; + struct samsung_laptop *samsung = srfkill->samsung; + struct sabi_data data; + int ret; + + ret = swsmi_wireless_status(samsung, &data); + if (ret) + return ; + + if (srfkill->type == RFKILL_TYPE_WLAN) + ret = data.data[WL_STATUS_WLAN]; + else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) + ret = data.data[WL_STATUS_BT]; + else + return ; + + rfkill_set_sw_state(rfkill, !ret); +} + +static struct rfkill_ops swsmi_rfkill_ops = { + .set_block = swsmi_rfkill_set, + .query = swsmi_rfkill_query, +}; + +static ssize_t get_performance_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &config->commands; + struct sabi_data sretval; + int retval; + int i; + + /* Read the state */ + retval = sabi_command(samsung, commands->get_performance_level, + NULL, &sretval); + if (retval) + return retval; + + /* The logic is backwards, yeah, lots of fun... */ + for (i = 0; config->performance_levels[i].name; ++i) { + if (sretval.data[0] == config->performance_levels[i].value) + return sprintf(buf, "%s\n", config->performance_levels[i].name); + } + return sprintf(buf, "%s\n", "unknown"); +} + +static ssize_t set_performance_level(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + const struct sabi_config *config = samsung->config; + const struct sabi_commands *commands = &config->commands; + int i; + + if (count < 1) + return count; + + for (i = 0; config->performance_levels[i].name; ++i) { + const struct sabi_performance_level *level = + &config->performance_levels[i]; + if (!strncasecmp(level->name, buf, strlen(level->name))) { + sabi_set_commandb(samsung, + commands->set_performance_level, + level->value); + break; + } + } + + if (!config->performance_levels[i].name) + return -EINVAL; + + return count; +} + +static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, + get_performance_level, set_performance_level); + +static int read_battery_life_extender(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + if (commands->get_battery_life_extender == 0xFFFF) + return -ENODEV; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80; + retval = sabi_command(samsung, commands->get_battery_life_extender, + &data, &data); + + if (retval) + return retval; + + if (data.data[0] != 0 && data.data[0] != 1) + return -ENODEV; + + return data.data[0]; +} + +static int write_battery_life_extender(struct samsung_laptop *samsung, + int enabled) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80 | enabled; + return sabi_command(samsung, commands->set_battery_life_extender, + &data, NULL); +} + +static ssize_t get_battery_life_extender(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret; + + ret = read_battery_life_extender(samsung); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t set_battery_life_extender(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret, value; + + if (!count || sscanf(buf, "%i", &value) != 1) + return -EINVAL; + + ret = write_battery_life_extender(samsung, !!value); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO, + get_battery_life_extender, set_battery_life_extender); + +static int read_usb_charge(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + if (commands->get_usb_charge == 0xFFFF) + return -ENODEV; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80; + retval = sabi_command(samsung, commands->get_usb_charge, + &data, &data); + + if (retval) + return retval; + + if (data.data[0] != 0 && data.data[0] != 1) + return -ENODEV; + + return data.data[0]; +} + +static int write_usb_charge(struct samsung_laptop *samsung, + int enabled) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x80 | enabled; + return sabi_command(samsung, commands->set_usb_charge, + &data, NULL); +} + +static ssize_t get_usb_charge(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret; + + ret = read_usb_charge(samsung); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t set_usb_charge(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct samsung_laptop *samsung = dev_get_drvdata(dev); + int ret, value; + + if (!count || sscanf(buf, "%i", &value) != 1) + return -EINVAL; + + ret = write_usb_charge(samsung, !!value); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO, + get_usb_charge, set_usb_charge); + +static struct attribute *platform_attributes[] = { + &dev_attr_performance_level.attr, + &dev_attr_battery_life_extender.attr, + &dev_attr_usb_charge.attr, + NULL +}; + +static int find_signature(void __iomem *memcheck, const char *testStr) +{ + int i = 0; + int loca; + + for (loca = 0; loca < 0xffff; loca++) { + char temp = readb(memcheck + loca); + + if (temp == testStr[i]) { + if (i == strlen(testStr)-1) + break; + ++i; + } else { + i = 0; + } + } + return loca; +} + +static void samsung_rfkill_exit(struct samsung_laptop *samsung) +{ + if (samsung->wlan.rfkill) { + rfkill_unregister(samsung->wlan.rfkill); + rfkill_destroy(samsung->wlan.rfkill); + samsung->wlan.rfkill = NULL; + } + if (samsung->bluetooth.rfkill) { + rfkill_unregister(samsung->bluetooth.rfkill); + rfkill_destroy(samsung->bluetooth.rfkill); + samsung->bluetooth.rfkill = NULL; + } +} + +static int samsung_new_rfkill(struct samsung_laptop *samsung, + struct samsung_rfkill *arfkill, + const char *name, enum rfkill_type type, + const struct rfkill_ops *ops, + int blocked) +{ + struct rfkill **rfkill = &arfkill->rfkill; + int ret; + + arfkill->type = type; + arfkill->samsung = samsung; + + *rfkill = rfkill_alloc(name, &samsung->platform_device->dev, + type, ops, arfkill); + + if (!*rfkill) + return -EINVAL; + + if (blocked != -1) + rfkill_init_sw_state(*rfkill, blocked); + + ret = rfkill_register(*rfkill); + if (ret) { + rfkill_destroy(*rfkill); + *rfkill = NULL; + return ret; + } + return 0; +} + +static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung) +{ + return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan", + RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1); +} + +static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung) +{ + struct sabi_data data; + int ret; + + ret = swsmi_wireless_status(samsung, &data); + if (ret) { + /* Some swsmi laptops use the old seclinux way to control + * wireless devices */ + if (ret == -EINVAL) + ret = samsung_rfkill_init_seclinux(samsung); + return ret; + } + + /* 0x02 seems to mean that the device is no present/available */ + + if (data.data[WL_STATUS_WLAN] != 0x02) + ret = samsung_new_rfkill(samsung, &samsung->wlan, + "samsung-wlan", + RFKILL_TYPE_WLAN, + &swsmi_rfkill_ops, + !data.data[WL_STATUS_WLAN]); + if (ret) + goto exit; + + if (data.data[WL_STATUS_BT] != 0x02) + ret = samsung_new_rfkill(samsung, &samsung->bluetooth, + "samsung-bluetooth", + RFKILL_TYPE_BLUETOOTH, + &swsmi_rfkill_ops, + !data.data[WL_STATUS_BT]); + if (ret) + goto exit; + +exit: + if (ret) + samsung_rfkill_exit(samsung); + + return ret; +} + +static int __init samsung_rfkill_init(struct samsung_laptop *samsung) +{ + if (samsung->config->sabi_version == 2) + return samsung_rfkill_init_seclinux(samsung); + if (samsung->config->sabi_version == 3) + return samsung_rfkill_init_swsmi(samsung); + return 0; +} + +static int kbd_backlight_enable(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + if (commands->kbd_backlight == 0xFFFF) + return -ENODEV; + + memset(&data, 0, sizeof(data)); + data.d0 = 0xaabb; + retval = sabi_command(samsung, commands->kbd_backlight, + &data, &data); + + if (retval) + return retval; + + if (data.d0 != 0xccdd) + return -ENODEV; + return 0; +} + +static int kbd_backlight_read(struct samsung_laptop *samsung) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + int retval; + + memset(&data, 0, sizeof(data)); + data.data[0] = 0x81; + retval = sabi_command(samsung, commands->kbd_backlight, + &data, &data); + + if (retval) + return retval; + + return data.data[0]; +} + +static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness) +{ + const struct sabi_commands *commands = &samsung->config->commands; + struct sabi_data data; + + memset(&data, 0, sizeof(data)); + data.d0 = 0x82 | ((brightness & 0xFF) << 8); + return sabi_command(samsung, commands->kbd_backlight, + &data, NULL); +} + +static void kbd_led_update(struct work_struct *work) +{ + struct samsung_laptop *samsung; + + samsung = container_of(work, struct samsung_laptop, kbd_led_work); + kbd_backlight_write(samsung, samsung->kbd_led_wk); +} + +static void kbd_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct samsung_laptop *samsung; + + samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); + + if (value > samsung->kbd_led.max_brightness) + value = samsung->kbd_led.max_brightness; + else if (value < 0) + value = 0; + + samsung->kbd_led_wk = value; + queue_work(samsung->led_workqueue, &samsung->kbd_led_work); +} + +static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) +{ + struct samsung_laptop *samsung; + + samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); + return kbd_backlight_read(samsung); +} + +static void samsung_leds_exit(struct samsung_laptop *samsung) +{ + if (!IS_ERR_OR_NULL(samsung->kbd_led.dev)) + led_classdev_unregister(&samsung->kbd_led); + if (samsung->led_workqueue) + destroy_workqueue(samsung->led_workqueue); +} + +static int __init samsung_leds_init(struct samsung_laptop *samsung) +{ + int ret = 0; + + samsung->led_workqueue = create_singlethread_workqueue("led_workqueue"); + if (!samsung->led_workqueue) + return -ENOMEM; + + if (kbd_backlight_enable(samsung) >= 0) { + INIT_WORK(&samsung->kbd_led_work, kbd_led_update); + + samsung->kbd_led.name = "samsung::kbd_backlight"; + samsung->kbd_led.brightness_set = kbd_led_set; + samsung->kbd_led.brightness_get = kbd_led_get; + samsung->kbd_led.max_brightness = 8; + if (samsung->quirks->four_kbd_backlight_levels) + samsung->kbd_led.max_brightness = 4; + + ret = led_classdev_register(&samsung->platform_device->dev, + &samsung->kbd_led); + } + + if (ret) + samsung_leds_exit(samsung); + + return ret; +} + +static void samsung_backlight_exit(struct samsung_laptop *samsung) +{ + if (samsung->backlight_device) { + backlight_device_unregister(samsung->backlight_device); + samsung->backlight_device = NULL; + } +} + +static int __init samsung_backlight_init(struct samsung_laptop *samsung) +{ + struct backlight_device *bd; + struct backlight_properties props; + + if (!samsung->handle_backlight) + return 0; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = samsung->config->max_brightness - + samsung->config->min_brightness; + + bd = backlight_device_register("samsung", + &samsung->platform_device->dev, + samsung, &backlight_ops, + &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + samsung->backlight_device = bd; + samsung->backlight_device->props.brightness = read_brightness(samsung); + samsung->backlight_device->props.power = FB_BLANK_UNBLANK; + backlight_update_status(samsung->backlight_device); + + return 0; +} + +static umode_t samsung_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct platform_device *pdev = to_platform_device(dev); + struct samsung_laptop *samsung = platform_get_drvdata(pdev); + bool ok = true; + + if (attr == &dev_attr_performance_level.attr) + ok = !!samsung->config->performance_levels[0].name; + if (attr == &dev_attr_battery_life_extender.attr) + ok = !!(read_battery_life_extender(samsung) >= 0); + if (attr == &dev_attr_usb_charge.attr) + ok = !!(read_usb_charge(samsung) >= 0); + + return ok ? attr->mode : 0; +} + +static struct attribute_group platform_attribute_group = { + .is_visible = samsung_sysfs_is_visible, + .attrs = platform_attributes +}; + +static void samsung_sysfs_exit(struct samsung_laptop *samsung) +{ + struct platform_device *device = samsung->platform_device; + + sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); +} + +static int __init samsung_sysfs_init(struct samsung_laptop *samsung) +{ + struct platform_device *device = samsung->platform_device; + + return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); + +} + +static int show_call(struct seq_file *m, void *data) +{ + struct samsung_laptop *samsung = m->private; + struct sabi_data *sdata = &samsung->debug.data; + int ret; + + seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", + samsung->debug.command, + sdata->d0, sdata->d1, sdata->d2, sdata->d3); + + ret = sabi_command(samsung, samsung->debug.command, sdata, sdata); + + if (ret) { + seq_printf(m, "SABI command 0x%04x failed\n", + samsung->debug.command); + return ret; + } + + seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", + sdata->d0, sdata->d1, sdata->d2, sdata->d3); + return 0; +} + +static int samsung_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, show_call, inode->i_private); +} + +static const struct file_operations samsung_laptop_call_io_ops = { + .owner = THIS_MODULE, + .open = samsung_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void samsung_debugfs_exit(struct samsung_laptop *samsung) +{ + debugfs_remove_recursive(samsung->debug.root); +} + +static int samsung_debugfs_init(struct samsung_laptop *samsung) +{ + struct dentry *dent; + + samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL); + if (!samsung->debug.root) { + pr_err("failed to create debugfs directory"); + goto error_debugfs; + } + + samsung->debug.f0000_wrapper.data = samsung->f0000_segment; + samsung->debug.f0000_wrapper.size = 0xffff; + + samsung->debug.data_wrapper.data = &samsung->debug.data; + samsung->debug.data_wrapper.size = sizeof(samsung->debug.data); + + samsung->debug.sdiag_wrapper.data = samsung->sdiag; + samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); + + dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR, + samsung->debug.root, &samsung->debug.command); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d0); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d1); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d2); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root, + &samsung->debug.data.d3); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR, + samsung->debug.root, + &samsung->debug.data_wrapper); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, + samsung->debug.root, + &samsung->debug.f0000_wrapper); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_file("call", S_IFREG | S_IRUGO, + samsung->debug.root, samsung, + &samsung_laptop_call_io_ops); + if (!dent) + goto error_debugfs; + + dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, + samsung->debug.root, + &samsung->debug.sdiag_wrapper); + if (!dent) + goto error_debugfs; + + return 0; + +error_debugfs: + samsung_debugfs_exit(samsung); + return -ENOMEM; +} + +static void samsung_sabi_exit(struct samsung_laptop *samsung) +{ + const struct sabi_config *config = samsung->config; + + /* Turn off "Linux" mode in the BIOS */ + if (config && config->commands.set_linux != 0xff) + sabi_set_commandb(samsung, config->commands.set_linux, 0x80); + + if (samsung->sabi_iface) { + iounmap(samsung->sabi_iface); + samsung->sabi_iface = NULL; + } + if (samsung->f0000_segment) { + iounmap(samsung->f0000_segment); + samsung->f0000_segment = NULL; + } + + samsung->config = NULL; +} + +static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca, + unsigned int ifaceP) +{ + const struct sabi_config *config = samsung->config; + + printk(KERN_DEBUG "This computer supports SABI==%x\n", + loca + 0xf0000 - 6); + + printk(KERN_DEBUG "SABI header:\n"); + printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", + readw(samsung->sabi + config->header_offsets.port)); + printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", + readb(samsung->sabi + config->header_offsets.iface_func)); + printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", + readb(samsung->sabi + config->header_offsets.en_mem)); + printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", + readb(samsung->sabi + config->header_offsets.re_mem)); + printk(KERN_DEBUG " SABI data offset = 0x%04x\n", + readw(samsung->sabi + config->header_offsets.data_offset)); + printk(KERN_DEBUG " SABI data segment = 0x%04x\n", + readw(samsung->sabi + config->header_offsets.data_segment)); + + printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP); +} + +static void __init samsung_sabi_diag(struct samsung_laptop *samsung) +{ + int loca = find_signature(samsung->f0000_segment, "SDiaG@"); + int i; + + if (loca == 0xffff) + return ; + + /* Example: + * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A + * + * Product name: 90X3A + * BIOS Version: 07HL + */ + loca += 1; + for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) { + char temp = readb(samsung->f0000_segment + loca); + + if (isalnum(temp) || temp == '/' || temp == '-') + samsung->sdiag[i++] = temp; + else + break ; + } + + if (debug && samsung->sdiag[0]) + pr_info("sdiag: %s", samsung->sdiag); +} + +static int __init samsung_sabi_init(struct samsung_laptop *samsung) +{ + const struct sabi_config *config = NULL; + const struct sabi_commands *commands; + unsigned int ifaceP; + int ret = 0; + int i; + int loca; + + samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff); + if (!samsung->f0000_segment) { + if (debug || force) + pr_err("Can't map the segment at 0xf0000\n"); + ret = -EINVAL; + goto exit; + } + + samsung_sabi_diag(samsung); + + /* Try to find one of the signatures in memory to find the header */ + for (i = 0; sabi_configs[i].test_string != 0; ++i) { + samsung->config = &sabi_configs[i]; + loca = find_signature(samsung->f0000_segment, + samsung->config->test_string); + if (loca != 0xffff) + break; + } + + if (loca == 0xffff) { + if (debug || force) + pr_err("This computer does not support SABI\n"); + ret = -ENODEV; + goto exit; + } + + config = samsung->config; + commands = &config->commands; + + /* point to the SMI port Number */ + loca += 1; + samsung->sabi = (samsung->f0000_segment + loca); + + /* Get a pointer to the SABI Interface */ + ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4; + ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff; + + if (debug) + samsung_sabi_infos(samsung, loca, ifaceP); + + samsung->sabi_iface = ioremap_nocache(ifaceP, 16); + if (!samsung->sabi_iface) { + pr_err("Can't remap %x\n", ifaceP); + ret = -EINVAL; + goto exit; + } + + /* Turn on "Linux" mode in the BIOS */ + if (commands->set_linux != 0xff) { + int retval = sabi_set_commandb(samsung, + commands->set_linux, 0x81); + if (retval) { + pr_warn("Linux mode was not set!\n"); + ret = -ENODEV; + goto exit; + } + } + + /* Check for stepping quirk */ + if (samsung->handle_backlight) + check_for_stepping_quirk(samsung); + + pr_info("detected SABI interface: %s\n", + samsung->config->test_string); + +exit: + if (ret) + samsung_sabi_exit(samsung); + + return ret; +} + +static void samsung_platform_exit(struct samsung_laptop *samsung) +{ + if (samsung->platform_device) { + platform_device_unregister(samsung->platform_device); + samsung->platform_device = NULL; + } +} + +static int samsung_pm_notification(struct notifier_block *nb, + unsigned long val, void *ptr) +{ + struct samsung_laptop *samsung; + + samsung = container_of(nb, struct samsung_laptop, pm_nb); + if (val == PM_POST_HIBERNATION && + samsung->quirks->enable_kbd_backlight) + kbd_backlight_enable(samsung); + + return 0; +} + +static int __init samsung_platform_init(struct samsung_laptop *samsung) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("samsung", -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + samsung->platform_device = pdev; + platform_set_drvdata(samsung->platform_device, samsung); + return 0; +} + +static struct samsung_quirks *quirks; + +static int __init samsung_dmi_matched(const struct dmi_system_id *d) +{ + quirks = d->driver_data; + return 0; +} + +static struct dmi_system_id __initdata samsung_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */ + }, + }, + /* DMI ids for laptops with bad Chassis Type */ + { + .ident = "R40/R41", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"), + DMI_MATCH(DMI_BOARD_NAME, "R40/R41"), + }, + }, + /* Specific DMI ids for laptop with quirks */ + { + .callback = samsung_dmi_matched, + .ident = "N150P", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "N150P"), + DMI_MATCH(DMI_BOARD_NAME, "N150P"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "N145P/N250P/N260P", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), + DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "N150/N210/N220", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), + DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "NF110/NF210/NF310", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), + DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "X360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "X360"), + DMI_MATCH(DMI_BOARD_NAME, "X360"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "N250P", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "N250P"), + DMI_MATCH(DMI_BOARD_NAME, "N250P"), + }, + .driver_data = &samsung_broken_acpi_video, + }, + { + .callback = samsung_dmi_matched, + .ident = "730U3E/740U3E", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"), + }, + .driver_data = &samsung_np740u3e, + }, + { }, +}; +MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); + +static struct platform_device *samsung_platform_device; + +static int __init samsung_init(void) +{ + struct samsung_laptop *samsung; + int ret; + + if (efi_enabled(EFI_BOOT)) + return -ENODEV; + + quirks = &samsung_unknown; + if (!force && !dmi_check_system(samsung_dmi_table)) + return -ENODEV; + + samsung = kzalloc(sizeof(*samsung), GFP_KERNEL); + if (!samsung) + return -ENOMEM; + + mutex_init(&samsung->sabi_mutex); + samsung->handle_backlight = true; + samsung->quirks = quirks; + + +#ifdef CONFIG_ACPI + if (samsung->quirks->broken_acpi_video) + acpi_video_dmi_promote_vendor(); + + /* Don't handle backlight here if the acpi video already handle it */ + if (acpi_video_backlight_support()) { + samsung->handle_backlight = false; + } else if (samsung->quirks->broken_acpi_video) { + pr_info("Disabling ACPI video driver\n"); + acpi_video_unregister(); + } +#endif + + ret = samsung_platform_init(samsung); + if (ret) + goto error_platform; + + ret = samsung_sabi_init(samsung); + if (ret) + goto error_sabi; + +#ifdef CONFIG_ACPI + /* Only log that if we are really on a sabi platform */ + if (acpi_video_backlight_support()) + pr_info("Backlight controlled by ACPI video driver\n"); +#endif + + ret = samsung_sysfs_init(samsung); + if (ret) + goto error_sysfs; + + ret = samsung_backlight_init(samsung); + if (ret) + goto error_backlight; + + ret = samsung_rfkill_init(samsung); + if (ret) + goto error_rfkill; + + ret = samsung_leds_init(samsung); + if (ret) + goto error_leds; + + ret = samsung_debugfs_init(samsung); + if (ret) + goto error_debugfs; + + samsung->pm_nb.notifier_call = samsung_pm_notification; + register_pm_notifier(&samsung->pm_nb); + + samsung_platform_device = samsung->platform_device; + return ret; + +error_debugfs: + samsung_leds_exit(samsung); +error_leds: + samsung_rfkill_exit(samsung); +error_rfkill: + samsung_backlight_exit(samsung); +error_backlight: + samsung_sysfs_exit(samsung); +error_sysfs: + samsung_sabi_exit(samsung); +error_sabi: + samsung_platform_exit(samsung); +error_platform: + kfree(samsung); + return ret; +} + +static void __exit samsung_exit(void) +{ + struct samsung_laptop *samsung; + + samsung = platform_get_drvdata(samsung_platform_device); + unregister_pm_notifier(&samsung->pm_nb); + + samsung_debugfs_exit(samsung); + samsung_leds_exit(samsung); + samsung_rfkill_exit(samsung); + samsung_backlight_exit(samsung); + samsung_sysfs_exit(samsung); + samsung_sabi_exit(samsung); + samsung_platform_exit(samsung); + + kfree(samsung); + samsung_platform_device = NULL; +} + +module_init(samsung_init); +module_exit(samsung_exit); + +MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); +MODULE_DESCRIPTION("Samsung Backlight driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/samsung-q10.c b/drivers/platform/x86/samsung-q10.c new file mode 100644 index 00000000000..5413f62d2e6 --- /dev/null +++ b/drivers/platform/x86/samsung-q10.c @@ -0,0 +1,170 @@ +/* + * Driver for Samsung Q10 and related laptops: controls the backlight + * + * Copyright (c) 2011 Frederick van der Wyck <fvanderwyck@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/backlight.h> +#include <linux/dmi.h> +#include <linux/acpi.h> + +#define SAMSUNGQ10_BL_MAX_INTENSITY 7 + +static acpi_handle ec_handle; + +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, + "Disable the DMI check and force the driver to be loaded"); + +static int samsungq10_bl_set_intensity(struct backlight_device *bd) +{ + + acpi_status status; + int i; + + for (i = 0; i < SAMSUNGQ10_BL_MAX_INTENSITY; i++) { + status = acpi_evaluate_object(ec_handle, "_Q63", NULL, NULL); + if (ACPI_FAILURE(status)) + return -EIO; + } + for (i = 0; i < bd->props.brightness; i++) { + status = acpi_evaluate_object(ec_handle, "_Q64", NULL, NULL); + if (ACPI_FAILURE(status)) + return -EIO; + } + + return 0; +} + +static int samsungq10_bl_get_intensity(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static const struct backlight_ops samsungq10_bl_ops = { + .get_brightness = samsungq10_bl_get_intensity, + .update_status = samsungq10_bl_set_intensity, +}; + +static int samsungq10_probe(struct platform_device *pdev) +{ + + struct backlight_properties props; + struct backlight_device *bd; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = SAMSUNGQ10_BL_MAX_INTENSITY; + bd = backlight_device_register("samsung", &pdev->dev, NULL, + &samsungq10_bl_ops, &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + platform_set_drvdata(pdev, bd); + + return 0; +} + +static int samsungq10_remove(struct platform_device *pdev) +{ + + struct backlight_device *bd = platform_get_drvdata(pdev); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver samsungq10_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + }, + .probe = samsungq10_probe, + .remove = samsungq10_remove, +}; + +static struct platform_device *samsungq10_device; + +static int __init dmi_check_callback(const struct dmi_system_id *id) +{ + printk(KERN_INFO KBUILD_MODNAME ": found model '%s'\n", id->ident); + return 1; +} + +static struct dmi_system_id __initdata samsungq10_dmi_table[] = { + { + .ident = "Samsung Q10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Samsung"), + DMI_MATCH(DMI_PRODUCT_NAME, "SQ10"), + }, + .callback = dmi_check_callback, + }, + { + .ident = "Samsung Q20", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG Electronics"), + DMI_MATCH(DMI_PRODUCT_NAME, "SENS Q20"), + }, + .callback = dmi_check_callback, + }, + { + .ident = "Samsung Q25", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG Electronics"), + DMI_MATCH(DMI_PRODUCT_NAME, "NQ25"), + }, + .callback = dmi_check_callback, + }, + { + .ident = "Dell Latitude X200", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "X200"), + }, + .callback = dmi_check_callback, + }, + { }, +}; +MODULE_DEVICE_TABLE(dmi, samsungq10_dmi_table); + +static int __init samsungq10_init(void) +{ + if (!force && !dmi_check_system(samsungq10_dmi_table)) + return -ENODEV; + + ec_handle = ec_get_handle(); + + if (!ec_handle) + return -ENODEV; + + samsungq10_device = platform_create_bundle(&samsungq10_driver, + samsungq10_probe, + NULL, 0, NULL, 0); + + return PTR_ERR_OR_ZERO(samsungq10_device); +} + +static void __exit samsungq10_exit(void) +{ + platform_device_unregister(samsungq10_device); + platform_driver_unregister(&samsungq10_driver); +} + +module_init(samsungq10_init); +module_exit(samsungq10_exit); + +MODULE_AUTHOR("Frederick van der Wyck <fvanderwyck@gmail.com>"); +MODULE_DESCRIPTION("Samsung Q10 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 5af53340da6..9c5a07417b2 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -42,6 +42,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> @@ -58,9 +60,7 @@ #include <linux/kfifo.h> #include <linux/workqueue.h> #include <linux/acpi.h> -#include <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> -#include <asm/uaccess.h> +#include <linux/slab.h> #include <linux/sonypi.h> #include <linux/sony-laptop.h> #include <linux/rfkill.h> @@ -68,14 +68,14 @@ #include <linux/poll.h> #include <linux/miscdevice.h> #endif +#include <asm/uaccess.h> -#define DRV_PFX "sony-laptop: " -#define dprintk(msg...) do { \ - if (debug) printk(KERN_WARNING DRV_PFX msg); \ +#define dprintk(fmt, ...) \ +do { \ + if (debug) \ + pr_warn(fmt, ##__VA_ARGS__); \ } while (0) -#define SONY_LAPTOP_DRIVER_VERSION "0.6" - #define SONY_NC_CLASS "sony-nc" #define SONY_NC_HID "SNY5001" #define SONY_NC_DRIVER_NAME "Sony Notebook Control Driver" @@ -87,7 +87,6 @@ MODULE_AUTHOR("Stelian Pop, Mattia Dongili"); MODULE_DESCRIPTION("Sony laptop extras driver (SPIC and SNC ACPI device)"); MODULE_LICENSE("GPL"); -MODULE_VERSION(SONY_LAPTOP_DRIVER_VERSION); static int debug; module_param(debug, int, 0); @@ -123,6 +122,65 @@ MODULE_PARM_DESC(minor, "default is -1 (automatic)"); #endif +static int kbd_backlight = -1; +module_param(kbd_backlight, int, 0444); +MODULE_PARM_DESC(kbd_backlight, + "set this to 0 to disable keyboard backlight, " + "1 to enable it with automatic control and 2 to have it always " + "on (default: no change from current value)"); + +static int kbd_backlight_timeout = -1; +module_param(kbd_backlight_timeout, int, 0444); +MODULE_PARM_DESC(kbd_backlight_timeout, + "meaningful values vary from 0 to 3 and their meaning depends " + "on the model (default: no change from current value)"); + +#ifdef CONFIG_PM_SLEEP +static void sony_nc_thermal_resume(void); +#endif +static int sony_nc_kbd_backlight_setup(struct platform_device *pd, + unsigned int handle); +static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd, + unsigned int handle); + +static int sony_nc_battery_care_setup(struct platform_device *pd, + unsigned int handle); +static void sony_nc_battery_care_cleanup(struct platform_device *pd); + +static int sony_nc_thermal_setup(struct platform_device *pd); +static void sony_nc_thermal_cleanup(struct platform_device *pd); + +static int sony_nc_lid_resume_setup(struct platform_device *pd, + unsigned int handle); +static void sony_nc_lid_resume_cleanup(struct platform_device *pd); + +static int sony_nc_gfx_switch_setup(struct platform_device *pd, + unsigned int handle); +static void sony_nc_gfx_switch_cleanup(struct platform_device *pd); +static int __sony_nc_gfx_switch_status_get(void); + +static int sony_nc_highspeed_charging_setup(struct platform_device *pd); +static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd); + +static int sony_nc_lowbatt_setup(struct platform_device *pd); +static void sony_nc_lowbatt_cleanup(struct platform_device *pd); + +static int sony_nc_fanspeed_setup(struct platform_device *pd); +static void sony_nc_fanspeed_cleanup(struct platform_device *pd); + +static int sony_nc_usb_charge_setup(struct platform_device *pd); +static void sony_nc_usb_charge_cleanup(struct platform_device *pd); + +static int sony_nc_panelid_setup(struct platform_device *pd); +static void sony_nc_panelid_cleanup(struct platform_device *pd); + +static int sony_nc_smart_conn_setup(struct platform_device *pd); +static void sony_nc_smart_conn_cleanup(struct platform_device *pd); + +static int sony_nc_touchpad_setup(struct platform_device *pd, + unsigned int handle); +static void sony_nc_touchpad_cleanup(struct platform_device *pd); + enum sony_nc_rfkill { SONY_WIFI, SONY_BLUETOOTH, @@ -134,6 +192,9 @@ enum sony_nc_rfkill { static int sony_rfkill_handle; static struct rfkill *sony_rfkill_devices[N_SONY_RFKILL]; static int sony_rfkill_address[N_SONY_RFKILL] = {0x300, 0x500, 0x700, 0x900}; +static int sony_nc_rfkill_setup(struct acpi_device *device, + unsigned int handle); +static void sony_nc_rfkill_cleanup(void); static void sony_nc_rfkill_update(void); /*********** Input Devices ***********/ @@ -145,7 +206,7 @@ struct sony_laptop_input_s { struct input_dev *key_dev; struct kfifo fifo; spinlock_t fifo_lock; - struct workqueue_struct *wq; + struct timer_list release_key_timer; }; static struct sony_laptop_input_s sony_laptop_input = { @@ -234,6 +295,7 @@ static int sony_laptop_input_index[] = { 57, /* 70 SONYPI_EVENT_VOLUME_DEC_PRESSED */ -1, /* 71 SONYPI_EVENT_BRIGHTNESS_PRESSED */ 58, /* 72 SONYPI_EVENT_MEDIA_PRESSED */ + 59, /* 72 SONYPI_EVENT_VENDOR_PRESSED */ }; static int sony_laptop_input_keycode_map[] = { @@ -254,8 +316,8 @@ static int sony_laptop_input_keycode_map[] = { KEY_FN_F10, /* 14 SONYPI_EVENT_FNKEY_F10 */ KEY_FN_F11, /* 15 SONYPI_EVENT_FNKEY_F11 */ KEY_FN_F12, /* 16 SONYPI_EVENT_FNKEY_F12 */ - KEY_FN_F1, /* 17 SONYPI_EVENT_FNKEY_1 */ - KEY_FN_F2, /* 18 SONYPI_EVENT_FNKEY_2 */ + KEY_FN_1, /* 17 SONYPI_EVENT_FNKEY_1 */ + KEY_FN_2, /* 18 SONYPI_EVENT_FNKEY_2 */ KEY_FN_D, /* 19 SONYPI_EVENT_FNKEY_D */ KEY_FN_E, /* 20 SONYPI_EVENT_FNKEY_E */ KEY_FN_F, /* 21 SONYPI_EVENT_FNKEY_F */ @@ -296,23 +358,30 @@ static int sony_laptop_input_keycode_map[] = { KEY_VOLUMEUP, /* 56 SONYPI_EVENT_VOLUME_INC_PRESSED */ KEY_VOLUMEDOWN, /* 57 SONYPI_EVENT_VOLUME_DEC_PRESSED */ KEY_MEDIA, /* 58 SONYPI_EVENT_MEDIA_PRESSED */ + KEY_VENDOR, /* 59 SONYPI_EVENT_VENDOR_PRESSED */ }; /* release buttons after a short delay if pressed */ -static void do_sony_laptop_release_key(struct work_struct *work) +static void do_sony_laptop_release_key(unsigned long unused) { struct sony_laptop_keypress kp; + unsigned long flags; + + spin_lock_irqsave(&sony_laptop_input.fifo_lock, flags); - while (kfifo_out_locked(&sony_laptop_input.fifo, (unsigned char *)&kp, - sizeof(kp), &sony_laptop_input.fifo_lock) - == sizeof(kp)) { - msleep(10); + if (kfifo_out(&sony_laptop_input.fifo, + (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) { input_report_key(kp.dev, kp.key, 0); input_sync(kp.dev); } + + /* If there is something in the fifo schedule next release. */ + if (kfifo_len(&sony_laptop_input.fifo) != 0) + mod_timer(&sony_laptop_input.release_key_timer, + jiffies + msecs_to_jiffies(10)); + + spin_unlock_irqrestore(&sony_laptop_input.fifo_lock, flags); } -static DECLARE_WORK(sony_laptop_release_key_work, - do_sony_laptop_release_key); /* forward event to the input subsystem */ static void sony_laptop_report_input_event(u8 event) @@ -320,6 +389,7 @@ static void sony_laptop_report_input_event(u8 event) struct input_dev *jog_dev = sony_laptop_input.jog_dev; struct input_dev *key_dev = sony_laptop_input.key_dev; struct sony_laptop_keypress kp = { NULL }; + int scancode = -1; if (event == SONYPI_EVENT_FNKEY_RELEASED || event == SONYPI_EVENT_ANYBUTTON_RELEASED) { @@ -353,8 +423,8 @@ static void sony_laptop_report_input_event(u8 event) dprintk("sony_laptop_report_input_event, event not known: %d\n", event); break; } - if (sony_laptop_input_index[event] != -1) { - kp.key = sony_laptop_input_keycode_map[sony_laptop_input_index[event]]; + if ((scancode = sony_laptop_input_index[event]) != -1) { + kp.key = sony_laptop_input_keycode_map[scancode]; if (kp.key != KEY_UNKNOWN) kp.dev = key_dev; } @@ -362,17 +432,19 @@ static void sony_laptop_report_input_event(u8 event) } if (kp.dev) { + /* if we have a scancode we emit it so we can always + remap the key */ + if (scancode != -1) + input_event(kp.dev, EV_MSC, MSC_SCAN, scancode); input_report_key(kp.dev, kp.key, 1); - /* we emit the scancode so we can always remap the key */ - input_event(kp.dev, EV_MSC, MSC_SCAN, event); input_sync(kp.dev); - kfifo_in_locked(&sony_laptop_input.fifo, - (unsigned char *)&kp, sizeof(kp), - &sony_laptop_input.fifo_lock); - if (!work_pending(&sony_laptop_release_key_work)) - queue_work(sony_laptop_input.wq, - &sony_laptop_release_key_work); + /* schedule key release */ + kfifo_in_locked(&sony_laptop_input.fifo, + (unsigned char *)&kp, sizeof(kp), + &sony_laptop_input.fifo_lock); + mod_timer(&sony_laptop_input.release_key_timer, + jiffies + msecs_to_jiffies(10)); } else dprintk("unknown input event %.2x\n", event); } @@ -390,27 +462,21 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device) /* kfifo */ spin_lock_init(&sony_laptop_input.fifo_lock); - error = - kfifo_alloc(&sony_laptop_input.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL); + error = kfifo_alloc(&sony_laptop_input.fifo, + SONY_LAPTOP_BUF_SIZE, GFP_KERNEL); if (error) { - printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n"); + pr_err("kfifo_alloc failed\n"); goto err_dec_users; } - /* init workqueue */ - sony_laptop_input.wq = create_singlethread_workqueue("sony-laptop"); - if (!sony_laptop_input.wq) { - printk(KERN_ERR DRV_PFX - "Unable to create workqueue.\n"); - error = -ENXIO; - goto err_free_kfifo; - } + setup_timer(&sony_laptop_input.release_key_timer, + do_sony_laptop_release_key, 0); /* input keys */ key_dev = input_allocate_device(); if (!key_dev) { error = -ENOMEM; - goto err_destroy_wq; + goto err_free_kfifo; } key_dev->name = "Sony Vaio Keys"; @@ -419,18 +485,15 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device) key_dev->dev.parent = &acpi_device->dev; /* Initialize the Input Drivers: special keys */ - set_bit(EV_KEY, key_dev->evbit); - set_bit(EV_MSC, key_dev->evbit); - set_bit(MSC_SCAN, key_dev->mscbit); + input_set_capability(key_dev, EV_MSC, MSC_SCAN); + + __set_bit(EV_KEY, key_dev->evbit); key_dev->keycodesize = sizeof(sony_laptop_input_keycode_map[0]); key_dev->keycodemax = ARRAY_SIZE(sony_laptop_input_keycode_map); key_dev->keycode = &sony_laptop_input_keycode_map; - for (i = 0; i < ARRAY_SIZE(sony_laptop_input_keycode_map); i++) { - if (sony_laptop_input_keycode_map[i] != KEY_RESERVED) { - set_bit(sony_laptop_input_keycode_map[i], - key_dev->keybit); - } - } + for (i = 0; i < ARRAY_SIZE(sony_laptop_input_keycode_map); i++) + __set_bit(sony_laptop_input_keycode_map[i], key_dev->keybit); + __clear_bit(KEY_RESERVED, key_dev->keybit); error = input_register_device(key_dev); if (error) @@ -448,11 +511,10 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device) jog_dev->name = "Sony Vaio Jogdial"; jog_dev->id.bustype = BUS_ISA; jog_dev->id.vendor = PCI_VENDOR_ID_SONY; - key_dev->dev.parent = &acpi_device->dev; + jog_dev->dev.parent = &acpi_device->dev; - jog_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); - jog_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_MIDDLE); - jog_dev->relbit[0] = BIT_MASK(REL_WHEEL); + input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE); + input_set_capability(jog_dev, EV_REL, REL_WHEEL); error = input_register_device(jog_dev); if (error) @@ -473,9 +535,6 @@ err_unregister_keydev: err_free_keydev: input_free_device(key_dev); -err_destroy_wq: - destroy_workqueue(sony_laptop_input.wq); - err_free_kfifo: kfifo_free(&sony_laptop_input.fifo); @@ -486,12 +545,23 @@ err_dec_users: static void sony_laptop_remove_input(void) { - /* cleanup only after the last user has gone */ + struct sony_laptop_keypress kp = { NULL }; + + /* Cleanup only after the last user has gone */ if (!atomic_dec_and_test(&sony_laptop_input.users)) return; - /* flush workqueue first */ - flush_workqueue(sony_laptop_input.wq); + del_timer_sync(&sony_laptop_input.release_key_timer); + + /* + * Generate key-up events for remaining keys. Note that we don't + * need locking since nobody is adding new events to the kfifo. + */ + while (kfifo_out(&sony_laptop_input.fifo, + (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) { + input_report_key(kp.dev, kp.key, 0); + input_sync(kp.dev); + } /* destroy input devs */ input_unregister_device(sony_laptop_input.key_dev); @@ -502,7 +572,6 @@ static void sony_laptop_remove_input(void) sony_laptop_input.jog_dev = NULL; } - destroy_workqueue(sony_laptop_input.wq); kfifo_free(&sony_laptop_input.fifo); } @@ -557,8 +626,7 @@ static void sony_pf_remove(void) if (!atomic_dec_and_test(&sony_pf_users)) return; - platform_device_del(sony_pf_device); - platform_device_put(sony_pf_device); + platform_device_unregister(sony_pf_device); platform_driver_unregister(&sony_pf_driver); } @@ -586,7 +654,7 @@ struct sony_nc_value { int value; /* current setting */ int valid; /* Has ever been set */ int debug; /* active only in debug mode ? */ - struct device_attribute devattr; /* sysfs atribute */ + struct device_attribute devattr; /* sysfs attribute */ }; #define SNC_HANDLE_NAMES(_name, _values...) \ @@ -665,85 +733,200 @@ static struct acpi_device *sony_nc_acpi_device = NULL; /* * acpi_evaluate_object wrappers + * all useful calls into SNC methods take one or zero parameters and return + * integers or arrays. */ -static int acpi_callgetfunc(acpi_handle handle, char *name, int *result) +static union acpi_object *__call_snc_method(acpi_handle handle, char *method, + u64 *value) { - struct acpi_buffer output; - union acpi_object out_obj; + union acpi_object *result = NULL; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; acpi_status status; - output.length = sizeof(out_obj); - output.pointer = &out_obj; + if (value) { + struct acpi_object_list params; + union acpi_object in; + in.type = ACPI_TYPE_INTEGER; + in.integer.value = *value; + params.count = 1; + params.pointer = ∈ + status = acpi_evaluate_object(handle, method, ¶ms, &output); + dprintk("__call_snc_method: [%s:0x%.8x%.8x]\n", method, + (unsigned int)(*value >> 32), + (unsigned int)*value & 0xffffffff); + } else { + status = acpi_evaluate_object(handle, method, NULL, &output); + dprintk("__call_snc_method: [%s]\n", method); + } - status = acpi_evaluate_object(handle, name, NULL, &output); - if ((status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER)) { - *result = out_obj.integer.value; - return 0; + if (ACPI_FAILURE(status)) { + pr_err("Failed to evaluate [%s]\n", method); + return NULL; } - printk(KERN_WARNING DRV_PFX "acpi_callreadfunc failed\n"); + result = (union acpi_object *) output.pointer; + if (!result) + dprintk("No return object [%s]\n", method); - return -1; + return result; } -static int acpi_callsetfunc(acpi_handle handle, char *name, int value, - int *result) +static int sony_nc_int_call(acpi_handle handle, char *name, int *value, + int *result) { - struct acpi_object_list params; - union acpi_object in_obj; - struct acpi_buffer output; - union acpi_object out_obj; - acpi_status status; + union acpi_object *object = NULL; + if (value) { + u64 v = *value; + object = __call_snc_method(handle, name, &v); + } else + object = __call_snc_method(handle, name, NULL); - params.count = 1; - params.pointer = &in_obj; - in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = value; - - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = acpi_evaluate_object(handle, name, ¶ms, &output); - if (status == AE_OK) { - if (result != NULL) { - if (out_obj.type != ACPI_TYPE_INTEGER) { - printk(KERN_WARNING DRV_PFX "acpi_evaluate_object bad " - "return type\n"); - return -1; - } - *result = out_obj.integer.value; + if (!object) + return -EINVAL; + + if (object->type != ACPI_TYPE_INTEGER) { + pr_warn("Invalid acpi_object: expected 0x%x got 0x%x\n", + ACPI_TYPE_INTEGER, object->type); + kfree(object); + return -EINVAL; + } + + if (result) + *result = object->integer.value; + + kfree(object); + return 0; +} + +#define MIN(a, b) (a > b ? b : a) +static int sony_nc_buffer_call(acpi_handle handle, char *name, u64 *value, + void *buffer, size_t buflen) +{ + int ret = 0; + size_t len; + union acpi_object *object = __call_snc_method(handle, name, value); + + if (!object) + return -EINVAL; + + if (object->type == ACPI_TYPE_BUFFER) { + len = MIN(buflen, object->buffer.length); + memcpy(buffer, object->buffer.pointer, len); + + } else if (object->type == ACPI_TYPE_INTEGER) { + len = MIN(buflen, sizeof(object->integer.value)); + memcpy(buffer, &object->integer.value, len); + + } else { + pr_warn("Invalid acpi_object: expected 0x%x got 0x%x\n", + ACPI_TYPE_BUFFER, object->type); + ret = -EINVAL; + } + + kfree(object); + return ret; +} + +struct sony_nc_handles { + u16 cap[0x10]; + struct device_attribute devattr; +}; + +static struct sony_nc_handles *handles; + +static ssize_t sony_nc_handles_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(handles->cap); i++) { + len += snprintf(buffer + len, PAGE_SIZE - len, "0x%.4x ", + handles->cap[i]); + } + len += snprintf(buffer + len, PAGE_SIZE - len, "\n"); + + return len; +} + +static int sony_nc_handles_setup(struct platform_device *pd) +{ + int i, r, result, arg; + + handles = kzalloc(sizeof(*handles), GFP_KERNEL); + if (!handles) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(handles->cap); i++) { + arg = i + 0x20; + r = sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg, + &result); + if (!r) { + dprintk("caching handle 0x%.4x (offset: 0x%.2x)\n", + result, i); + handles->cap[i] = result; + } + } + + if (debug) { + sysfs_attr_init(&handles->devattr.attr); + handles->devattr.attr.name = "handles"; + handles->devattr.attr.mode = S_IRUGO; + handles->devattr.show = sony_nc_handles_show; + + /* allow reading capabilities via sysfs */ + if (device_create_file(&pd->dev, &handles->devattr)) { + kfree(handles); + handles = NULL; + return -1; } - return 0; } - printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n"); + return 0; +} - return -1; +static int sony_nc_handles_cleanup(struct platform_device *pd) +{ + if (handles) { + if (debug) + device_remove_file(&pd->dev, &handles->devattr); + kfree(handles); + handles = NULL; + } + return 0; } static int sony_find_snc_handle(int handle) { int i; - int result; - for (i = 0x20; i < 0x30; i++) { - acpi_callsetfunc(sony_nc_acpi_handle, "SN00", i, &result); - if (result == handle) - return i-0x20; - } + /* not initialized yet, return early */ + if (!handles || !handle) + return -EINVAL; - return -1; + for (i = 0; i < 0x10; i++) { + if (handles->cap[i] == handle) { + dprintk("found handle 0x%.4x (offset: 0x%.2x)\n", + handle, i); + return i; + } + } + dprintk("handle 0x%.4x not found\n", handle); + return -EINVAL; } static int sony_call_snc_handle(int handle, int argument, int *result) { + int arg, ret = 0; int offset = sony_find_snc_handle(handle); if (offset < 0) - return -1; + return offset; - return acpi_callsetfunc(sony_nc_acpi_handle, "SN07", offset | argument, - result); + arg = offset | argument; + ret = sony_nc_int_call(sony_nc_acpi_handle, "SN07", &arg, result); + dprintk("called SN07 with 0x%.4x (result: 0x%.4x)\n", arg, *result); + return ret; } /* @@ -787,14 +970,16 @@ static int boolean_validate(const int direction, const int value) static ssize_t sony_nc_sysfs_show(struct device *dev, struct device_attribute *attr, char *buffer) { - int value; + int value, ret = 0; struct sony_nc_value *item = container_of(attr, struct sony_nc_value, devattr); if (!*item->acpiget) return -EIO; - if (acpi_callgetfunc(sony_nc_acpi_handle, *item->acpiget, &value) < 0) + ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiget, NULL, + &value); + if (ret < 0) return -EIO; if (item->validate) @@ -808,6 +993,7 @@ static ssize_t sony_nc_sysfs_store(struct device *dev, const char *buffer, size_t count) { int value; + int ret = 0; struct sony_nc_value *item = container_of(attr, struct sony_nc_value, devattr); @@ -817,7 +1003,8 @@ static ssize_t sony_nc_sysfs_store(struct device *dev, if (count > 31) return -EINVAL; - value = simple_strtoul(buffer, NULL, 10); + if (kstrtoint(buffer, 10, &value)) + return -EINVAL; if (item->validate) value = item->validate(SNC_VALIDATE_IN, value); @@ -825,8 +1012,11 @@ static ssize_t sony_nc_sysfs_store(struct device *dev, if (value < 0) return value; - if (acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, value, NULL) < 0) + ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiset, + &value, NULL); + if (ret < 0) return -EIO; + item->value = value; item->valid = 1; return count; @@ -836,27 +1026,66 @@ static ssize_t sony_nc_sysfs_store(struct device *dev, /* * Backlight device */ +struct sony_backlight_props { + struct backlight_device *dev; + int handle; + int cmd_base; + u8 offset; + u8 maxlvl; +}; +struct sony_backlight_props sony_bl_props; + static int sony_backlight_update_status(struct backlight_device *bd) { - return acpi_callsetfunc(sony_nc_acpi_handle, "SBRT", - bd->props.brightness + 1, NULL); + int arg = bd->props.brightness + 1; + return sony_nc_int_call(sony_nc_acpi_handle, "SBRT", &arg, NULL); } static int sony_backlight_get_brightness(struct backlight_device *bd) { int value; - if (acpi_callgetfunc(sony_nc_acpi_handle, "GBRT", &value)) + if (sony_nc_int_call(sony_nc_acpi_handle, "GBRT", NULL, &value)) return 0; /* brightness levels are 1-based, while backlight ones are 0-based */ return value - 1; } -static struct backlight_device *sony_backlight_device; -static struct backlight_ops sony_backlight_ops = { +static int sony_nc_get_brightness_ng(struct backlight_device *bd) +{ + int result; + struct sony_backlight_props *sdev = + (struct sony_backlight_props *)bl_get_data(bd); + + sony_call_snc_handle(sdev->handle, sdev->cmd_base + 0x100, &result); + + return (result & 0xff) - sdev->offset; +} + +static int sony_nc_update_status_ng(struct backlight_device *bd) +{ + int value, result; + struct sony_backlight_props *sdev = + (struct sony_backlight_props *)bl_get_data(bd); + + value = bd->props.brightness + sdev->offset; + if (sony_call_snc_handle(sdev->handle, sdev->cmd_base | (value << 0x10), + &result)) + return -EIO; + + return value; +} + +static const struct backlight_ops sony_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, .update_status = sony_backlight_update_status, .get_brightness = sony_backlight_get_brightness, }; +static const struct backlight_ops sony_backlight_ng_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = sony_nc_update_status_ng, + .get_brightness = sony_nc_get_brightness_ng, +}; /* * New SNC-only Vaios event mapping to driver known keys @@ -885,16 +1114,30 @@ static struct sony_nc_event sony_100_events[] = { { 0x06, SONYPI_EVENT_FNKEY_RELEASED }, { 0x87, SONYPI_EVENT_FNKEY_F7 }, { 0x07, SONYPI_EVENT_FNKEY_RELEASED }, + { 0x88, SONYPI_EVENT_FNKEY_F8 }, + { 0x08, SONYPI_EVENT_FNKEY_RELEASED }, { 0x89, SONYPI_EVENT_FNKEY_F9 }, { 0x09, SONYPI_EVENT_FNKEY_RELEASED }, { 0x8A, SONYPI_EVENT_FNKEY_F10 }, { 0x0A, SONYPI_EVENT_FNKEY_RELEASED }, + { 0x8B, SONYPI_EVENT_FNKEY_F11 }, + { 0x0B, SONYPI_EVENT_FNKEY_RELEASED }, { 0x8C, SONYPI_EVENT_FNKEY_F12 }, { 0x0C, SONYPI_EVENT_FNKEY_RELEASED }, + { 0x9d, SONYPI_EVENT_ZOOM_PRESSED }, + { 0x1d, SONYPI_EVENT_ANYBUTTON_RELEASED }, { 0x9f, SONYPI_EVENT_CD_EJECT_PRESSED }, { 0x1f, SONYPI_EVENT_ANYBUTTON_RELEASED }, { 0xa1, SONYPI_EVENT_MEDIA_PRESSED }, { 0x21, SONYPI_EVENT_ANYBUTTON_RELEASED }, + { 0xa4, SONYPI_EVENT_CD_EJECT_PRESSED }, + { 0x24, SONYPI_EVENT_ANYBUTTON_RELEASED }, + { 0xa5, SONYPI_EVENT_VENDOR_PRESSED }, + { 0x25, SONYPI_EVENT_ANYBUTTON_RELEASED }, + { 0xa6, SONYPI_EVENT_HELP_PRESSED }, + { 0x26, SONYPI_EVENT_ANYBUTTON_RELEASED }, + { 0xa8, SONYPI_EVENT_FNKEY_1 }, + { 0x28, SONYPI_EVENT_ANYBUTTON_RELEASED }, { 0, 0 }, }; @@ -916,65 +1159,137 @@ static struct sony_nc_event sony_127_events[] = { { 0, 0 }, }; +static int sony_nc_hotkeys_decode(u32 event, unsigned int handle) +{ + int ret = -EINVAL; + unsigned int result = 0; + struct sony_nc_event *key_event; + + if (sony_call_snc_handle(handle, 0x200, &result)) { + dprintk("Unable to decode event 0x%.2x 0x%.2x\n", handle, + event); + return -EINVAL; + } + + result &= 0xFF; + + if (handle == 0x0100) + key_event = sony_100_events; + else + key_event = sony_127_events; + + for (; key_event->data; key_event++) { + if (key_event->data == result) { + ret = key_event->event; + break; + } + } + + if (!key_event->data) + pr_info("Unknown hotkey 0x%.2x/0x%.2x (handle 0x%.2x)\n", + event, result, handle); + + return ret; +} + /* * ACPI callbacks */ +enum event_types { + HOTKEY = 1, + KILLSWITCH, + GFX_SWITCH +}; static void sony_nc_notify(struct acpi_device *device, u32 event) { - u32 ev = event; - - if (ev >= 0x90) { - /* New-style event */ - int result; - int key_handle = 0; - ev -= 0x90; - - if (sony_find_snc_handle(0x100) == ev) - key_handle = 0x100; - if (sony_find_snc_handle(0x127) == ev) - key_handle = 0x127; - - if (key_handle) { - struct sony_nc_event *key_event; - - if (sony_call_snc_handle(key_handle, 0x200, &result)) { - dprintk("sony_nc_notify, unable to decode" - " event 0x%.2x 0x%.2x\n", key_handle, - ev); - /* restore the original event */ - ev = event; - } else { - ev = result & 0xFF; - - if (key_handle == 0x100) - key_event = sony_100_events; - else - key_event = sony_127_events; - - for (; key_event->data; key_event++) { - if (key_event->data == ev) { - ev = key_event->event; - break; - } - } - - if (!key_event->data) - printk(KERN_INFO DRV_PFX - "Unknown event: 0x%x 0x%x\n", - key_handle, - ev); - else - sony_laptop_report_input_event(ev); - } - } else if (sony_find_snc_handle(sony_rfkill_handle) == ev) { - sony_nc_rfkill_update(); + u32 real_ev = event; + u8 ev_type = 0; + dprintk("sony_nc_notify, event: 0x%.2x\n", event); + + if (event >= 0x90) { + unsigned int result = 0; + unsigned int arg = 0; + unsigned int handle = 0; + unsigned int offset = event - 0x90; + + if (offset >= ARRAY_SIZE(handles->cap)) { + pr_err("Event 0x%x outside of capabilities list\n", + event); return; } - } else - sony_laptop_report_input_event(ev); + handle = handles->cap[offset]; + + /* list of handles known for generating events */ + switch (handle) { + /* hotkey event */ + case 0x0100: + case 0x0127: + ev_type = HOTKEY; + real_ev = sony_nc_hotkeys_decode(event, handle); + + if (real_ev > 0) + sony_laptop_report_input_event(real_ev); + else + /* restore the original event for reporting */ + real_ev = event; + + break; + + /* wlan switch */ + case 0x0124: + case 0x0135: + /* events on this handle are reported when the + * switch changes position or for battery + * events. We'll notify both of them but only + * update the rfkill device status when the + * switch is moved. + */ + ev_type = KILLSWITCH; + sony_call_snc_handle(handle, 0x0100, &result); + real_ev = result & 0x03; + + /* hw switch event */ + if (real_ev == 1) + sony_nc_rfkill_update(); + + break; + + case 0x0128: + case 0x0146: + /* Hybrid GFX switching */ + sony_call_snc_handle(handle, 0x0000, &result); + dprintk("GFX switch event received (reason: %s)\n", + (result == 0x1) ? "switch change" : + (result == 0x2) ? "output switch" : + (result == 0x3) ? "output switch" : + ""); + + ev_type = GFX_SWITCH; + real_ev = __sony_nc_gfx_switch_status_get(); + break; + + case 0x015B: + /* Hybrid GFX switching SVS151290S */ + ev_type = GFX_SWITCH; + real_ev = __sony_nc_gfx_switch_status_get(); + break; + default: + dprintk("Unknown event 0x%x for handle 0x%x\n", + event, handle); + break; + } + + /* clear the event (and the event reason when present) */ + arg = 1 << offset; + sony_nc_int_call(sony_nc_acpi_handle, "SN05", &arg, &result); - dprintk("sony_nc_notify, event: 0x%.2x\n", ev); - acpi_bus_generate_proc_event(sony_nc_acpi_device, 1, ev); + } else { + /* old style event */ + ev_type = HOTKEY; + sony_laptop_report_input_event(real_ev); + } + acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class, + dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev); } static acpi_status sony_walk_callback(acpi_handle handle, u32 level, @@ -983,7 +1298,7 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level, struct acpi_device_info *info; if (ACPI_SUCCESS(acpi_get_object_info(handle, &info))) { - printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n", + pr_warn("method: name: %4.4s, args %X\n", (char *)&info->name, info->param_count); kfree(info); @@ -995,62 +1310,286 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level, /* * ACPI device */ -static int sony_nc_function_setup(struct acpi_device *device) +static void sony_nc_function_setup(struct acpi_device *device, + struct platform_device *pf_device) { - int result; + unsigned int i, result, bitmask, arg; + + if (!handles) + return; + + /* setup found handles here */ + for (i = 0; i < ARRAY_SIZE(handles->cap); i++) { + unsigned int handle = handles->cap[i]; + + if (!handle) + continue; + + dprintk("setting up handle 0x%.4x\n", handle); + + switch (handle) { + case 0x0100: + case 0x0101: + case 0x0127: + /* setup hotkeys */ + sony_call_snc_handle(handle, 0, &result); + break; + case 0x0102: + /* setup hotkeys */ + sony_call_snc_handle(handle, 0x100, &result); + break; + case 0x0105: + case 0x0148: + /* touchpad enable/disable */ + result = sony_nc_touchpad_setup(pf_device, handle); + if (result) + pr_err("couldn't set up touchpad control function (%d)\n", + result); + break; + case 0x0115: + case 0x0136: + case 0x013f: + result = sony_nc_battery_care_setup(pf_device, handle); + if (result) + pr_err("couldn't set up battery care function (%d)\n", + result); + break; + case 0x0119: + case 0x015D: + result = sony_nc_lid_resume_setup(pf_device, handle); + if (result) + pr_err("couldn't set up lid resume function (%d)\n", + result); + break; + case 0x0122: + result = sony_nc_thermal_setup(pf_device); + if (result) + pr_err("couldn't set up thermal profile function (%d)\n", + result); + break; + case 0x0128: + case 0x0146: + case 0x015B: + result = sony_nc_gfx_switch_setup(pf_device, handle); + if (result) + pr_err("couldn't set up GFX Switch status (%d)\n", + result); + break; + case 0x0131: + result = sony_nc_highspeed_charging_setup(pf_device); + if (result) + pr_err("couldn't set up high speed charging function (%d)\n", + result); + break; + case 0x0124: + case 0x0135: + result = sony_nc_rfkill_setup(device, handle); + if (result) + pr_err("couldn't set up rfkill support (%d)\n", + result); + break; + case 0x0137: + case 0x0143: + case 0x014b: + case 0x014c: + case 0x0163: + result = sony_nc_kbd_backlight_setup(pf_device, handle); + if (result) + pr_err("couldn't set up keyboard backlight function (%d)\n", + result); + break; + case 0x0121: + result = sony_nc_lowbatt_setup(pf_device); + if (result) + pr_err("couldn't set up low battery function (%d)\n", + result); + break; + case 0x0149: + result = sony_nc_fanspeed_setup(pf_device); + if (result) + pr_err("couldn't set up fan speed function (%d)\n", + result); + break; + case 0x0155: + result = sony_nc_usb_charge_setup(pf_device); + if (result) + pr_err("couldn't set up USB charge support (%d)\n", + result); + break; + case 0x011D: + result = sony_nc_panelid_setup(pf_device); + if (result) + pr_err("couldn't set up panel ID function (%d)\n", + result); + break; + case 0x0168: + result = sony_nc_smart_conn_setup(pf_device); + if (result) + pr_err("couldn't set up smart connect support (%d)\n", + result); + break; + default: + continue; + } + } /* Enable all events */ - acpi_callsetfunc(sony_nc_acpi_handle, "SN02", 0xffff, &result); + arg = 0x10; + if (!sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg, &bitmask)) + sony_nc_int_call(sony_nc_acpi_handle, "SN02", &bitmask, + &result); +} - /* Setup hotkeys */ - sony_call_snc_handle(0x0100, 0, &result); - sony_call_snc_handle(0x0101, 0, &result); - sony_call_snc_handle(0x0102, 0x100, &result); - sony_call_snc_handle(0x0127, 0, &result); +static void sony_nc_function_cleanup(struct platform_device *pd) +{ + unsigned int i, result, bitmask, handle; - return 0; + /* get enabled events and disable them */ + sony_nc_int_call(sony_nc_acpi_handle, "SN01", NULL, &bitmask); + sony_nc_int_call(sony_nc_acpi_handle, "SN03", &bitmask, &result); + + /* cleanup handles here */ + for (i = 0; i < ARRAY_SIZE(handles->cap); i++) { + + handle = handles->cap[i]; + + if (!handle) + continue; + + switch (handle) { + case 0x0105: + case 0x0148: + sony_nc_touchpad_cleanup(pd); + break; + case 0x0115: + case 0x0136: + case 0x013f: + sony_nc_battery_care_cleanup(pd); + break; + case 0x0119: + case 0x015D: + sony_nc_lid_resume_cleanup(pd); + break; + case 0x0122: + sony_nc_thermal_cleanup(pd); + break; + case 0x0128: + case 0x0146: + case 0x015B: + sony_nc_gfx_switch_cleanup(pd); + break; + case 0x0131: + sony_nc_highspeed_charging_cleanup(pd); + break; + case 0x0124: + case 0x0135: + sony_nc_rfkill_cleanup(); + break; + case 0x0137: + case 0x0143: + case 0x014b: + case 0x014c: + case 0x0163: + sony_nc_kbd_backlight_cleanup(pd, handle); + break; + case 0x0121: + sony_nc_lowbatt_cleanup(pd); + break; + case 0x0149: + sony_nc_fanspeed_cleanup(pd); + break; + case 0x0155: + sony_nc_usb_charge_cleanup(pd); + break; + case 0x011D: + sony_nc_panelid_cleanup(pd); + break; + case 0x0168: + sony_nc_smart_conn_cleanup(pd); + break; + default: + continue; + } + } + + /* finally cleanup the handles list */ + sony_nc_handles_cleanup(pd); } -static int sony_nc_resume(struct acpi_device *device) +#ifdef CONFIG_PM_SLEEP +static void sony_nc_function_resume(void) +{ + unsigned int i, result, bitmask, arg; + + dprintk("Resuming SNC device\n"); + + for (i = 0; i < ARRAY_SIZE(handles->cap); i++) { + unsigned int handle = handles->cap[i]; + + if (!handle) + continue; + + switch (handle) { + case 0x0100: + case 0x0101: + case 0x0127: + /* re-enable hotkeys */ + sony_call_snc_handle(handle, 0, &result); + break; + case 0x0102: + /* re-enable hotkeys */ + sony_call_snc_handle(handle, 0x100, &result); + break; + case 0x0122: + sony_nc_thermal_resume(); + break; + case 0x0124: + case 0x0135: + sony_nc_rfkill_update(); + break; + default: + continue; + } + } + + /* Enable all events */ + arg = 0x10; + if (!sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg, &bitmask)) + sony_nc_int_call(sony_nc_acpi_handle, "SN02", &bitmask, + &result); +} + +static int sony_nc_resume(struct device *dev) { struct sony_nc_value *item; - acpi_handle handle; for (item = sony_nc_values; item->name; item++) { int ret; if (!item->valid) continue; - ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, - item->value, NULL); + ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiset, + &item->value, NULL); if (ret < 0) { - printk("%s: %d\n", __func__, ret); + pr_err("%s: %d\n", __func__, ret); break; } } - if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "ECON", - &handle))) { - if (acpi_callsetfunc(sony_nc_acpi_handle, "ECON", 1, NULL)) + if (acpi_has_method(sony_nc_acpi_handle, "ECON")) { + int arg = 1; + if (sony_nc_int_call(sony_nc_acpi_handle, "ECON", &arg, NULL)) dprintk("ECON Method failed\n"); } - if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00", - &handle))) { - dprintk("Doing SNC setup\n"); - sony_nc_function_setup(device); - } - - /* set the last requested brightness level */ - if (sony_backlight_device && - sony_backlight_update_status(sony_backlight_device) < 0) - printk(KERN_WARNING DRV_PFX "unable to restore brightness level\n"); - - /* re-read rfkill state */ - sony_nc_rfkill_update(); + if (acpi_has_method(sony_nc_acpi_handle, "SN00")) + sony_nc_function_resume(); return 0; } +#endif + +static SIMPLE_DEV_PM_OPS(sony_nc_pm, NULL, sony_nc_resume); static void sony_nc_rfkill_cleanup(void) { @@ -1070,7 +1609,7 @@ static int sony_nc_rfkill_set(void *data, bool blocked) int argument = sony_rfkill_address[(long) data] + 0x100; if (!blocked) - argument |= 0xff0000; + argument |= 0x070000; return sony_call_snc_handle(sony_rfkill_handle, argument, &result); } @@ -1087,7 +1626,7 @@ static int sony_nc_setup_rfkill(struct acpi_device *device, enum rfkill_type type; const char *name; int result; - bool hwblock; + bool hwblock, swblock; switch (nc_type) { case SONY_WIFI: @@ -1115,8 +1654,21 @@ static int sony_nc_setup_rfkill(struct acpi_device *device, if (!rfk) return -ENOMEM; - sony_call_snc_handle(sony_rfkill_handle, 0x200, &result); + if (sony_call_snc_handle(sony_rfkill_handle, 0x200, &result) < 0) { + rfkill_destroy(rfk); + return -1; + } hwblock = !(result & 0x1); + + if (sony_call_snc_handle(sony_rfkill_handle, + sony_rfkill_address[nc_type], + &result) < 0) { + rfkill_destroy(rfk); + return -1; + } + swblock = !(result & 0x2); + + rfkill_init_sw_state(rfk, swblock); rfkill_set_hw_state(rfk, hwblock); err = rfkill_register(rfk); @@ -1128,7 +1680,7 @@ static int sony_nc_setup_rfkill(struct acpi_device *device, return err; } -static void sony_nc_rfkill_update() +static void sony_nc_rfkill_update(void) { enum sony_nc_rfkill i; int result; @@ -1152,89 +1704,1453 @@ static void sony_nc_rfkill_update() sony_call_snc_handle(sony_rfkill_handle, argument, &result); rfkill_set_states(sony_rfkill_devices[i], - !(result & 0xf), false); + !(result & 0x2), false); } } -static void sony_nc_rfkill_setup(struct acpi_device *device) +static int sony_nc_rfkill_setup(struct acpi_device *device, + unsigned int handle) { - int offset; - u8 dev_code, i; - acpi_status status; - struct acpi_object_list params; - union acpi_object in_obj; - union acpi_object *device_enum; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - - offset = sony_find_snc_handle(0x124); - if (offset == -1) { - offset = sony_find_snc_handle(0x135); - if (offset == -1) - return; - else - sony_rfkill_handle = 0x135; - } else - sony_rfkill_handle = 0x124; - dprintk("Found rkfill handle: 0x%.4x\n", sony_rfkill_handle); - - /* need to read the whole buffer returned by the acpi call to SN06 - * here otherwise we may miss some features + u64 offset; + int i; + unsigned char buffer[32] = { 0 }; + + offset = sony_find_snc_handle(handle); + sony_rfkill_handle = handle; + + i = sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &offset, buffer, + 32); + if (i < 0) + return i; + + /* The buffer is filled with magic numbers describing the devices + * available, 0xff terminates the enumeration. + * Known codes: + * 0x00 WLAN + * 0x10 BLUETOOTH + * 0x20 WWAN GPRS-EDGE + * 0x21 WWAN HSDPA + * 0x22 WWAN EV-DO + * 0x23 WWAN GPS + * 0x25 Gobi WWAN no GPS + * 0x26 Gobi WWAN + GPS + * 0x28 Gobi WWAN no GPS + * 0x29 Gobi WWAN + GPS + * 0x30 WIMAX + * 0x50 Gobi WWAN no GPS + * 0x51 Gobi WWAN + GPS + * 0x70 no SIM card slot + * 0x71 SIM card slot */ - params.count = 1; - params.pointer = &in_obj; - in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = offset; - status = acpi_evaluate_object(sony_nc_acpi_handle, "SN06", ¶ms, - &buffer); - if (ACPI_FAILURE(status)) { - dprintk("Radio device enumeration failed\n"); - return; - } + for (i = 0; i < ARRAY_SIZE(buffer); i++) { - device_enum = (union acpi_object *) buffer.pointer; - if (!device_enum || device_enum->type != ACPI_TYPE_BUFFER) { - printk(KERN_ERR "Invalid SN06 return object 0x%.2x\n", - device_enum->type); - goto out_no_enum; - } + if (buffer[i] == 0xff) + break; - /* the buffer is filled with magic numbers describing the devices - * available, 0xff terminates the enumeration - */ - while ((dev_code = *(device_enum->buffer.pointer + i)) != 0xff && - i < device_enum->buffer.length) { - i++; - dprintk("Radio devices, looking at 0x%.2x\n", dev_code); + dprintk("Radio devices, found 0x%.2x\n", buffer[i]); - if (dev_code == 0 && !sony_rfkill_devices[SONY_WIFI]) + if (buffer[i] == 0 && !sony_rfkill_devices[SONY_WIFI]) sony_nc_setup_rfkill(device, SONY_WIFI); - if (dev_code == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH]) + if (buffer[i] == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH]) sony_nc_setup_rfkill(device, SONY_BLUETOOTH); - if ((0xf0 & dev_code) == 0x20 && + if (((0xf0 & buffer[i]) == 0x20 || + (0xf0 & buffer[i]) == 0x50) && !sony_rfkill_devices[SONY_WWAN]) sony_nc_setup_rfkill(device, SONY_WWAN); - if (dev_code == 0x30 && !sony_rfkill_devices[SONY_WIMAX]) + if (buffer[i] == 0x30 && !sony_rfkill_devices[SONY_WIMAX]) sony_nc_setup_rfkill(device, SONY_WIMAX); } + return 0; +} + +/* Keyboard backlight feature */ +struct kbd_backlight { + unsigned int handle; + unsigned int base; + unsigned int mode; + unsigned int timeout; + struct device_attribute mode_attr; + struct device_attribute timeout_attr; +}; + +static struct kbd_backlight *kbdbl_ctl; + +static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value) +{ + int result; + + if (value > 2) + return -EINVAL; + + if (sony_call_snc_handle(kbdbl_ctl->handle, + (value << 0x10) | (kbdbl_ctl->base), &result)) + return -EIO; + + /* Try to turn the light on/off immediately */ + if (value != 1) + sony_call_snc_handle(kbdbl_ctl->handle, + (value << 0x0f) | (kbdbl_ctl->base + 0x100), + &result); + + kbdbl_ctl->mode = value; + + return 0; +} + +static ssize_t sony_nc_kbd_backlight_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + int ret = 0; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value)) + return -EINVAL; + + ret = __sony_nc_kbd_backlight_mode_set(value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t sony_nc_kbd_backlight_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + ssize_t count = 0; + count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_ctl->mode); + return count; +} + +static int __sony_nc_kbd_backlight_timeout_set(u8 value) +{ + int result; + + if (value > 3) + return -EINVAL; + + if (sony_call_snc_handle(kbdbl_ctl->handle, (value << 0x10) | + (kbdbl_ctl->base + 0x200), &result)) + return -EIO; + + kbdbl_ctl->timeout = value; + + return 0; +} + +static ssize_t sony_nc_kbd_backlight_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + int ret = 0; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value)) + return -EINVAL; + + ret = __sony_nc_kbd_backlight_timeout_set(value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t sony_nc_kbd_backlight_timeout_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + ssize_t count = 0; + count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_ctl->timeout); + return count; +} + +static int sony_nc_kbd_backlight_setup(struct platform_device *pd, + unsigned int handle) +{ + int result; + int ret = 0; + + if (kbdbl_ctl) { + pr_warn("handle 0x%.4x: keyboard backlight setup already done for 0x%.4x\n", + handle, kbdbl_ctl->handle); + return -EBUSY; + } + + /* verify the kbd backlight presence, these handles are not used for + * keyboard backlight only + */ + ret = sony_call_snc_handle(handle, handle == 0x0137 ? 0x0B00 : 0x0100, + &result); + if (ret) + return ret; + + if ((handle == 0x0137 && !(result & 0x02)) || + !(result & 0x01)) { + dprintk("no backlight keyboard found\n"); + return 0; + } + + kbdbl_ctl = kzalloc(sizeof(*kbdbl_ctl), GFP_KERNEL); + if (!kbdbl_ctl) + return -ENOMEM; + + kbdbl_ctl->mode = kbd_backlight; + kbdbl_ctl->timeout = kbd_backlight_timeout; + kbdbl_ctl->handle = handle; + if (handle == 0x0137) + kbdbl_ctl->base = 0x0C00; + else + kbdbl_ctl->base = 0x4000; + + sysfs_attr_init(&kbdbl_ctl->mode_attr.attr); + kbdbl_ctl->mode_attr.attr.name = "kbd_backlight"; + kbdbl_ctl->mode_attr.attr.mode = S_IRUGO | S_IWUSR; + kbdbl_ctl->mode_attr.show = sony_nc_kbd_backlight_mode_show; + kbdbl_ctl->mode_attr.store = sony_nc_kbd_backlight_mode_store; + + sysfs_attr_init(&kbdbl_ctl->timeout_attr.attr); + kbdbl_ctl->timeout_attr.attr.name = "kbd_backlight_timeout"; + kbdbl_ctl->timeout_attr.attr.mode = S_IRUGO | S_IWUSR; + kbdbl_ctl->timeout_attr.show = sony_nc_kbd_backlight_timeout_show; + kbdbl_ctl->timeout_attr.store = sony_nc_kbd_backlight_timeout_store; + + ret = device_create_file(&pd->dev, &kbdbl_ctl->mode_attr); + if (ret) + goto outkzalloc; + + ret = device_create_file(&pd->dev, &kbdbl_ctl->timeout_attr); + if (ret) + goto outmode; + + __sony_nc_kbd_backlight_mode_set(kbdbl_ctl->mode); + __sony_nc_kbd_backlight_timeout_set(kbdbl_ctl->timeout); + + return 0; + +outmode: + device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr); +outkzalloc: + kfree(kbdbl_ctl); + kbdbl_ctl = NULL; + return ret; +} + +static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd, + unsigned int handle) +{ + if (kbdbl_ctl && handle == kbdbl_ctl->handle) { + device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr); + device_remove_file(&pd->dev, &kbdbl_ctl->timeout_attr); + kfree(kbdbl_ctl); + kbdbl_ctl = NULL; + } +} + +struct battery_care_control { + struct device_attribute attrs[2]; + unsigned int handle; +}; +static struct battery_care_control *bcare_ctl; + +static ssize_t sony_nc_battery_care_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result, cmd; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value)) + return -EINVAL; + + /* limit values (2 bits): + * 00 - none + * 01 - 80% + * 10 - 50% + * 11 - 100% + * + * bit 0: 0 disable BCL, 1 enable BCL + * bit 1: 1 tell to store the battery limit (see bits 6,7) too + * bits 2,3: reserved + * bits 4,5: store the limit into the EC + * bits 6,7: store the limit into the battery + */ + cmd = 0; + + if (value > 0) { + if (value <= 50) + cmd = 0x20; + + else if (value <= 80) + cmd = 0x10; + + else if (value <= 100) + cmd = 0x30; + + else + return -EINVAL; + + /* + * handle 0x0115 should allow storing on battery too; + * handle 0x0136 same as 0x0115 + health status; + * handle 0x013f, same as 0x0136 but no storing on the battery + */ + if (bcare_ctl->handle != 0x013f) + cmd = cmd | (cmd << 2); + + cmd = (cmd | 0x1) << 0x10; + } + + if (sony_call_snc_handle(bcare_ctl->handle, cmd | 0x0100, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_battery_care_limit_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result, status; + + if (sony_call_snc_handle(bcare_ctl->handle, 0x0000, &result)) + return -EIO; + + status = (result & 0x01) ? ((result & 0x30) >> 0x04) : 0; + switch (status) { + case 1: + status = 80; + break; + case 2: + status = 50; + break; + case 3: + status = 100; + break; + default: + status = 0; + break; + } + + return snprintf(buffer, PAGE_SIZE, "%d\n", status); +} + +static ssize_t sony_nc_battery_care_health_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + ssize_t count = 0; + unsigned int health; + + if (sony_call_snc_handle(bcare_ctl->handle, 0x0200, &health)) + return -EIO; + + count = snprintf(buffer, PAGE_SIZE, "%d\n", health & 0xff); + + return count; +} + +static int sony_nc_battery_care_setup(struct platform_device *pd, + unsigned int handle) +{ + int ret = 0; + + bcare_ctl = kzalloc(sizeof(struct battery_care_control), GFP_KERNEL); + if (!bcare_ctl) + return -ENOMEM; + + bcare_ctl->handle = handle; + + sysfs_attr_init(&bcare_ctl->attrs[0].attr); + bcare_ctl->attrs[0].attr.name = "battery_care_limiter"; + bcare_ctl->attrs[0].attr.mode = S_IRUGO | S_IWUSR; + bcare_ctl->attrs[0].show = sony_nc_battery_care_limit_show; + bcare_ctl->attrs[0].store = sony_nc_battery_care_limit_store; + + ret = device_create_file(&pd->dev, &bcare_ctl->attrs[0]); + if (ret) + goto outkzalloc; + + /* 0x0115 is for models with no health reporting capability */ + if (handle == 0x0115) + return 0; + + sysfs_attr_init(&bcare_ctl->attrs[1].attr); + bcare_ctl->attrs[1].attr.name = "battery_care_health"; + bcare_ctl->attrs[1].attr.mode = S_IRUGO; + bcare_ctl->attrs[1].show = sony_nc_battery_care_health_show; + + ret = device_create_file(&pd->dev, &bcare_ctl->attrs[1]); + if (ret) + goto outlimiter; + + return 0; + +outlimiter: + device_remove_file(&pd->dev, &bcare_ctl->attrs[0]); + +outkzalloc: + kfree(bcare_ctl); + bcare_ctl = NULL; + + return ret; +} + +static void sony_nc_battery_care_cleanup(struct platform_device *pd) +{ + if (bcare_ctl) { + device_remove_file(&pd->dev, &bcare_ctl->attrs[0]); + if (bcare_ctl->handle != 0x0115) + device_remove_file(&pd->dev, &bcare_ctl->attrs[1]); + + kfree(bcare_ctl); + bcare_ctl = NULL; + } +} + +struct snc_thermal_ctrl { + unsigned int mode; + unsigned int profiles; + struct device_attribute mode_attr; + struct device_attribute profiles_attr; +}; +static struct snc_thermal_ctrl *th_handle; + +#define THM_PROFILE_MAX 3 +static const char * const snc_thermal_profiles[] = { + "balanced", + "silent", + "performance" +}; + +static int sony_nc_thermal_mode_set(unsigned short mode) +{ + unsigned int result; + + /* the thermal profile seems to be a two bit bitmask: + * lsb -> silent + * msb -> performance + * no bit set is the normal operation and is always valid + * Some vaio models only have "balanced" and "performance" + */ + if ((mode && !(th_handle->profiles & mode)) || mode >= THM_PROFILE_MAX) + return -EINVAL; + + if (sony_call_snc_handle(0x0122, mode << 0x10 | 0x0200, &result)) + return -EIO; + + th_handle->mode = mode; + + return 0; +} + +static int sony_nc_thermal_mode_get(void) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0122, 0x0100, &result)) + return -EIO; + + return result & 0xff; +} + +static ssize_t sony_nc_thermal_profiles_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + short cnt; + size_t idx = 0; + + for (cnt = 0; cnt < THM_PROFILE_MAX; cnt++) { + if (!cnt || (th_handle->profiles & cnt)) + idx += snprintf(buffer + idx, PAGE_SIZE - idx, "%s ", + snc_thermal_profiles[cnt]); + } + idx += snprintf(buffer + idx, PAGE_SIZE - idx, "\n"); -out_no_enum: - kfree(buffer.pointer); - return; + return idx; +} + +static ssize_t sony_nc_thermal_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned short cmd; + size_t len = count; + + if (count == 0) + return -EINVAL; + + /* skip the newline if present */ + if (buffer[len - 1] == '\n') + len--; + + for (cmd = 0; cmd < THM_PROFILE_MAX; cmd++) + if (strncmp(buffer, snc_thermal_profiles[cmd], len) == 0) + break; + + if (sony_nc_thermal_mode_set(cmd)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_thermal_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + ssize_t count = 0; + int mode = sony_nc_thermal_mode_get(); + + if (mode < 0) + return mode; + + count = snprintf(buffer, PAGE_SIZE, "%s\n", snc_thermal_profiles[mode]); + + return count; +} + +static int sony_nc_thermal_setup(struct platform_device *pd) +{ + int ret = 0; + th_handle = kzalloc(sizeof(struct snc_thermal_ctrl), GFP_KERNEL); + if (!th_handle) + return -ENOMEM; + + ret = sony_call_snc_handle(0x0122, 0x0000, &th_handle->profiles); + if (ret) { + pr_warn("couldn't to read the thermal profiles\n"); + goto outkzalloc; + } + + ret = sony_nc_thermal_mode_get(); + if (ret < 0) { + pr_warn("couldn't to read the current thermal profile"); + goto outkzalloc; + } + th_handle->mode = ret; + + sysfs_attr_init(&th_handle->profiles_attr.attr); + th_handle->profiles_attr.attr.name = "thermal_profiles"; + th_handle->profiles_attr.attr.mode = S_IRUGO; + th_handle->profiles_attr.show = sony_nc_thermal_profiles_show; + + sysfs_attr_init(&th_handle->mode_attr.attr); + th_handle->mode_attr.attr.name = "thermal_control"; + th_handle->mode_attr.attr.mode = S_IRUGO | S_IWUSR; + th_handle->mode_attr.show = sony_nc_thermal_mode_show; + th_handle->mode_attr.store = sony_nc_thermal_mode_store; + + ret = device_create_file(&pd->dev, &th_handle->profiles_attr); + if (ret) + goto outkzalloc; + + ret = device_create_file(&pd->dev, &th_handle->mode_attr); + if (ret) + goto outprofiles; + + return 0; + +outprofiles: + device_remove_file(&pd->dev, &th_handle->profiles_attr); +outkzalloc: + kfree(th_handle); + th_handle = NULL; + return ret; +} + +static void sony_nc_thermal_cleanup(struct platform_device *pd) +{ + if (th_handle) { + device_remove_file(&pd->dev, &th_handle->profiles_attr); + device_remove_file(&pd->dev, &th_handle->mode_attr); + kfree(th_handle); + th_handle = NULL; + } +} + +#ifdef CONFIG_PM_SLEEP +static void sony_nc_thermal_resume(void) +{ + unsigned int status = sony_nc_thermal_mode_get(); + + if (status != th_handle->mode) + sony_nc_thermal_mode_set(th_handle->mode); +} +#endif + +/* resume on LID open */ +#define LID_RESUME_S5 0 +#define LID_RESUME_S4 1 +#define LID_RESUME_S3 2 +#define LID_RESUME_MAX 3 +struct snc_lid_resume_control { + struct device_attribute attrs[LID_RESUME_MAX]; + unsigned int status; + int handle; +}; +static struct snc_lid_resume_control *lid_ctl; + +static ssize_t sony_nc_lid_resume_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + unsigned int pos = LID_RESUME_S5; + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + /* the value we have to write to SNC is a bitmask: + * +--------------+ + * | S3 | S4 | S5 | + * +--------------+ + * 2 1 0 + */ + while (pos < LID_RESUME_MAX) { + if (&lid_ctl->attrs[pos].attr == &attr->attr) + break; + pos++; + } + if (pos == LID_RESUME_MAX) + return -EINVAL; + + if (value) + value = lid_ctl->status | (1 << pos); + else + value = lid_ctl->status & ~(1 << pos); + + if (sony_call_snc_handle(lid_ctl->handle, value << 0x10 | 0x0100, + &result)) + return -EIO; + + lid_ctl->status = value; + + return count; +} + +static ssize_t sony_nc_lid_resume_show(struct device *dev, + struct device_attribute *attr, + char *buffer) +{ + unsigned int pos = LID_RESUME_S5; + + while (pos < LID_RESUME_MAX) { + if (&lid_ctl->attrs[pos].attr == &attr->attr) + return snprintf(buffer, PAGE_SIZE, "%d\n", + (lid_ctl->status >> pos) & 0x01); + pos++; + } + return -EINVAL; +} + +static int sony_nc_lid_resume_setup(struct platform_device *pd, + unsigned int handle) +{ + unsigned int result; + int i; + + if (sony_call_snc_handle(handle, 0x0000, &result)) + return -EIO; + + lid_ctl = kzalloc(sizeof(struct snc_lid_resume_control), GFP_KERNEL); + if (!lid_ctl) + return -ENOMEM; + + lid_ctl->status = result & 0x7; + lid_ctl->handle = handle; + + sysfs_attr_init(&lid_ctl->attrs[0].attr); + lid_ctl->attrs[LID_RESUME_S5].attr.name = "lid_resume_S5"; + lid_ctl->attrs[LID_RESUME_S5].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S5].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S5].store = sony_nc_lid_resume_store; + + if (handle == 0x0119) { + sysfs_attr_init(&lid_ctl->attrs[1].attr); + lid_ctl->attrs[LID_RESUME_S4].attr.name = "lid_resume_S4"; + lid_ctl->attrs[LID_RESUME_S4].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S4].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S4].store = sony_nc_lid_resume_store; + + sysfs_attr_init(&lid_ctl->attrs[2].attr); + lid_ctl->attrs[LID_RESUME_S3].attr.name = "lid_resume_S3"; + lid_ctl->attrs[LID_RESUME_S3].attr.mode = S_IRUGO | S_IWUSR; + lid_ctl->attrs[LID_RESUME_S3].show = sony_nc_lid_resume_show; + lid_ctl->attrs[LID_RESUME_S3].store = sony_nc_lid_resume_store; + } + for (i = 0; i < LID_RESUME_MAX && + lid_ctl->attrs[LID_RESUME_S3].attr.name; i++) { + result = device_create_file(&pd->dev, &lid_ctl->attrs[i]); + if (result) + goto liderror; + } + + return 0; + +liderror: + for (i--; i >= 0; i--) + device_remove_file(&pd->dev, &lid_ctl->attrs[i]); + + kfree(lid_ctl); + lid_ctl = NULL; + + return result; +} + +static void sony_nc_lid_resume_cleanup(struct platform_device *pd) +{ + int i; + + if (lid_ctl) { + for (i = 0; i < LID_RESUME_MAX; i++) { + if (!lid_ctl->attrs[i].attr.name) + break; + + device_remove_file(&pd->dev, &lid_ctl->attrs[i]); + } + + kfree(lid_ctl); + lid_ctl = NULL; + } +} + +/* GFX Switch position */ +enum gfx_switch { + SPEED, + STAMINA, + AUTO +}; +struct snc_gfx_switch_control { + struct device_attribute attr; + unsigned int handle; +}; +static struct snc_gfx_switch_control *gfxs_ctl; + +/* returns 0 for speed, 1 for stamina */ +static int __sony_nc_gfx_switch_status_get(void) +{ + unsigned int result; + + if (sony_call_snc_handle(gfxs_ctl->handle, + gfxs_ctl->handle == 0x015B ? 0x0000 : 0x0100, + &result)) + return -EIO; + + switch (gfxs_ctl->handle) { + case 0x0146: + /* 1: discrete GFX (speed) + * 0: integrated GFX (stamina) + */ + return result & 0x1 ? SPEED : STAMINA; + break; + case 0x015B: + /* 0: discrete GFX (speed) + * 1: integrated GFX (stamina) + */ + return result & 0x1 ? STAMINA : SPEED; + break; + case 0x0128: + /* it's a more elaborated bitmask, for now: + * 2: integrated GFX (stamina) + * 0: discrete GFX (speed) + */ + dprintk("GFX Status: 0x%x\n", result); + return result & 0x80 ? AUTO : + result & 0x02 ? STAMINA : SPEED; + break; + } + return -EINVAL; +} + +static ssize_t sony_nc_gfx_switch_status_show(struct device *dev, + struct device_attribute *attr, + char *buffer) +{ + int pos = __sony_nc_gfx_switch_status_get(); + + if (pos < 0) + return pos; + + return snprintf(buffer, PAGE_SIZE, "%s\n", + pos == SPEED ? "speed" : + pos == STAMINA ? "stamina" : + pos == AUTO ? "auto" : "unknown"); +} + +static int sony_nc_gfx_switch_setup(struct platform_device *pd, + unsigned int handle) +{ + unsigned int result; + + gfxs_ctl = kzalloc(sizeof(struct snc_gfx_switch_control), GFP_KERNEL); + if (!gfxs_ctl) + return -ENOMEM; + + gfxs_ctl->handle = handle; + + sysfs_attr_init(&gfxs_ctl->attr.attr); + gfxs_ctl->attr.attr.name = "gfx_switch_status"; + gfxs_ctl->attr.attr.mode = S_IRUGO; + gfxs_ctl->attr.show = sony_nc_gfx_switch_status_show; + + result = device_create_file(&pd->dev, &gfxs_ctl->attr); + if (result) + goto gfxerror; + + return 0; + +gfxerror: + kfree(gfxs_ctl); + gfxs_ctl = NULL; + + return result; +} + +static void sony_nc_gfx_switch_cleanup(struct platform_device *pd) +{ + if (gfxs_ctl) { + device_remove_file(&pd->dev, &gfxs_ctl->attr); + + kfree(gfxs_ctl); + gfxs_ctl = NULL; + } +} + +/* High speed charging function */ +static struct device_attribute *hsc_handle; + +static ssize_t sony_nc_highspeed_charging_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0131, value << 0x10 | 0x0200, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_highspeed_charging_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0131, 0x0100, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01); +} + +static int sony_nc_highspeed_charging_setup(struct platform_device *pd) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0131, 0x0000, &result) || !(result & 0x01)) { + /* some models advertise the handle but have no implementation + * for it + */ + pr_info("No High Speed Charging capability found\n"); + return 0; + } + + hsc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!hsc_handle) + return -ENOMEM; + + sysfs_attr_init(&hsc_handle->attr); + hsc_handle->attr.name = "battery_highspeed_charging"; + hsc_handle->attr.mode = S_IRUGO | S_IWUSR; + hsc_handle->show = sony_nc_highspeed_charging_show; + hsc_handle->store = sony_nc_highspeed_charging_store; + + result = device_create_file(&pd->dev, hsc_handle); + if (result) { + kfree(hsc_handle); + hsc_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd) +{ + if (hsc_handle) { + device_remove_file(&pd->dev, hsc_handle); + kfree(hsc_handle); + hsc_handle = NULL; + } +} + +/* low battery function */ +static struct device_attribute *lowbatt_handle; + +static ssize_t sony_nc_lowbatt_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0121, value << 8, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_lowbatt_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0121, 0x0200, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 1); +} + +static int sony_nc_lowbatt_setup(struct platform_device *pd) +{ + unsigned int result; + + lowbatt_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!lowbatt_handle) + return -ENOMEM; + + sysfs_attr_init(&lowbatt_handle->attr); + lowbatt_handle->attr.name = "lowbatt_hibernate"; + lowbatt_handle->attr.mode = S_IRUGO | S_IWUSR; + lowbatt_handle->show = sony_nc_lowbatt_show; + lowbatt_handle->store = sony_nc_lowbatt_store; + + result = device_create_file(&pd->dev, lowbatt_handle); + if (result) { + kfree(lowbatt_handle); + lowbatt_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_lowbatt_cleanup(struct platform_device *pd) +{ + if (lowbatt_handle) { + device_remove_file(&pd->dev, lowbatt_handle); + kfree(lowbatt_handle); + lowbatt_handle = NULL; + } +} + +/* fan speed function */ +static struct device_attribute *fan_handle, *hsf_handle; + +static ssize_t sony_nc_hsfan_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0149, value << 0x10 | 0x0200, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_hsfan_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0149, 0x0100, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01); +} + +static ssize_t sony_nc_fanspeed_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0149, 0x0300, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0xff); +} + +static int sony_nc_fanspeed_setup(struct platform_device *pd) +{ + unsigned int result; + + fan_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!fan_handle) + return -ENOMEM; + + hsf_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!hsf_handle) { + result = -ENOMEM; + goto out_hsf_handle_alloc; + } + + sysfs_attr_init(&fan_handle->attr); + fan_handle->attr.name = "fanspeed"; + fan_handle->attr.mode = S_IRUGO; + fan_handle->show = sony_nc_fanspeed_show; + fan_handle->store = NULL; + + sysfs_attr_init(&hsf_handle->attr); + hsf_handle->attr.name = "fan_forced"; + hsf_handle->attr.mode = S_IRUGO | S_IWUSR; + hsf_handle->show = sony_nc_hsfan_show; + hsf_handle->store = sony_nc_hsfan_store; + + result = device_create_file(&pd->dev, fan_handle); + if (result) + goto out_fan_handle; + + result = device_create_file(&pd->dev, hsf_handle); + if (result) + goto out_hsf_handle; + + return 0; + +out_hsf_handle: + device_remove_file(&pd->dev, fan_handle); + +out_fan_handle: + kfree(hsf_handle); + hsf_handle = NULL; + +out_hsf_handle_alloc: + kfree(fan_handle); + fan_handle = NULL; + return result; +} + +static void sony_nc_fanspeed_cleanup(struct platform_device *pd) +{ + if (fan_handle) { + device_remove_file(&pd->dev, fan_handle); + kfree(fan_handle); + fan_handle = NULL; + } + if (hsf_handle) { + device_remove_file(&pd->dev, hsf_handle); + kfree(hsf_handle); + hsf_handle = NULL; + } +} + +/* USB charge function */ +static struct device_attribute *uc_handle; + +static ssize_t sony_nc_usb_charge_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0155, value << 0x10 | 0x0100, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_usb_charge_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0155, 0x0000, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01); +} + +static int sony_nc_usb_charge_setup(struct platform_device *pd) +{ + unsigned int result; + + if (sony_call_snc_handle(0x0155, 0x0000, &result) || !(result & 0x01)) { + /* some models advertise the handle but have no implementation + * for it + */ + pr_info("No USB Charge capability found\n"); + return 0; + } + + uc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!uc_handle) + return -ENOMEM; + + sysfs_attr_init(&uc_handle->attr); + uc_handle->attr.name = "usb_charge"; + uc_handle->attr.mode = S_IRUGO | S_IWUSR; + uc_handle->show = sony_nc_usb_charge_show; + uc_handle->store = sony_nc_usb_charge_store; + + result = device_create_file(&pd->dev, uc_handle); + if (result) { + kfree(uc_handle); + uc_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_usb_charge_cleanup(struct platform_device *pd) +{ + if (uc_handle) { + device_remove_file(&pd->dev, uc_handle); + kfree(uc_handle); + uc_handle = NULL; + } +} + +/* Panel ID function */ +static struct device_attribute *panel_handle; + +static ssize_t sony_nc_panelid_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(0x011D, 0x0000, &result)) + return -EIO; + + return snprintf(buffer, PAGE_SIZE, "%d\n", result); +} + +static int sony_nc_panelid_setup(struct platform_device *pd) +{ + unsigned int result; + + panel_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!panel_handle) + return -ENOMEM; + + sysfs_attr_init(&panel_handle->attr); + panel_handle->attr.name = "panel_id"; + panel_handle->attr.mode = S_IRUGO; + panel_handle->show = sony_nc_panelid_show; + panel_handle->store = NULL; + + result = device_create_file(&pd->dev, panel_handle); + if (result) { + kfree(panel_handle); + panel_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_panelid_cleanup(struct platform_device *pd) +{ + if (panel_handle) { + device_remove_file(&pd->dev, panel_handle); + kfree(panel_handle); + panel_handle = NULL; + } +} + +/* smart connect function */ +static struct device_attribute *sc_handle; + +static ssize_t sony_nc_smart_conn_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + if (sony_call_snc_handle(0x0168, value << 0x10, &result)) + return -EIO; + + return count; +} + +static int sony_nc_smart_conn_setup(struct platform_device *pd) +{ + unsigned int result; + + sc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL); + if (!sc_handle) + return -ENOMEM; + + sysfs_attr_init(&sc_handle->attr); + sc_handle->attr.name = "smart_connect"; + sc_handle->attr.mode = S_IWUSR; + sc_handle->show = NULL; + sc_handle->store = sony_nc_smart_conn_store; + + result = device_create_file(&pd->dev, sc_handle); + if (result) { + kfree(sc_handle); + sc_handle = NULL; + return result; + } + + return 0; +} + +static void sony_nc_smart_conn_cleanup(struct platform_device *pd) +{ + if (sc_handle) { + device_remove_file(&pd->dev, sc_handle); + kfree(sc_handle); + sc_handle = NULL; + } +} + +/* Touchpad enable/disable */ +struct touchpad_control { + struct device_attribute attr; + int handle; +}; +static struct touchpad_control *tp_ctl; + +static ssize_t sony_nc_touchpad_store(struct device *dev, + struct device_attribute *attr, const char *buffer, size_t count) +{ + unsigned int result; + unsigned long value; + + if (count > 31) + return -EINVAL; + + if (kstrtoul(buffer, 10, &value) || value > 1) + return -EINVAL; + + /* sysfs: 0 disabled, 1 enabled + * EC: 0 enabled, 1 disabled + */ + if (sony_call_snc_handle(tp_ctl->handle, + (!value << 0x10) | 0x100, &result)) + return -EIO; + + return count; +} + +static ssize_t sony_nc_touchpad_show(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + unsigned int result; + + if (sony_call_snc_handle(tp_ctl->handle, 0x000, &result)) + return -EINVAL; + + return snprintf(buffer, PAGE_SIZE, "%d\n", !(result & 0x01)); +} + +static int sony_nc_touchpad_setup(struct platform_device *pd, + unsigned int handle) +{ + int ret = 0; + + tp_ctl = kzalloc(sizeof(struct touchpad_control), GFP_KERNEL); + if (!tp_ctl) + return -ENOMEM; + + tp_ctl->handle = handle; + + sysfs_attr_init(&tp_ctl->attr.attr); + tp_ctl->attr.attr.name = "touchpad"; + tp_ctl->attr.attr.mode = S_IRUGO | S_IWUSR; + tp_ctl->attr.show = sony_nc_touchpad_show; + tp_ctl->attr.store = sony_nc_touchpad_store; + + ret = device_create_file(&pd->dev, &tp_ctl->attr); + if (ret) { + kfree(tp_ctl); + tp_ctl = NULL; + } + + return ret; +} + +static void sony_nc_touchpad_cleanup(struct platform_device *pd) +{ + if (tp_ctl) { + device_remove_file(&pd->dev, &tp_ctl->attr); + kfree(tp_ctl); + tp_ctl = NULL; + } +} + +static void sony_nc_backlight_ng_read_limits(int handle, + struct sony_backlight_props *props) +{ + u64 offset; + int i; + int lvl_table_len = 0; + u8 min = 0xff, max = 0x00; + unsigned char buffer[32] = { 0 }; + + props->handle = handle; + props->offset = 0; + props->maxlvl = 0xff; + + offset = sony_find_snc_handle(handle); + + /* try to read the boundaries from ACPI tables, if we fail the above + * defaults should be reasonable + */ + i = sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &offset, buffer, + 32); + if (i < 0) + return; + + switch (handle) { + case 0x012f: + case 0x0137: + lvl_table_len = 9; + break; + case 0x143: + case 0x14b: + case 0x14c: + lvl_table_len = 16; + break; + } + + /* the buffer lists brightness levels available, brightness levels are + * from position 0 to 8 in the array, other values are used by ALS + * control. + */ + for (i = 0; i < lvl_table_len && i < ARRAY_SIZE(buffer); i++) { + + dprintk("Brightness level: %d\n", buffer[i]); + + if (!buffer[i]) + break; + + if (buffer[i] > max) + max = buffer[i]; + if (buffer[i] < min) + min = buffer[i]; + } + props->offset = min; + props->maxlvl = max; + dprintk("Brightness levels: min=%d max=%d\n", props->offset, + props->maxlvl); +} + +static void sony_nc_backlight_setup(void) +{ + int max_brightness = 0; + const struct backlight_ops *ops = NULL; + struct backlight_properties props; + + if (sony_find_snc_handle(0x12f) >= 0) { + ops = &sony_backlight_ng_ops; + sony_bl_props.cmd_base = 0x0100; + sony_nc_backlight_ng_read_limits(0x12f, &sony_bl_props); + max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset; + + } else if (sony_find_snc_handle(0x137) >= 0) { + ops = &sony_backlight_ng_ops; + sony_bl_props.cmd_base = 0x0100; + sony_nc_backlight_ng_read_limits(0x137, &sony_bl_props); + max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset; + + } else if (sony_find_snc_handle(0x143) >= 0) { + ops = &sony_backlight_ng_ops; + sony_bl_props.cmd_base = 0x3000; + sony_nc_backlight_ng_read_limits(0x143, &sony_bl_props); + max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset; + + } else if (sony_find_snc_handle(0x14b) >= 0) { + ops = &sony_backlight_ng_ops; + sony_bl_props.cmd_base = 0x3000; + sony_nc_backlight_ng_read_limits(0x14b, &sony_bl_props); + max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset; + + } else if (sony_find_snc_handle(0x14c) >= 0) { + ops = &sony_backlight_ng_ops; + sony_bl_props.cmd_base = 0x3000; + sony_nc_backlight_ng_read_limits(0x14c, &sony_bl_props); + max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset; + + } else if (acpi_has_method(sony_nc_acpi_handle, "GBRT")) { + ops = &sony_backlight_ops; + max_brightness = SONY_MAX_BRIGHTNESS - 1; + + } else + return; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = max_brightness; + sony_bl_props.dev = backlight_device_register("sony", NULL, + &sony_bl_props, + ops, &props); + + if (IS_ERR(sony_bl_props.dev)) { + pr_warn("unable to register backlight device\n"); + sony_bl_props.dev = NULL; + } else + sony_bl_props.dev->props.brightness = + ops->get_brightness(sony_bl_props.dev); +} + +static void sony_nc_backlight_cleanup(void) +{ + if (sony_bl_props.dev) + backlight_device_unregister(sony_bl_props.dev); } static int sony_nc_add(struct acpi_device *device) { acpi_status status; int result = 0; - acpi_handle handle; struct sony_nc_value *item; - printk(KERN_INFO DRV_PFX "%s v%s.\n", - SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); - sony_nc_acpi_device = device; strcpy(acpi_device_class(device), "sony/hotkey"); @@ -1249,63 +3165,48 @@ static int sony_nc_add(struct acpi_device *device) goto outwalk; } + result = sony_pf_add(); + if (result) + goto outpresent; + if (debug) { - status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_nc_acpi_handle, - 1, sony_walk_callback, NULL, NULL, NULL); + status = acpi_walk_namespace(ACPI_TYPE_METHOD, + sony_nc_acpi_handle, 1, sony_walk_callback, + NULL, NULL, NULL); if (ACPI_FAILURE(status)) { - printk(KERN_WARNING DRV_PFX "unable to walk acpi resources\n"); + pr_warn("unable to walk acpi resources\n"); result = -ENODEV; - goto outwalk; + goto outpresent; } } - if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "ECON", - &handle))) { - if (acpi_callsetfunc(sony_nc_acpi_handle, "ECON", 1, NULL)) + result = sony_laptop_setup_input(device); + if (result) { + pr_err("Unable to create input devices\n"); + goto outplatform; + } + + if (acpi_has_method(sony_nc_acpi_handle, "ECON")) { + int arg = 1; + if (sony_nc_int_call(sony_nc_acpi_handle, "ECON", &arg, NULL)) dprintk("ECON Method failed\n"); } - if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00", - &handle))) { + if (acpi_has_method(sony_nc_acpi_handle, "SN00")) { dprintk("Doing SNC setup\n"); - sony_nc_function_setup(device); - sony_nc_rfkill_setup(device); + /* retrieve the available handles */ + result = sony_nc_handles_setup(sony_pf_device); + if (!result) + sony_nc_function_setup(device, sony_pf_device); } /* setup input devices and helper fifo */ - result = sony_laptop_setup_input(device); - if (result) { - printk(KERN_ERR DRV_PFX - "Unable to create input devices.\n"); - goto outwalk; - } - if (acpi_video_backlight_support()) { - printk(KERN_INFO DRV_PFX "brightness ignored, must be " - "controlled by ACPI video driver\n"); - } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT", - &handle))) { - sony_backlight_device = backlight_device_register("sony", NULL, - NULL, - &sony_backlight_ops); - - if (IS_ERR(sony_backlight_device)) { - printk(KERN_WARNING DRV_PFX "unable to register backlight device\n"); - sony_backlight_device = NULL; - } else { - sony_backlight_device->props.brightness = - sony_backlight_get_brightness - (sony_backlight_device); - sony_backlight_device->props.max_brightness = - SONY_MAX_BRIGHTNESS - 1; - } - + pr_info("brightness ignored, must be controlled by ACPI video driver\n"); + } else { + sony_nc_backlight_setup(); } - result = sony_pf_add(); - if (result) - goto outbacklight; - /* create sony_pf sysfs attributes related to the SNC device */ for (item = sony_nc_values; item->name; ++item) { @@ -1314,9 +3215,8 @@ static int sony_nc_add(struct acpi_device *device) /* find the available acpiget as described in the DSDT */ for (; item->acpiget && *item->acpiget; ++item->acpiget) { - if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, - *item->acpiget, - &handle))) { + if (acpi_has_method(sony_nc_acpi_handle, + *item->acpiget)) { dprintk("Found %s getter: %s\n", item->name, *item->acpiget); item->devattr.attr.mode |= S_IRUGO; @@ -1326,9 +3226,8 @@ static int sony_nc_add(struct acpi_device *device) /* find the available acpiset as described in the DSDT */ for (; item->acpiset && *item->acpiset; ++item->acpiset) { - if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, - *item->acpiset, - &handle))) { + if (acpi_has_method(sony_nc_acpi_handle, + *item->acpiset)) { dprintk("Found %s setter: %s\n", item->name, *item->acpiset); item->devattr.attr.mode |= S_IWUSR; @@ -1345,31 +3244,33 @@ static int sony_nc_add(struct acpi_device *device) } } + pr_info("SNC setup done.\n"); return 0; - out_sysfs: +out_sysfs: for (item = sony_nc_values; item->name; ++item) { device_remove_file(&sony_pf_device->dev, &item->devattr); } - sony_pf_remove(); - - outbacklight: - if (sony_backlight_device) - backlight_device_unregister(sony_backlight_device); + sony_nc_backlight_cleanup(); + sony_nc_function_cleanup(sony_pf_device); + sony_nc_handles_cleanup(sony_pf_device); +outplatform: sony_laptop_remove_input(); - outwalk: +outpresent: + sony_pf_remove(); + +outwalk: sony_nc_rfkill_cleanup(); return result; } -static int sony_nc_remove(struct acpi_device *device, int type) +static int sony_nc_remove(struct acpi_device *device) { struct sony_nc_value *item; - if (sony_backlight_device) - backlight_device_unregister(sony_backlight_device); + sony_nc_backlight_cleanup(); sony_nc_acpi_device = NULL; @@ -1377,9 +3278,10 @@ static int sony_nc_remove(struct acpi_device *device, int type) device_remove_file(&sony_pf_device->dev, &item->devattr); } + sony_nc_function_cleanup(sony_pf_device); + sony_nc_handles_cleanup(sony_pf_device); sony_pf_remove(); sony_laptop_remove_input(); - sony_nc_rfkill_cleanup(); dprintk(SONY_NC_DRIVER_NAME " removed.\n"); return 0; @@ -1405,9 +3307,9 @@ static struct acpi_driver sony_nc_driver = { .ops = { .add = sony_nc_add, .remove = sony_nc_remove, - .resume = sony_nc_resume, .notify = sony_nc_notify, }, + .drv.pm = &sony_nc_pm, }; /*********** SPIC (SNY6001) Device ***********/ @@ -1415,7 +3317,6 @@ static struct acpi_driver sony_nc_driver = { #define SONYPI_DEVICE_TYPE1 0x00000001 #define SONYPI_DEVICE_TYPE2 0x00000002 #define SONYPI_DEVICE_TYPE3 0x00000004 -#define SONYPI_DEVICE_TYPE4 0x00000008 #define SONYPI_TYPE1_OFFSET 0x04 #define SONYPI_TYPE2_OFFSET 0x12 @@ -1561,8 +3462,8 @@ static struct sonypi_event sonypi_blueev[] = { /* The set of possible wireless events */ static struct sonypi_event sonypi_wlessev[] = { - { 0x59, SONYPI_EVENT_WIRELESS_ON }, - { 0x5a, SONYPI_EVENT_WIRELESS_OFF }, + { 0x59, SONYPI_EVENT_IGNORE }, + { 0x5a, SONYPI_EVENT_IGNORE }, { 0, 0 } }; @@ -1819,9 +3720,9 @@ out: if (pcidev) pci_dev_put(pcidev); - printk(KERN_INFO DRV_PFX "detected Type%d model\n", - dev->model == SONYPI_DEVICE_TYPE1 ? 1 : - dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3); + pr_info("detected Type%d model\n", + dev->model == SONYPI_DEVICE_TYPE1 ? 1 : + dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3); } /* camera tests and poweron/poweroff */ @@ -1867,7 +3768,7 @@ static int __sony_pic_camera_ready(void) static int __sony_pic_camera_off(void) { if (!camera) { - printk(KERN_WARNING DRV_PFX "camera control not enabled\n"); + pr_warn("camera control not enabled\n"); return -ENODEV; } @@ -1887,7 +3788,7 @@ static int __sony_pic_camera_on(void) int i, j, x; if (!camera) { - printk(KERN_WARNING DRV_PFX "camera control not enabled\n"); + pr_warn("camera control not enabled\n"); return -ENODEV; } @@ -1910,7 +3811,7 @@ static int __sony_pic_camera_on(void) } if (j == 0) { - printk(KERN_WARNING DRV_PFX "failed to power on camera\n"); + pr_warn("failed to power on camera\n"); return -ENODEV; } @@ -1966,8 +3867,7 @@ int sony_pic_camera_command(int command, u8 value) ITERATIONS_SHORT); break; default: - printk(KERN_ERR DRV_PFX "sony_pic_camera_command invalid: %d\n", - command); + pr_err("sony_pic_camera_command invalid: %d\n", command); break; } mutex_unlock(&spic_dev.lock); @@ -1994,7 +3894,9 @@ static ssize_t sony_pic_wwanpower_store(struct device *dev, if (count > 31) return -EINVAL; - value = simple_strtoul(buffer, NULL, 10); + if (kstrtoul(buffer, 10, &value)) + return -EINVAL; + mutex_lock(&spic_dev.lock); __sony_pic_set_wwanpower(value); mutex_unlock(&spic_dev.lock); @@ -2031,7 +3933,9 @@ static ssize_t sony_pic_bluetoothpower_store(struct device *dev, if (count > 31) return -EINVAL; - value = simple_strtoul(buffer, NULL, 10); + if (kstrtoul(buffer, 10, &value)) + return -EINVAL; + mutex_lock(&spic_dev.lock); __sony_pic_set_bluetoothpower(value); mutex_unlock(&spic_dev.lock); @@ -2070,7 +3974,9 @@ static ssize_t sony_pic_fanspeed_store(struct device *dev, if (count > 31) return -EINVAL; - value = simple_strtoul(buffer, NULL, 10); + if (kstrtoul(buffer, 10, &value)) + return -EINVAL; + if (sony_pic_set_fanspeed(value)) return -EIO; @@ -2186,7 +4092,7 @@ static ssize_t sonypi_misc_read(struct file *file, char __user *buf, } if (ret > 0) { - struct inode *inode = file->f_path.dentry->d_inode; + struct inode *inode = file_inode(file); inode->i_atime = current_fs_time(inode->i_sb); } @@ -2224,11 +4130,12 @@ static long sonypi_misc_ioctl(struct file *fp, unsigned int cmd, mutex_lock(&spic_dev.lock); switch (cmd) { case SONYPI_IOCGBRT: - if (sony_backlight_device == NULL) { + if (sony_bl_props.dev == NULL) { ret = -EIO; break; } - if (acpi_callgetfunc(sony_nc_acpi_handle, "GBRT", &value)) { + if (sony_nc_int_call(sony_nc_acpi_handle, "GBRT", NULL, + &value)) { ret = -EIO; break; } @@ -2237,7 +4144,7 @@ static long sonypi_misc_ioctl(struct file *fp, unsigned int cmd, ret = -EFAULT; break; case SONYPI_IOCSBRT: - if (sony_backlight_device == NULL) { + if (sony_bl_props.dev == NULL) { ret = -EIO; break; } @@ -2245,14 +4152,15 @@ static long sonypi_misc_ioctl(struct file *fp, unsigned int cmd, ret = -EFAULT; break; } - if (acpi_callsetfunc(sony_nc_acpi_handle, "SBRT", - (val8 >> 5) + 1, NULL)) { + value = (val8 >> 5) + 1; + if (sony_nc_int_call(sony_nc_acpi_handle, "SBRT", &value, + NULL)) { ret = -EIO; break; } /* sync the backlight device status */ - sony_backlight_device->props.brightness = - sony_backlight_get_brightness(sony_backlight_device); + sony_bl_props.dev->props.brightness = + sony_backlight_get_brightness(sony_bl_props.dev); break; case SONYPI_IOCGBAT1CAP: if (ec_read16(SONYPI_BAT1_FULL, &val16)) { @@ -2348,6 +4256,7 @@ static const struct file_operations sonypi_misc_fops = { .release = sonypi_misc_release, .fasync = sonypi_misc_fasync, .unlocked_ioctl = sonypi_misc_ioctl, + .llseek = noop_llseek, }; static struct miscdevice sonypi_misc_device = { @@ -2372,7 +4281,7 @@ static int sonypi_compat_init(void) error = kfifo_alloc(&sonypi_compat.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL); if (error) { - printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n"); + pr_err("kfifo_alloc failed\n"); return error; } @@ -2382,12 +4291,12 @@ static int sonypi_compat_init(void) sonypi_misc_device.minor = minor; error = misc_register(&sonypi_misc_device); if (error) { - printk(KERN_ERR DRV_PFX "misc_register failed\n"); + pr_err("misc_register failed\n"); goto err_free_kfifo; } if (minor == -1) - printk(KERN_INFO DRV_PFX "device allocated minor is %d\n", - sonypi_misc_device.minor); + pr_info("device allocated minor is %d\n", + sonypi_misc_device.minor); return 0; @@ -2446,9 +4355,8 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context) } for (i = 0; i < p->interrupt_count; i++) { if (!p->interrupts[i]) { - printk(KERN_WARNING DRV_PFX - "Invalid IRQ %d\n", - p->interrupts[i]); + pr_warn("Invalid IRQ %d\n", + p->interrupts[i]); continue; } interrupt = kzalloc(sizeof(*interrupt), @@ -2486,14 +4394,14 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context) ioport->io2.address_length); } else { - printk(KERN_ERR DRV_PFX "Unknown SPIC Type, more than 2 IO Ports\n"); + pr_err("Unknown SPIC Type, more than 2 IO Ports\n"); return AE_ERROR; } return AE_OK; } default: dprintk("Resource %d isn't an IRQ nor an IO port\n", - resource->type); + resource->type); case ACPI_RESOURCE_TYPE_END_TAG: return AE_OK; @@ -2514,7 +4422,7 @@ static int sony_pic_possible_resources(struct acpi_device *device) dprintk("Evaluating _STA\n"); result = acpi_bus_get_status(device); if (result) { - printk(KERN_WARNING DRV_PFX "Unable to read status\n"); + pr_warn("Unable to read status\n"); goto end; } @@ -2530,9 +4438,7 @@ static int sony_pic_possible_resources(struct acpi_device *device) status = acpi_walk_resources(device->handle, METHOD_NAME__PRS, sony_pic_read_possible_resource, &spic_dev); if (ACPI_FAILURE(status)) { - printk(KERN_WARNING DRV_PFX - "Failure evaluating %s\n", - METHOD_NAME__PRS); + pr_warn("Failure evaluating %s\n", METHOD_NAME__PRS); result = -ENODEV; } end: @@ -2618,7 +4524,7 @@ static int sony_pic_enable(struct acpi_device *device, resource->res3.data.irq.sharable = ACPI_SHARED; resource->res4.type = ACPI_RESOURCE_TYPE_END_TAG; - + resource->res4.length = sizeof(struct acpi_resource); } /* setup Type 2/3 resources */ else { @@ -2637,6 +4543,7 @@ static int sony_pic_enable(struct acpi_device *device, resource->res2.data.irq.sharable = ACPI_SHARED; resource->res3.type = ACPI_RESOURCE_TYPE_END_TAG; + resource->res3.length = sizeof(struct acpi_resource); } /* Attempt to set the resource */ @@ -2645,7 +4552,7 @@ static int sony_pic_enable(struct acpi_device *device, /* check for total failure */ if (ACPI_FAILURE(status)) { - printk(KERN_ERR DRV_PFX "Error evaluating _SRS\n"); + pr_err("Error evaluating _SRS\n"); result = -ENODEV; goto end; } @@ -2701,6 +4608,9 @@ static irqreturn_t sony_pic_irq(int irq, void *dev_id) if (ev == dev->event_types[i].events[j].data) { device_event = dev->event_types[i].events[j].event; + /* some events may require ignoring */ + if (!device_event) + return IRQ_HANDLED; goto found; } } @@ -2718,9 +4628,7 @@ static irqreturn_t sony_pic_irq(int irq, void *dev_id) found: sony_laptop_report_input_event(device_event); - acpi_bus_generate_proc_event(dev->acpi_dev, 1, device_event); sonypi_compat_report_event(device_event); - return IRQ_HANDLED; } @@ -2729,13 +4637,13 @@ found: * ACPI driver * *****************/ -static int sony_pic_remove(struct acpi_device *device, int type) +static int sony_pic_remove(struct acpi_device *device) { struct sony_pic_ioport *io, *tmp_io; struct sony_pic_irq *irq, *tmp_irq; if (sony_pic_disable(device)) { - printk(KERN_ERR DRV_PFX "Couldn't disable device.\n"); + pr_err("Couldn't disable device\n"); return -ENXIO; } @@ -2775,9 +4683,6 @@ static int sony_pic_add(struct acpi_device *device) struct sony_pic_ioport *io, *tmp_io; struct sony_pic_irq *irq, *tmp_irq; - printk(KERN_INFO DRV_PFX "%s v%s.\n", - SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); - spic_dev.acpi_dev = device; strcpy(acpi_device_class(device), "sony/hotkey"); sony_pic_detect_device_type(&spic_dev); @@ -2786,26 +4691,25 @@ static int sony_pic_add(struct acpi_device *device) /* read _PRS resources */ result = sony_pic_possible_resources(device); if (result) { - printk(KERN_ERR DRV_PFX - "Unable to read possible resources.\n"); + pr_err("Unable to read possible resources\n"); goto err_free_resources; } /* setup input devices and helper fifo */ result = sony_laptop_setup_input(device); if (result) { - printk(KERN_ERR DRV_PFX - "Unable to create input devices.\n"); + pr_err("Unable to create input devices\n"); goto err_free_resources; } - if (sonypi_compat_init()) + result = sonypi_compat_init(); + if (result) goto err_remove_input; /* request io port */ list_for_each_entry_reverse(io, &spic_dev.ioports, list) { if (request_region(io->io1.minimum, io->io1.address_length, - "Sony Programable I/O Device")) { + "Sony Programmable I/O Device")) { dprintk("I/O port1: 0x%.4x (0x%.4x) + 0x%.2x\n", io->io1.minimum, io->io1.maximum, io->io1.address_length); @@ -2813,7 +4717,7 @@ static int sony_pic_add(struct acpi_device *device) if (io->io2.minimum) { if (request_region(io->io2.minimum, io->io2.address_length, - "Sony Programable I/O Device")) { + "Sony Programmable I/O Device")) { dprintk("I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n", io->io2.minimum, io->io2.maximum, io->io2.address_length); @@ -2836,7 +4740,7 @@ static int sony_pic_add(struct acpi_device *device) } } if (!spic_dev.cur_ioport) { - printk(KERN_ERR DRV_PFX "Failed to request_region.\n"); + pr_err("Failed to request_region\n"); result = -ENODEV; goto err_remove_compat; } @@ -2844,7 +4748,7 @@ static int sony_pic_add(struct acpi_device *device) /* request IRQ */ list_for_each_entry_reverse(irq, &spic_dev.interrupts, list) { if (!request_irq(irq->irq.interrupts[0], sony_pic_irq, - IRQF_DISABLED, "sony-laptop", &spic_dev)) { + 0, "sony-laptop", &spic_dev)) { dprintk("IRQ: %d - triggering: %d - " "polarity: %d - shr: %d\n", irq->irq.interrupts[0], @@ -2856,7 +4760,7 @@ static int sony_pic_add(struct acpi_device *device) } } if (!spic_dev.cur_irq) { - printk(KERN_ERR DRV_PFX "Failed to request_irq.\n"); + pr_err("Failed to request_irq\n"); result = -ENODEV; goto err_release_region; } @@ -2864,7 +4768,7 @@ static int sony_pic_add(struct acpi_device *device) /* set resource status _SRS */ result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq); if (result) { - printk(KERN_ERR DRV_PFX "Couldn't enable device.\n"); + pr_err("Couldn't enable device\n"); goto err_free_irq; } @@ -2878,6 +4782,7 @@ static int sony_pic_add(struct acpi_device *device) if (result) goto err_remove_pf; + pr_info("SPIC setup done.\n"); return 0; err_remove_pf: @@ -2917,18 +4822,23 @@ err_free_resources: return result; } -static int sony_pic_suspend(struct acpi_device *device, pm_message_t state) +#ifdef CONFIG_PM_SLEEP +static int sony_pic_suspend(struct device *dev) { - if (sony_pic_disable(device)) + if (sony_pic_disable(to_acpi_device(dev))) return -ENXIO; return 0; } -static int sony_pic_resume(struct acpi_device *device) +static int sony_pic_resume(struct device *dev) { - sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq); + sony_pic_enable(to_acpi_device(dev), + spic_dev.cur_ioport, spic_dev.cur_irq); return 0; } +#endif + +static SIMPLE_DEV_PM_OPS(sony_pic_pm, sony_pic_suspend, sony_pic_resume); static const struct acpi_device_id sony_pic_device_ids[] = { {SONY_PIC_HID, 0}, @@ -2943,9 +4853,8 @@ static struct acpi_driver sony_pic_driver = { .ops = { .add = sony_pic_add, .remove = sony_pic_remove, - .suspend = sony_pic_suspend, - .resume = sony_pic_resume, }, + .drv.pm = &sony_pic_pm, }; static struct dmi_system_id __initdata sonypi_dmi_table[] = { @@ -2973,8 +4882,7 @@ static int __init sony_laptop_init(void) if (!no_spic && dmi_check_system(sonypi_dmi_table)) { result = acpi_bus_register_driver(&sony_pic_driver); if (result) { - printk(KERN_ERR DRV_PFX - "Unable to register SPIC driver."); + pr_err("Unable to register SPIC driver\n"); goto out; } spic_drv_registered = 1; @@ -2982,7 +4890,7 @@ static int __init sony_laptop_init(void) result = acpi_bus_register_driver(&sony_nc_driver); if (result) { - printk(KERN_ERR DRV_PFX "Unable to register SNC driver."); + pr_err("Unable to register SNC driver\n"); goto out_unregister_pic; } diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c index dd33b51c348..6a6ea28a7e5 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -25,13 +25,14 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> +#include <linux/slab.h> #include <linux/init.h> #include <linux/types.h> -#include <acpi/acpi.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/acpi.h> #include <linux/platform_device.h> #define GUID "C364AC71-36DB-495A-8494-B439D472A505" @@ -39,9 +40,6 @@ #define TC1100_INSTANCE_WIRELESS 1 #define TC1100_INSTANCE_JOGDIAL 2 -#define TC1100_LOGPREFIX "tc1100-wmi: " -#define TC1100_INFO KERN_INFO TC1100_LOGPREFIX - MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho"); MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras"); MODULE_LICENSE("GPL"); @@ -161,7 +159,7 @@ set_bool_##value(struct device *dev, struct device_attribute *attr, \ return -EINVAL; \ return count; \ } \ -static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \ +static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, \ show_bool_##value, set_bool_##value); show_set_bool(wireless, TC1100_INSTANCE_WIRELESS); @@ -187,7 +185,7 @@ static int __init tc1100_probe(struct platform_device *device) } -static int __devexit tc1100_remove(struct platform_device *device) +static int tc1100_remove(struct platform_device *device) { sysfs_remove_group(&device->dev.kobj, &tc1100_attribute_group); @@ -241,7 +239,7 @@ static struct platform_driver tc1100_driver = { .pm = &tc1100_pm_ops, #endif }, - .remove = __devexit_p(tc1100_remove), + .remove = tc1100_remove, }; static int __init tc1100_init(void) @@ -263,7 +261,7 @@ static int __init tc1100_init(void) if (error) goto err_device_del; - printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras loaded\n"); + pr_info("HP Compaq TC1100 Tablet WMI Extras loaded\n"); return 0; err_device_del: diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index e67e4feb35c..d82f196e3cf 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -21,7 +21,9 @@ * 02110-1301, USA. */ -#define TPACPI_VERSION "0.24" +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define TPACPI_VERSION "0.25" #define TPACPI_SYSFS_VERSION 0x020700 /* @@ -58,7 +60,7 @@ #include <linux/kthread.h> #include <linux/freezer.h> #include <linux/delay.h> - +#include <linux/slab.h> #include <linux/nvram.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> @@ -71,20 +73,16 @@ #include <linux/input.h> #include <linux/leds.h> #include <linux/rfkill.h> -#include <asm/uaccess.h> - #include <linux/dmi.h> #include <linux/jiffies.h> #include <linux/workqueue.h> - +#include <linux/acpi.h> +#include <linux/pci_ids.h> +#include <linux/thinkpad_acpi.h> #include <sound/core.h> #include <sound/control.h> #include <sound/initval.h> - -#include <acpi/acpi_drivers.h> - -#include <linux/pci_ids.h> - +#include <asm/uaccess.h> /* ThinkPad CMOS commands */ #define TP_CMOS_VOLUME_DOWN 0 @@ -121,8 +119,15 @@ enum { TP_NVRAM_POS_LEVEL_VOLUME = 0, }; +/* Misc NVRAM-related */ +enum { + TP_NVRAM_LEVEL_VOLUME_MAX = 14, +}; + /* ACPI HIDs */ -#define TPACPI_ACPI_HKEY_HID "IBM0068" +#define TPACPI_ACPI_IBM_HKEY_HID "IBM0068" +#define TPACPI_ACPI_LENOVO_HKEY_HID "LEN0068" +#define TPACPI_ACPI_EC_HID "PNP0C09" /* Input IDs */ #define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */ @@ -174,6 +179,10 @@ enum tpacpi_hkey_event_t { /* 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 */ /* User-interface events */ TP_HKEY_EV_LID_CLOSE = 0x5001, /* laptop lid closed */ @@ -184,6 +193,10 @@ enum tpacpi_hkey_event_t { 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 */ + /* Thermal events */ TP_HKEY_EV_ALARM_BAT_HOT = 0x6011, /* battery too hot */ TP_HKEY_EV_ALARM_BAT_XHOT = 0x6012, /* battery critically hot */ @@ -191,6 +204,9 @@ enum tpacpi_hkey_event_t { TP_HKEY_EV_ALARM_SENSOR_XHOT = 0x6022, /* sensor critically hot */ TP_HKEY_EV_THM_TABLE_CHANGED = 0x6030, /* thermal table changed */ + /* AC-related events */ + TP_HKEY_EV_AC_CHANGED = 0x6040, /* AC status changed */ + /* Misc */ TP_HKEY_EV_RFKILL_CHANGED = 0x7000, /* rfkill switch changed */ }; @@ -216,17 +232,6 @@ enum tpacpi_hkey_event_t { #define TPACPI_MAX_ACPI_ARGS 3 -/* printk headers */ -#define TPACPI_LOG TPACPI_FILE ": " -#define TPACPI_EMERG KERN_EMERG TPACPI_LOG -#define TPACPI_ALERT KERN_ALERT TPACPI_LOG -#define TPACPI_CRIT KERN_CRIT TPACPI_LOG -#define TPACPI_ERR KERN_ERR TPACPI_LOG -#define TPACPI_WARN KERN_WARNING TPACPI_LOG -#define TPACPI_NOTICE KERN_NOTICE TPACPI_LOG -#define TPACPI_INFO KERN_INFO TPACPI_LOG -#define TPACPI_DEBUG KERN_DEBUG TPACPI_LOG - /* Debugging printk groups */ #define TPACPI_DBG_ALL 0xffff #define TPACPI_DBG_DISCLOSETASK 0x8000 @@ -266,7 +271,7 @@ struct ibm_struct { int (*write) (char *); void (*exit) (void); void (*resume) (void); - void (*suspend) (pm_message_t state); + void (*suspend) (void); void (*shutdown) (void); struct list_head all_drivers; @@ -286,6 +291,7 @@ struct ibm_init_struct { char param[32]; int (*init) (struct ibm_init_struct *); + umode_t base_procfs_mode; struct ibm_struct *data; }; @@ -297,8 +303,8 @@ static struct { u32 hotkey_tablet:1; u32 light:1; u32 light_status:1; - u32 bright_16levels:1; u32 bright_acpimode:1; + u32 bright_unkfw:1; u32 wan:1; u32 uwb:1; u32 fan_ctrl_status_undef:1; @@ -358,18 +364,21 @@ struct tpacpi_led_classdev { struct led_classdev led_classdev; struct work_struct work; enum led_status_t new_state; - unsigned int led; + int led; }; +/* brightness level capabilities */ +static unsigned int bright_maxlvl; /* 0 = unknown */ + #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES static int dbg_wlswemul; -static int tpacpi_wlsw_emulstate; +static bool tpacpi_wlsw_emulstate; static int dbg_bluetoothemul; -static int tpacpi_bluetooth_emulstate; +static bool tpacpi_bluetooth_emulstate; static int dbg_wwanemul; -static int tpacpi_wwan_emulstate; +static bool tpacpi_wwan_emulstate; static int dbg_uwbemul; -static int tpacpi_uwb_emulstate; +static bool tpacpi_uwb_emulstate; #endif @@ -377,34 +386,36 @@ static int tpacpi_uwb_emulstate; * Debugging helpers */ -#define dbg_printk(a_dbg_level, format, arg...) \ - do { if (dbg_level & (a_dbg_level)) \ - printk(TPACPI_DEBUG "%s: " format, __func__ , ## arg); \ - } while (0) +#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 -#define vdbg_printk(a_dbg_level, format, arg...) \ - do { } while (0) +static inline const char *str_supported(int is_supported) { return ""; } +#define vdbg_printk(a_dbg_level, format, arg...) \ + no_printk(format, ##arg) #endif static void tpacpi_log_usertask(const char * const what) { - printk(TPACPI_DEBUG "%s: access by process with PID %d\n", - what, task_tgid_vnr(current)); + 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(TPACPI_DEBUG "%s: PID %d: " format, \ - what, task_tgid_vnr(current), ## arg); \ - } \ - } while (0) +#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 @@ -478,6 +489,15 @@ static unsigned long __init tpacpi_check_quirks( return 0; } +static inline bool __pure __init tpacpi_is_lenovo(void) +{ + return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO; +} + +static inline bool __pure __init tpacpi_is_ibm(void) +{ + return thinkpad_id.vendor == PCI_VENDOR_ID_IBM; +} /**************************************************************************** **************************************************************************** @@ -492,21 +512,13 @@ static unsigned long __init tpacpi_check_quirks( */ static acpi_handle root_handle; +static acpi_handle ec_handle; #define TPACPI_HANDLE(object, parent, paths...) \ static acpi_handle object##_handle; \ - static acpi_handle *object##_parent = &parent##_handle; \ - static char *object##_path; \ - static char *object##_paths[] = { paths } - -TPACPI_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */ - "\\_SB.PCI.ISA.EC", /* 570 */ - "\\_SB.PCI0.ISA0.EC0", /* 600e/x, 770e, 770x */ - "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */ - "\\_SB.PCI0.AD4S.EC0", /* i1400, R30 */ - "\\_SB.PCI0.ICH3.EC0", /* R31 */ - "\\_SB.PCI0.LPC.EC", /* all others */ - ); + 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 */ @@ -522,20 +534,12 @@ TPACPI_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ "HKEY", /* all others */ ); /* 570 */ -TPACPI_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */ - "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */ - "\\_SB.PCI0.VID0", /* 770e */ - "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */ - "\\_SB.PCI0.AGP.VID", /* all others */ - ); /* R30, R31 */ - - /************************************************************************* * ACPI helpers */ static int acpi_evalf(acpi_handle handle, - void *res, char *method, char *fmt, ...) + int *res, char *method, char *fmt, ...) { char *fmt0 = fmt; struct acpi_object_list params; @@ -549,7 +553,7 @@ static int acpi_evalf(acpi_handle handle, int quiet; if (!*fmt) { - printk(TPACPI_ERR "acpi_evalf() called with empty format\n"); + pr_err("acpi_evalf() called with empty format\n"); return 0; } @@ -574,8 +578,9 @@ static int acpi_evalf(acpi_handle handle, break; /* add more types as needed */ default: - printk(TPACPI_ERR "acpi_evalf() called " + pr_err("acpi_evalf() called " "with invalid format character '%c'\n", c); + va_end(ap); return 0; } } @@ -592,23 +597,24 @@ static int acpi_evalf(acpi_handle handle, switch (res_type) { case 'd': /* int */ - if (res) - *(int *)res = out_obj.integer.value; - success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; + 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: - printk(TPACPI_ERR "acpi_evalf() called " + pr_err("acpi_evalf() called " "with invalid format character '%c'\n", res_type); return 0; } if (!success && !quiet) - printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %d\n", - method, fmt0, status); + pr_err("acpi_evalf(%s, %s, ...) failed: %s\n", + method, fmt0, acpi_format_exception(status)); return success; } @@ -659,11 +665,11 @@ static int issue_thinkpad_cmos_command(int cmos_cmd) #define TPACPI_ACPIHANDLE_INIT(object) \ drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \ - object##_paths, ARRAY_SIZE(object##_paths), &object##_path) + object##_paths, ARRAY_SIZE(object##_paths)) -static void drv_acpi_handle_init(char *name, - acpi_handle *handle, acpi_handle parent, - char **paths, int num_paths, char **path) +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; @@ -674,10 +680,9 @@ static void drv_acpi_handle_init(char *name, for (i = 0; i < num_paths; i++) { status = acpi_get_handle(parent, paths[i], handle); if (ACPI_SUCCESS(status)) { - *path = paths[i]; dbg_printk(TPACPI_DBG_INIT, "Found ACPI handle %s for %s\n", - *path, name); + paths[i], name); return; } } @@ -687,6 +692,51 @@ static void drv_acpi_handle_init(char *name, *handle = NULL; } +static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, + u32 level, void *context, void **return_value) +{ + struct acpi_device *dev; + if (!strcmp(context, "video")) { + if (acpi_bus_get_device(handle, &dev)) + return AE_OK; + if (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; @@ -715,8 +765,7 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); if (rc < 0) { - printk(TPACPI_ERR "acpi_bus_get_device(%s) failed: %d\n", - ibm->name, rc); + pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc); return -ENODEV; } @@ -729,13 +778,11 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) ibm->acpi->type, dispatch_acpi_notify, ibm); if (ACPI_FAILURE(status)) { if (status == AE_ALREADY_EXISTS) { - printk(TPACPI_NOTICE - "another device driver is already " - "handling %s events\n", ibm->name); + pr_notice("another device driver is already " + "handling %s events\n", ibm->name); } else { - printk(TPACPI_ERR - "acpi_install_notify_handler(%s) failed: %d\n", - ibm->name, status); + pr_err("acpi_install_notify_handler(%s) failed: %s\n", + ibm->name, acpi_format_exception(status)); } return -ENODEV; } @@ -759,8 +806,7 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); if (!ibm->acpi->driver) { - printk(TPACPI_ERR - "failed to allocate memory for ibm->acpi->driver\n"); + pr_err("failed to allocate memory for ibm->acpi->driver\n"); return -ENOMEM; } @@ -771,7 +817,7 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) rc = acpi_bus_register_driver(ibm->acpi->driver); if (rc < 0) { - printk(TPACPI_ERR "acpi_bus_register_driver(%s) failed: %d\n", + pr_err("acpi_bus_register_driver(%s) failed: %d\n", ibm->name, rc); kfree(ibm->acpi->driver); ibm->acpi->driver = NULL; @@ -801,14 +847,14 @@ static int dispatch_proc_show(struct seq_file *m, void *v) static int dispatch_proc_open(struct inode *inode, struct file *file) { - return single_open(file, dispatch_proc_show, PDE(inode)->data); + 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(file->f_path.dentry->d_inode)->data; + struct ibm_struct *ibm = PDE_DATA(file_inode(file)); char *kernbuf; int ret; @@ -878,8 +924,8 @@ static struct input_dev *tpacpi_inputdev; static struct mutex tpacpi_inputdev_send_mutex; static LIST_HEAD(tpacpi_all_drivers); -static int tpacpi_suspend_handler(struct platform_device *pdev, - pm_message_t state) +#ifdef CONFIG_PM_SLEEP +static int tpacpi_suspend_handler(struct device *dev) { struct ibm_struct *ibm, *itmp; @@ -887,13 +933,13 @@ static int tpacpi_suspend_handler(struct platform_device *pdev, &tpacpi_all_drivers, all_drivers) { if (ibm->suspend) - (ibm->suspend)(state); + (ibm->suspend)(); } return 0; } -static int tpacpi_resume_handler(struct platform_device *pdev) +static int tpacpi_resume_handler(struct device *dev) { struct ibm_struct *ibm, *itmp; @@ -906,6 +952,10 @@ static int tpacpi_resume_handler(struct platform_device *pdev) 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) { @@ -923,9 +973,8 @@ static struct platform_driver tpacpi_pdriver = { .driver = { .name = TPACPI_DRVR_NAME, .owner = THIS_MODULE, + .pm = &tpacpi_pm, }, - .suspend = tpacpi_suspend_handler, - .resume = tpacpi_resume_handler, .shutdown = tpacpi_shutdown_handler, }; @@ -1029,89 +1078,14 @@ static int parse_strtoul(const char *buf, static void tpacpi_disable_brightness_delay(void) { if (acpi_evalf(hkey_handle, NULL, "PWMS", "qvd", 0)) - printk(TPACPI_NOTICE - "ACPI backlight control delay disabled\n"); -} - -static int __init tpacpi_query_bcl_levels(acpi_handle handle) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; - int rc; - - if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) { - obj = (union acpi_object *)buffer.pointer; - if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) { - printk(TPACPI_ERR "Unknown _BCL data, " - "please report this to %s\n", TPACPI_MAIL); - rc = 0; - } else { - rc = obj->package.count; - } - } else { - return 0; - } - - kfree(buffer.pointer); - return rc; -} - -static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle, - u32 lvl, void *context, void **rv) -{ - char name[ACPI_PATH_SEGMENT_LENGTH]; - struct acpi_buffer buffer = { sizeof(name), &name }; - - if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) && - !strncmp("_BCL", name, sizeof(name) - 1)) { - BUG_ON(!rv || !*rv); - **(int **)rv = tpacpi_query_bcl_levels(handle); - return AE_CTRL_TERMINATE; - } else { - return AE_OK; - } -} - -/* - * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map - */ -static int __init tpacpi_check_std_acpi_brightness_support(void) -{ - int status; - int bcl_levels = 0; - void *bcl_ptr = &bcl_levels; - - if (!vid_handle) { - TPACPI_ACPIHANDLE_INIT(vid); - } - if (!vid_handle) - return 0; - - /* - * Search for a _BCL method, and execute it. This is safe on all - * ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista - * BIOS in ACPI backlight control mode. We do NOT have to care - * about calling the _BCL method in an enabled video device, any - * will do for our purposes. - */ - - status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3, - tpacpi_acpi_walk_find_bcl, NULL, NULL, - &bcl_ptr); - - if (ACPI_SUCCESS(status) && bcl_levels > 2) { - tp_features.bright_acpimode = 1; - return (bcl_levels - 2); - } - - return 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"); - printk(TPACPI_WARN "WARNING: sysfs attribute %s is deprecated and " + pr_warn("WARNING: sysfs attribute %s is deprecated and " "will be removed. %s\n", what, details); } @@ -1286,8 +1260,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, &tpacpi_rfk_rfkill_ops, atp_rfk); if (!atp_rfk || !atp_rfk->rfkill) { - printk(TPACPI_ERR - "failed to allocate memory for rfkill class\n"); + pr_err("failed to allocate memory for rfkill class\n"); kfree(atp_rfk); return -ENOMEM; } @@ -1297,9 +1270,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, sw_status = (tp_rfkops->get_status)(); if (sw_status < 0) { - printk(TPACPI_ERR - "failed to read initial state for %s, error %d\n", - name, sw_status); + 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) { @@ -1313,9 +1285,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, res = rfkill_register(atp_rfk->rfkill); if (res < 0) { - printk(TPACPI_ERR - "failed to register %s rfkill switch: %d\n", - name, res); + pr_err("failed to register %s rfkill switch: %d\n", name, res); rfkill_destroy(atp_rfk->rfkill); kfree(atp_rfk); return res; @@ -1323,7 +1293,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, tpacpi_rfkill_switches[id] = atp_rfk; - printk(TPACPI_INFO "rfkill switch %s: radio is %sblocked\n", + pr_info("rfkill switch %s: radio is %sblocked\n", name, (sw_state || hw_state) ? "" : "un"); return 0; } @@ -1667,7 +1637,7 @@ static void tpacpi_remove_driver_attributes(struct device_driver *drv) * Table of recommended minimum BIOS versions * * Reasons for listing: - * 1. Stable BIOS, listed because the unknown ammount of + * 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 @@ -1847,10 +1817,8 @@ static void __init tpacpi_check_outdated_fw(void) * 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 " + 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"); } } @@ -1870,34 +1838,9 @@ static bool __init tpacpi_is_fw_known(void) ****************************************************************************/ /************************************************************************* - * thinkpad-acpi init subdriver + * thinkpad-acpi metadata subdriver */ -static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm) -{ - printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION); - printk(TPACPI_INFO "%s\n", TPACPI_URL); - - printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n", - (thinkpad_id.bios_version_str) ? - thinkpad_id.bios_version_str : "unknown", - (thinkpad_id.ec_version_str) ? - thinkpad_id.ec_version_str : "unknown"); - - if (thinkpad_id.vendor && thinkpad_id.model_str) - printk(TPACPI_INFO "%s %s, model %s\n", - (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ? - "IBM" : ((thinkpad_id.vendor == - PCI_VENDOR_ID_LENOVO) ? - "Lenovo" : "Unknown vendor"), - thinkpad_id.model_str, - (thinkpad_id.nummodel_str) ? - thinkpad_id.nummodel_str : "unknown"); - - tpacpi_check_outdated_fw(); - return 0; -} - static int thinkpad_acpi_driver_read(struct seq_file *m) { seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC); @@ -1960,6 +1903,17 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_VOLUMEDOWN, TP_ACPI_HOTKEYSCAN_MUTE, TP_ACPI_HOTKEYSCAN_THINKPAD, + TP_ACPI_HOTKEYSCAN_UNK1, + TP_ACPI_HOTKEYSCAN_UNK2, + TP_ACPI_HOTKEYSCAN_UNK3, + TP_ACPI_HOTKEYSCAN_UNK4, + TP_ACPI_HOTKEYSCAN_UNK5, + TP_ACPI_HOTKEYSCAN_UNK6, + TP_ACPI_HOTKEYSCAN_UNK7, + TP_ACPI_HOTKEYSCAN_UNK8, + + /* Hotkey keymap size */ + TPACPI_HOTKEY_MAP_LEN }; enum { /* Keys/events available through NVRAM polling */ @@ -2013,9 +1967,6 @@ struct tp_nvram_state { /* kthread for the hotkey poller */ static struct task_struct *tpacpi_hotkey_task; -/* 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 as an * atomic block. @@ -2074,14 +2025,13 @@ 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 unsigned int hotkey_report_mode; - static u16 *hotkey_keycode_map; static struct attribute_set *hotkey_dev_attributes; static void tpacpi_driver_event(const unsigned int hkey_event); static void hotkey_driver_event(const unsigned int scancode); +static void hotkey_poll_setup(const bool may_warn); /* HKEY.MHKG() return bits */ #define TP_HOTKEY_TABLET_MASK (1 << 3) @@ -2152,9 +2102,7 @@ void static hotkey_mask_warn_incomplete_mask(void) (hotkey_all_mask | TPACPI_HKEY_NVRAM_KNOWN_MASK); if (wantedmask) - printk(TPACPI_NOTICE - "required events 0x%08x not enabled!\n", - wantedmask); + pr_notice("required events 0x%08x not enabled!\n", wantedmask); } /* @@ -2192,10 +2140,9 @@ static int hotkey_mask_set(u32 mask) * a given event. */ if (!hotkey_mask_get() && !rc && (fwmask & ~hotkey_acpi_mask)) { - printk(TPACPI_NOTICE - "asked for hotkey mask 0x%08x, but " - "firmware forced it to 0x%08x\n", - 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) @@ -2219,13 +2166,11 @@ static int hotkey_user_mask_set(const u32 mask) (mask == 0xffff || mask == 0xffffff || mask == 0xffffffff)) { tp_warned.hotkey_mask_ff = 1; - printk(TPACPI_NOTICE - "setting the hotkey mask to 0x%08x is likely " - "not the best way to go about it\n", mask); - printk(TPACPI_NOTICE - "please consider using the driver defaults, " - "and refer to up-to-date thinkpad-acpi " - "documentation\n"); + 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. @@ -2264,6 +2209,8 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask) rc = hotkey_mask_set((hotkey_acpi_mask | hotkey_driver_mask) & ~hotkey_source_mask); + hotkey_poll_setup(true); + mutex_unlock(&hotkey_mutex); return rc; @@ -2309,16 +2256,12 @@ static void tpacpi_input_send_key(const unsigned int scancode) if (keycode != KEY_RESERVED) { mutex_lock(&tpacpi_inputdev_send_mutex); + input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); 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_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); 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); @@ -2340,10 +2283,6 @@ static struct tp_acpi_drv_struct ibm_hotkey_acpidriver; static void tpacpi_hotkey_send_key(unsigned int scancode) { tpacpi_input_send_key_masked(scancode); - if (hotkey_report_mode < 2) { - acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device, - 0x80, TP_HKEY_EV_HOTKEY_BASE + scancode); - } } static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) @@ -2382,24 +2321,56 @@ static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m) } } +#define TPACPI_COMPARE_KEY(__scancode, __member) \ +do { \ + if ((event_mask & (1 << __scancode)) && \ + oldn->__member != newn->__member) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) + +#define TPACPI_MAY_SEND_KEY(__scancode) \ +do { \ + if (event_mask & (1 << __scancode)) \ + tpacpi_hotkey_send_key(__scancode); \ +} while (0) + +static void issue_volchange(const unsigned int oldvol, + const unsigned int newvol, + const u32 event_mask) +{ + unsigned int i = oldvol; + + while (i > newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); + i--; + } + while (i < newvol) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); + i++; + } +} + +static void issue_brightnesschange(const unsigned int oldbrt, + const unsigned int newbrt, + const u32 event_mask) +{ + unsigned int i = oldbrt; + + while (i > newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); + i--; + } + while (i < newbrt) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); + i++; + } +} + static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, struct tp_nvram_state *newn, const u32 event_mask) { -#define TPACPI_COMPARE_KEY(__scancode, __member) \ - do { \ - if ((event_mask & (1 << __scancode)) && \ - oldn->__member != newn->__member) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) - -#define TPACPI_MAY_SEND_KEY(__scancode) \ - do { \ - if (event_mask & (1 << __scancode)) \ - tpacpi_hotkey_send_key(__scancode); \ - } while (0) - 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); @@ -2409,41 +2380,63 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle); - /* handle volume */ - if (oldn->volume_toggle != newn->volume_toggle) { - if (oldn->mute != newn->mute) { + /* + * Handle volume + * + * This code is supposed to duplicate the IBM firmware behaviour: + * - Pressing MUTE issues mute hotkey message, even when already mute + * - Pressing Volume up/down issues volume up/down hotkey messages, + * even when already at maximum or minimum volume + * - The act of unmuting issues volume up/down notification, + * depending which key was used to unmute + * + * We are constrained to what the NVRAM can tell us, which is not much + * and certainly not enough if more than one volume hotkey was pressed + * since the last poll cycle. + * + * Just to make our life interesting, some newer Lenovo ThinkPads have + * bugs in the BIOS and may fail to update volume_toggle properly. + */ + if (newn->mute) { + /* muted */ + if (!oldn->mute || + oldn->volume_toggle != newn->volume_toggle || + oldn->volume_level != newn->volume_level) { + /* recently muted, or repeated mute keypress, or + * multiple presses ending in mute */ + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); 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) { + } else { + /* unmute */ + if (oldn->mute) { + /* recently unmuted, issue 'unmute' keypress */ 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 { + } + if (oldn->volume_level != newn->volume_level) { + issue_volchange(oldn->volume_level, newn->volume_level, + event_mask); + } else if (oldn->volume_toggle != newn->volume_toggle) { + /* repeated vol up/down keypress at end of scale ? */ + if (newn->volume_level == 0) TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); - } + else if (newn->volume_level >= TP_NVRAM_LEVEL_VOLUME_MAX) + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); } } /* 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) { + if (oldn->brightness_level != newn->brightness_level) { + issue_brightnesschange(oldn->brightness_level, + newn->brightness_level, event_mask); + } else if (oldn->brightness_toggle != newn->brightness_toggle) { + /* repeated key presses that didn't change state */ + if (newn->brightness_level == 0) 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); - } - } + else if (newn->brightness_level >= bright_maxlvl + && !tp_features.bright_unkfw) + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); } #undef TPACPI_COMPARE_KEY @@ -2463,10 +2456,9 @@ static int hotkey_kthread(void *data) u32 poll_mask, event_mask; unsigned int si, so; unsigned long t; - unsigned int change_detector, must_reset; + unsigned int change_detector; unsigned int poll_freq; - - mutex_lock(&hotkey_thread_mutex); + bool was_frozen; if (tpacpi_lifecycle == TPACPI_LIFE_EXITING) goto exit; @@ -2495,14 +2487,14 @@ static int hotkey_kthread(void *data) t = 100; /* should never happen... */ } t = msleep_interruptible(t); - if (unlikely(kthread_should_stop())) + if (unlikely(kthread_freezable_should_stop(&was_frozen))) break; - must_reset = try_to_freeze(); - if (t > 0 && !must_reset) + + if (t > 0 && !was_frozen) continue; mutex_lock(&hotkey_thread_data_mutex); - if (must_reset || hotkey_config_change != change_detector) { + if (was_frozen || hotkey_config_change != change_detector) { /* forget old state on thaw or config change */ si = so; t = 0; @@ -2527,7 +2519,6 @@ static int hotkey_kthread(void *data) } exit: - mutex_unlock(&hotkey_thread_mutex); return 0; } @@ -2535,20 +2526,13 @@ exit: static void hotkey_poll_stop_sync(void) { if (tpacpi_hotkey_task) { - if (frozen(tpacpi_hotkey_task) || - freezing(tpacpi_hotkey_task)) - thaw_process(tpacpi_hotkey_task); - kthread_stop(tpacpi_hotkey_task); tpacpi_hotkey_task = NULL; - mutex_lock(&hotkey_thread_mutex); - /* at this point, the thread did exit */ - mutex_unlock(&hotkey_thread_mutex); } } /* call with hotkey_mutex held */ -static void hotkey_poll_setup(bool may_warn) +static void hotkey_poll_setup(const bool may_warn) { const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask; const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask; @@ -2561,8 +2545,7 @@ static void hotkey_poll_setup(bool may_warn) NULL, TPACPI_NVRAM_KTHREAD_NAME); if (IS_ERR(tpacpi_hotkey_task)) { tpacpi_hotkey_task = NULL; - printk(TPACPI_ERR - "could not create kernel thread " + pr_err("could not create kernel thread " "for hotkey polling\n"); } } @@ -2570,16 +2553,15 @@ static void hotkey_poll_setup(bool may_warn) hotkey_poll_stop_sync(); if (may_warn && (poll_driver_mask || poll_user_mask) && hotkey_poll_freq == 0) { - printk(TPACPI_NOTICE - "hot keys 0x%08x and/or events 0x%08x " - "require polling, which is currently " - "disabled\n", - poll_user_mask, poll_driver_mask); + pr_notice("hot keys 0x%08x and/or events 0x%08x " + "require polling, which is currently " + "disabled\n", + poll_user_mask, poll_driver_mask); } } } -static void hotkey_poll_setup_safe(bool may_warn) +static void hotkey_poll_setup_safe(const bool may_warn) { mutex_lock(&hotkey_mutex); hotkey_poll_setup(may_warn); @@ -2597,7 +2579,11 @@ static void hotkey_poll_set_freq(unsigned int freq) #else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ -static void hotkey_poll_setup_safe(bool __unused) +static void hotkey_poll_setup(const bool __unused) +{ +} + +static void hotkey_poll_setup_safe(const bool __unused) { } @@ -2607,16 +2593,11 @@ static int hotkey_inputdev_open(struct input_dev *dev) { switch (tpacpi_lifecycle) { case TPACPI_LIFE_INIT: - /* - * hotkey_init will call hotkey_poll_setup_safe - * at the appropriate moment - */ - return 0; - case TPACPI_LIFE_EXITING: - return -EBUSY; case TPACPI_LIFE_RUNNING: hotkey_poll_setup_safe(false); return 0; + case TPACPI_LIFE_EXITING: + return -EBUSY; } /* Should only happen if tpacpi_lifecycle is corrupt */ @@ -2627,7 +2608,7 @@ static int hotkey_inputdev_open(struct input_dev *dev) static void hotkey_inputdev_close(struct input_dev *dev) { /* disable hotkey polling when possible */ - if (tpacpi_lifecycle == TPACPI_LIFE_RUNNING && + if (tpacpi_lifecycle != TPACPI_LIFE_EXITING && !(hotkey_source_mask & hotkey_driver_mask)) hotkey_poll_setup_safe(false); } @@ -2799,13 +2780,13 @@ static ssize_t hotkey_source_mask_store(struct device *dev, mutex_unlock(&hotkey_mutex); if (rc < 0) - printk(TPACPI_ERR "hotkey_source_mask: failed to update the" - "firmware event mask!\n"); + pr_err("hotkey_source_mask: " + "failed to update the firmware event mask!\n"); if (r_ev) - printk(TPACPI_NOTICE "hotkey_source_mask: " - "some important events were disabled: " - "0x%04x\n", r_ev); + pr_notice("hotkey_source_mask: " + "some important events were disabled: 0x%04x\n", + r_ev); tpacpi_disclose_usertask("hotkey_source_mask", "set to 0x%08lx\n", t); @@ -2902,18 +2883,6 @@ static void hotkey_tablet_mode_notify_change(void) "hotkey_tablet_mode"); } -/* sysfs hotkey report_mode -------------------------------------------- */ -static ssize_t hotkey_report_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%d\n", - (hotkey_report_mode != 0) ? hotkey_report_mode : 1); -} - -static struct device_attribute dev_attr_hotkey_report_mode = - __ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL); - /* sysfs wakeup reason (pollable) -------------------------------------- */ static ssize_t hotkey_wakeup_reason_show(struct device *dev, struct device_attribute *attr, @@ -2955,7 +2924,6 @@ static struct attribute *hotkey_attributes[] __initdata = { &dev_attr_hotkey_enable.attr, &dev_attr_hotkey_bios_enabled.attr, &dev_attr_hotkey_bios_mask.attr, - &dev_attr_hotkey_report_mode.attr, &dev_attr_hotkey_wakeup_reason.attr, &dev_attr_hotkey_wakeup_hotunplug_complete.attr, &dev_attr_hotkey_mask.attr, @@ -2979,7 +2947,7 @@ static void tpacpi_send_radiosw_update(void) * rfkill input events, or we will race the rfkill core input * handler. * - * tpacpi_inputdev_send_mutex works as a syncronization point + * tpacpi_inputdev_send_mutex works as a synchronization point * for the above. * * We optimize to avoid numerous calls to hotkey_get_wlsw. @@ -3027,8 +2995,6 @@ static void hotkey_exit(void) if (hotkey_dev_attributes) delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj); - kfree(hotkey_keycode_map); - dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY, "restoring original HKEY status and mask\n"); /* yes, there is a bitwise or below, we want the @@ -3036,8 +3002,7 @@ static void hotkey_exit(void) if (((tp_features.hotkey_mask && hotkey_mask_set(hotkey_orig_mask)) | hotkey_status_set(false)) != 0) - printk(TPACPI_ERR - "failed to restore hot key mask " + pr_err("failed to restore hot key mask " "to BIOS defaults\n"); } @@ -3079,6 +3044,9 @@ static const struct tpacpi_quirk tpacpi_hotkey_qtable[] __initconst = { TPACPI_Q_IBM('1', 'D', TPACPI_HK_Q_INIMASK), /* X22, X23, X24 */ }; +typedef u16 tpacpi_keymap_entry_t; +typedef tpacpi_keymap_entry_t tpacpi_keymap_t[TPACPI_HOTKEY_MAP_LEN]; + static int __init hotkey_init(struct ibm_init_struct *iibm) { /* Requirements for changing the default keymaps: @@ -3110,9 +3078,17 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) * If the above is too much to ask, don't change the keymap. * Ask the thinkpad-acpi maintainer to do it, instead. */ - static u16 ibm_keycode_map[] __initdata = { + + enum keymap_index { + TPACPI_KEYMAP_IBM_GENERIC = 0, + TPACPI_KEYMAP_LENOVO_GENERIC, + }; + + static const tpacpi_keymap_t tpacpi_keymaps[] __initconst = { + /* Generic keymap for IBM ThinkPads */ + [TPACPI_KEYMAP_IBM_GENERIC] = { /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */ - KEY_FN_F1, KEY_FN_F2, KEY_COFFEE, KEY_SLEEP, + KEY_FN_F1, KEY_BATTERY, KEY_COFFEE, KEY_SLEEP, KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8, KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND, @@ -3143,11 +3119,13 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* (assignments unknown, please report if found) */ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - }; - static u16 lenovo_keycode_map[] __initdata = { + }, + + /* Generic keymap for Lenovo ThinkPads */ + [TPACPI_KEYMAP_LENOVO_GENERIC] = { /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */ KEY_FN_F1, KEY_COFFEE, KEY_BATTERY, KEY_SLEEP, - KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8, + KEY_WLAN, KEY_CAMERA, KEY_SWITCHVIDEOMODE, KEY_FN_F8, KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND, /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */ @@ -3184,13 +3162,38 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */ /* (assignments unknown, please report if found) */ - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, - KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, + + /* + * The mic mute button only sends 0x1a. It does not + * automatically mute the mic or change the mute light. + */ + KEY_MICMUTE, /* 0x1a: Mic mute (since ?400 or so) */ + + /* (assignments unknown, please report if found) */ + KEY_UNKNOWN, + + /* Extra keys in use since the X240 / T440 / T540 */ + KEY_CONFIG, KEY_SEARCH, KEY_SCALE, KEY_COMPUTER, + }, }; -#define TPACPI_HOTKEY_MAP_LEN ARRAY_SIZE(ibm_keycode_map) -#define TPACPI_HOTKEY_MAP_SIZE sizeof(ibm_keycode_map) -#define TPACPI_HOTKEY_MAP_TYPESIZE sizeof(ibm_keycode_map[0]) + static const struct tpacpi_quirk tpacpi_keymap_qtable[] __initconst = { + /* Generic maps (fallback) */ + { + .vendor = PCI_VENDOR_ID_IBM, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, + .quirks = TPACPI_KEYMAP_IBM_GENERIC, + }, + { + .vendor = PCI_VENDOR_ID_LENOVO, + .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, + .quirks = TPACPI_KEYMAP_LENOVO_GENERIC, + }, + }; + +#define TPACPI_HOTKEY_MAP_SIZE sizeof(tpacpi_keymap_t) +#define TPACPI_HOTKEY_MAP_TYPESIZE sizeof(tpacpi_keymap_entry_t) int res, i; int status; @@ -3199,6 +3202,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) bool tabletsw_state = false; unsigned long quirks; + unsigned long keymap_id; vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "initializing hotkey subdriver\n"); @@ -3211,7 +3215,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) mutex_init(&hotkey_mutex); #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL - mutex_init(&hotkey_thread_mutex); mutex_init(&hotkey_thread_data_mutex); #endif @@ -3248,10 +3251,9 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) for HKEY interface version 0x100 */ if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { if ((hkeyv >> 8) != 1) { - printk(TPACPI_ERR "unknown version of the " - "HKEY interface: 0x%x\n", hkeyv); - printk(TPACPI_ERR "please report this to %s\n", - TPACPI_MAIL); + pr_err("unknown version of the HKEY interface: 0x%x\n", + hkeyv); + pr_err("please report this to %s\n", TPACPI_MAIL); } else { /* * MHKV 0x100 in A31, R40, R40e, @@ -3264,8 +3266,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* Paranoia check AND init hotkey_all_mask */ if (!acpi_evalf(hkey_handle, &hotkey_all_mask, "MHKA", "qd")) { - printk(TPACPI_ERR - "missing MHKA handler, " + pr_err("missing MHKA handler, " "please report this to %s\n", TPACPI_MAIL); /* Fallback: pre-init for FN+F3,F4,F12 */ @@ -3303,16 +3304,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (dbg_wlswemul) { tp_features.hotkey_wlsw = 1; radiosw_state = !!tpacpi_wlsw_emulstate; - printk(TPACPI_INFO - "radio switch emulation enabled\n"); + pr_info("radio switch emulation enabled\n"); } else #endif /* Not all thinkpads have a hardware radio switch */ if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) { tp_features.hotkey_wlsw = 1; radiosw_state = !!status; - printk(TPACPI_INFO - "radio switch found; radios are %s\n", + pr_info("radio switch found; radios are %s\n", enabled(status, 0)); } if (tp_features.hotkey_wlsw) @@ -3323,8 +3322,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) { tp_features.hotkey_tablet = 1; tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK); - printk(TPACPI_INFO - "possible tablet mode switch found; " + pr_info("possible tablet mode switch found; " "ThinkPad in %s mode\n", (tabletsw_state) ? "tablet" : "laptop"); res = add_to_attr_set(hotkey_dev_attributes, @@ -3339,27 +3337,22 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) goto err_exit; /* Set up key map */ - hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE, GFP_KERNEL); if (!hotkey_keycode_map) { - printk(TPACPI_ERR - "failed to allocate memory for key map\n"); + pr_err("failed to allocate memory for key map\n"); res = -ENOMEM; goto err_exit; } - if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) { - dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, - "using Lenovo default hot key map\n"); - memcpy(hotkey_keycode_map, &lenovo_keycode_map, - TPACPI_HOTKEY_MAP_SIZE); - } else { - dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, - "using IBM default hot key map\n"); - memcpy(hotkey_keycode_map, &ibm_keycode_map, - TPACPI_HOTKEY_MAP_SIZE); - } + keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable, + ARRAY_SIZE(tpacpi_keymap_qtable)); + BUG_ON(keymap_id >= ARRAY_SIZE(tpacpi_keymaps)); + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, + "using keymap number %lu\n", keymap_id); + + memcpy(hotkey_keycode_map, &tpacpi_keymaps[keymap_id], + TPACPI_HOTKEY_MAP_SIZE); input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN); tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE; @@ -3387,19 +3380,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) } /* Do not issue duplicate brightness change events to - * userspace */ - if (!tp_features.bright_acpimode) - /* update bright_acpimode... */ - tpacpi_check_std_acpi_brightness_support(); - - if (tp_features.bright_acpimode && acpi_video_backlight_support()) { - printk(TPACPI_INFO - "This ThinkPad has standard ACPI backlight " - "brightness control, supported by the ACPI " - "video driver\n"); - printk(TPACPI_NOTICE - "Disabling thinkpad-acpi brightness events " - "by default...\n"); + * userspace. tpacpi_detect_brightness_capabilities() must have + * been called before this point */ + if (acpi_video_backlight_support()) { + pr_info("This ThinkPad has standard ACPI backlight " + "brightness control, supported by the ACPI " + "video driver\n"); + pr_notice("Disabling thinkpad-acpi brightness events " + "by default...\n"); /* Disable brightness up/down on Lenovo thinkpads when * ACPI is handling them, otherwise it is plain impossible @@ -3441,11 +3429,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) "initial masks: user=0x%08x, fw=0x%08x, poll=0x%08x\n", hotkey_user_mask, hotkey_acpi_mask, hotkey_source_mask); - dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, - "legacy ibm/hotkey event reporting over procfs %s\n", - (hotkey_report_mode < 2) ? - "enabled" : "disabled"); - tpacpi_inputdev->open = &hotkey_inputdev_open; tpacpi_inputdev->close = &hotkey_inputdev_close; @@ -3460,6 +3443,106 @@ err_exit: return (res < 0)? res : 1; } +/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser + * mode, Web conference mode, Function mode and Lay-flat mode. + * We support Home mode and Function mode currently. + * + * Will consider support rest of modes in future. + * + */ +enum ADAPTIVE_KEY_MODE { + HOME_MODE, + WEB_BROWSER_MODE, + WEB_CONFERENCE_MODE, + FUNCTION_MODE, + LAYFLAT_MODE +}; + +const int adaptive_keyboard_modes[] = { + HOME_MODE, +/* WEB_BROWSER_MODE = 2, + WEB_CONFERENCE_MODE = 3, */ + FUNCTION_MODE +}; + +#define DFR_CHANGE_ROW 0x101 +#define DFR_SHOW_QUICKVIEW_ROW 0x102 + +/* press Fn key a while second, it will switch to Function Mode. Then + * release Fn key, previous mode be restored. + */ +static bool adaptive_keyboard_mode_is_saved; +static int adaptive_keyboard_prev_mode; + +static int adaptive_keyboard_get_next_mode(int mode) +{ + size_t i; + size_t max_mode = ARRAY_SIZE(adaptive_keyboard_modes) - 1; + + for (i = 0; i <= max_mode; i++) { + if (adaptive_keyboard_modes[i] == mode) + break; + } + + if (i >= max_mode) + i = 0; + else + i++; + + return adaptive_keyboard_modes[i]; +} + +static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) +{ + u32 current_mode = 0; + int new_mode = 0; + + switch (scancode) { + case DFR_CHANGE_ROW: + if (adaptive_keyboard_mode_is_saved) { + new_mode = adaptive_keyboard_prev_mode; + adaptive_keyboard_mode_is_saved = false; + } else { + if (!acpi_evalf( + hkey_handle, ¤t_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode\n"); + return false; + } else { + new_mode = adaptive_keyboard_get_next_mode( + current_mode); + } + } + + if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) { + pr_err("Cannot set adaptive keyboard mode\n"); + return false; + } + + return true; + + case DFR_SHOW_QUICKVIEW_ROW: + if (!acpi_evalf(hkey_handle, + &adaptive_keyboard_prev_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode\n"); + return false; + } else { + adaptive_keyboard_mode_is_saved = true; + + if (!acpi_evalf(hkey_handle, + NULL, "STRW", "vd", FUNCTION_MODE)) { + pr_err("Cannot set adaptive keyboard mode\n"); + return false; + } + } + return true; + + default: + return false; + } +} + static bool hotkey_notify_hotkey(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) @@ -3469,7 +3552,8 @@ static bool hotkey_notify_hotkey(const u32 hkey, *send_acpi_ev = true; *ignore_acpi_ev = false; - if (scancode > 0 && scancode < 0x21) { + /* HKEY event 0x1001 is scancode 0x00 */ + if (scancode > 0 && scancode <= TPACPI_HOTKEY_MAP_LEN) { scancode--; if (!(hotkey_source_mask & (1 << scancode))) { tpacpi_input_send_key_masked(scancode); @@ -3478,6 +3562,8 @@ static bool hotkey_notify_hotkey(const u32 hkey, *ignore_acpi_ev = true; } return true; + } else { + return adaptive_keyboard_hotkey_notify_hotkey(scancode); } return false; } @@ -3505,8 +3591,7 @@ static bool hotkey_notify_wakeup(const u32 hkey, case TP_HKEY_EV_WKUP_S3_BATLOW: /* Battery on critical low level/S3 */ case TP_HKEY_EV_WKUP_S4_BATLOW: /* Battery on critical low level/S4 */ - printk(TPACPI_ALERT - "EMERGENCY WAKEUP: battery almost empty\n"); + pr_alert("EMERGENCY WAKEUP: battery almost empty\n"); /* how to auto-heal: */ /* 2313: woke up from S3, go to S4/S5 */ /* 2413: woke up from S4, go to S5 */ @@ -3517,14 +3602,40 @@ static bool hotkey_notify_wakeup(const u32 hkey, } if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) { - printk(TPACPI_INFO - "woke up due to a hot-unplug " - "request...\n"); + pr_info("woke up due to a hot-unplug request...\n"); hotkey_wakeup_reason_notify_change(); } return true; } +static bool hotkey_notify_dockevent(const u32 hkey, + bool *send_acpi_ev, + bool *ignore_acpi_ev) +{ + /* 0x4000-0x4FFF: dock-related events */ + *send_acpi_ev = true; + *ignore_acpi_ev = false; + + switch (hkey) { + case TP_HKEY_EV_UNDOCK_ACK: + /* ACPI undock operation completed after wakeup */ + hotkey_autosleep_ack = 1; + pr_info("undocked\n"); + hotkey_wakeup_hotunplug_complete_notify_change(); + return true; + + case TP_HKEY_EV_HOTPLUG_DOCK: /* docked to port replicator */ + pr_info("docked into hotplug port replicator\n"); + return true; + case TP_HKEY_EV_HOTPLUG_UNDOCK: /* undocked from port replicator */ + pr_info("undocked from hotplug port replicator\n"); + return true; + + default: + return false; + } +} + static bool hotkey_notify_usrevent(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) @@ -3559,49 +3670,58 @@ static bool hotkey_notify_usrevent(const u32 hkey, static void thermal_dump_all_sensors(void); -static bool hotkey_notify_thermal(const u32 hkey, +static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { bool known = true; - /* 0x6000-0x6FFF: thermal alarms */ + /* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */ *send_acpi_ev = true; *ignore_acpi_ev = false; switch (hkey) { case TP_HKEY_EV_THM_TABLE_CHANGED: - printk(TPACPI_INFO - "EC reports that Thermal Table has changed\n"); + pr_info("EC reports that Thermal Table has changed\n"); /* recommended action: do nothing, we don't have * Lenovo ATM information */ return true; case TP_HKEY_EV_ALARM_BAT_HOT: - printk(TPACPI_CRIT - "THERMAL ALARM: battery is too hot!\n"); + pr_crit("THERMAL ALARM: battery is too hot!\n"); /* recommended action: warn user through gui */ break; case TP_HKEY_EV_ALARM_BAT_XHOT: - printk(TPACPI_ALERT - "THERMAL EMERGENCY: battery is extremely hot!\n"); + pr_alert("THERMAL EMERGENCY: battery is extremely hot!\n"); /* recommended action: immediate sleep/hibernate */ break; case TP_HKEY_EV_ALARM_SENSOR_HOT: - printk(TPACPI_CRIT - "THERMAL ALARM: " + pr_crit("THERMAL ALARM: " "a sensor reports something is too hot!\n"); /* recommended action: warn user through gui, that */ /* some internal component is too hot */ break; case TP_HKEY_EV_ALARM_SENSOR_XHOT: - printk(TPACPI_ALERT - "THERMAL EMERGENCY: " - "a sensor reports something is extremely hot!\n"); + pr_alert("THERMAL EMERGENCY: " + "a sensor reports something is extremely hot!\n"); /* recommended action: immediate sleep/hibernate */ break; + case TP_HKEY_EV_AC_CHANGED: + /* X120e, X121e, X220, X220i, X220t, X230, T420, T420s, W520: + * AC status changed; can be triggered by plugging or + * unplugging AC adapter, docking or undocking. */ + + /* fallthrough */ + + case TP_HKEY_EV_KEY_NUMLOCK: + case TP_HKEY_EV_KEY_FN: + /* key press events, we just ignore them as long as the EC + * is still reporting them in the normal keyboard stream */ + *send_acpi_ev = false; + *ignore_acpi_ev = true; + return true; + default: - printk(TPACPI_ALERT - "THERMAL ALERT: unknown thermal alarm received\n"); + pr_warn("unknown possible thermal alarm or keyboard event received\n"); known = false; } @@ -3618,8 +3738,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) bool known_ev; if (event != 0x80) { - printk(TPACPI_ERR - "unknown HKEY notification event %d\n", event); + pr_err("unknown HKEY notification event %d\n", event); /* forward it to userspace, maybe it knows how to handle it */ acpi_bus_generate_netlink_event( ibm->acpi->device->pnp.device_class, @@ -3630,7 +3749,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) while (1) { if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) { - printk(TPACPI_ERR "failed to retrieve HKEY event\n"); + pr_err("failed to retrieve HKEY event\n"); return; } @@ -3655,27 +3774,25 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) break; case 3: /* 0x3000-0x3FFF: bay-related wakeups */ - if (hkey == TP_HKEY_EV_BAYEJ_ACK) { + switch (hkey) { + case TP_HKEY_EV_BAYEJ_ACK: hotkey_autosleep_ack = 1; - printk(TPACPI_INFO - "bay ejected\n"); + pr_info("bay ejected\n"); hotkey_wakeup_hotunplug_complete_notify_change(); known_ev = true; - } else { + break; + case TP_HKEY_EV_OPTDRV_EJ: + /* FIXME: kick libata if SATA link offline */ + known_ev = true; + break; + default: known_ev = false; } break; case 4: - /* 0x4000-0x4FFF: dock-related wakeups */ - if (hkey == TP_HKEY_EV_UNDOCK_ACK) { - hotkey_autosleep_ack = 1; - printk(TPACPI_INFO - "undocked\n"); - hotkey_wakeup_hotunplug_complete_notify_change(); - known_ev = true; - } else { - known_ev = false; - } + /* 0x4000-0x4FFF: dock-related events */ + known_ev = hotkey_notify_dockevent(hkey, &send_acpi_ev, + &ignore_acpi_ev); break; case 5: /* 0x5000-0x5FFF: human interface helpers */ @@ -3683,8 +3800,9 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) &ignore_acpi_ev); break; case 6: - /* 0x6000-0x6FFF: thermal alarms */ - known_ev = hotkey_notify_thermal(hkey, &send_acpi_ev, + /* 0x6000-0x6FFF: thermal alarms/notices and + * keyboard events */ + known_ev = hotkey_notify_6xxx(hkey, &send_acpi_ev, &ignore_acpi_ev); break; case 7: @@ -3701,18 +3819,9 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) known_ev = false; } if (!known_ev) { - printk(TPACPI_NOTICE - "unhandled HKEY event 0x%04x\n", hkey); - printk(TPACPI_NOTICE - "please report the conditions when this " - "event happened to %s\n", TPACPI_MAIL); - } - - /* Legacy events */ - if (!ignore_acpi_ev && - (send_acpi_ev || hotkey_report_mode < 2)) { - acpi_bus_generate_proc_event(ibm->acpi->device, - event, hkey); + pr_notice("unhandled HKEY event 0x%04x\n", hkey); + pr_notice("please report the conditions when this " + "event happened to %s\n", TPACPI_MAIL); } /* netlink events */ @@ -3725,21 +3834,35 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) } } -static void hotkey_suspend(pm_message_t state) +static void hotkey_suspend(void) { + int hkeyv; + /* Do these on suspend, we get the events on early resume! */ hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE; hotkey_autosleep_ack = 0; + + /* save previous mode of adaptive keyboard of X1 Carbon */ + if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { + if ((hkeyv >> 8) == 2) { + if (!acpi_evalf(hkey_handle, + &adaptive_keyboard_prev_mode, + "GTRW", "dd", 0)) { + pr_err("Cannot read adaptive keyboard mode.\n"); + } + } + } } static void hotkey_resume(void) { + int hkeyv; + tpacpi_disable_brightness_delay(); if (hotkey_status_set(true) < 0 || hotkey_mask_set(hotkey_acpi_mask) < 0) - printk(TPACPI_ERR - "error while attempting to reset the event " + pr_err("error while attempting to reset the event " "firmware interface\n"); tpacpi_send_radiosw_update(); @@ -3747,6 +3870,18 @@ static void hotkey_resume(void) hotkey_wakeup_reason_notify_change(); hotkey_wakeup_hotunplug_complete_notify_change(); hotkey_poll_setup_safe(false); + + /* restore previous mode of adapive keyboard of X1 Carbon */ + if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { + if ((hkeyv >> 8) == 2) { + if (!acpi_evalf(hkey_handle, + NULL, + "STRW", "vd", + adaptive_keyboard_prev_mode)) { + pr_err("Cannot set adaptive keyboard mode.\n"); + } + } + } } /* procfs -------------------------------------------------------------- */ @@ -3784,14 +3919,12 @@ static void hotkey_enabledisable_warn(bool enable) { tpacpi_log_usertask("procfs hotkey enable/disable"); if (!WARN((tpacpi_lifecycle == TPACPI_LIFE_RUNNING || !enable), - TPACPI_WARN - "hotkey enable/disable functionality has been " - "removed from the driver. Hotkeys are always " - "enabled\n")) - printk(TPACPI_ERR - "Please remove the hotkey=enable module " - "parameter, it is deprecated. Hotkeys are always " - "enabled\n"); + pr_fmt("hotkey enable/disable functionality has been " + "removed from the driver. " + "Hotkeys are always enabled.\n"))) + pr_err("Please remove the hotkey=enable module " + "parameter, it is deprecated. " + "Hotkeys are always enabled.\n"); } static int hotkey_write(char *buf) @@ -3840,7 +3973,8 @@ errexit: } static const struct acpi_device_id ibm_htk_device_ids[] = { - {TPACPI_ACPI_HKEY_HID, 0}, + {TPACPI_ACPI_IBM_HKEY_HID, 0}, + {TPACPI_ACPI_LENOVO_HKEY_HID, 0}, {"", 0}, }; @@ -3870,7 +4004,7 @@ enum { TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */ TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */ TP_ACPI_BLUETOOTH_RESUMECTRL = 0x04, /* Bluetooth state at resume: - off / last state */ + 0 = disable, 1 = enable */ }; enum { @@ -3916,10 +4050,11 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state) } #endif - /* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */ - status = TP_ACPI_BLUETOOTH_RESUMECTRL; if (state == TPACPI_RFK_RADIO_ON) - status |= TP_ACPI_BLUETOOTH_RADIOSSW; + status = TP_ACPI_BLUETOOTH_RADIOSSW + | TP_ACPI_BLUETOOTH_RESUMECTRL; + else + status = 0; if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) return -EIO; @@ -3969,11 +4104,10 @@ static void bluetooth_shutdown(void) /* Order firmware to save current state to NVRAM */ if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd", TP_ACPI_BLTH_SAVE_STATE)) - printk(TPACPI_NOTICE - "failed to save bluetooth state to NVRAM\n"); + pr_notice("failed to save bluetooth state to NVRAM\n"); else vdbg_printk(TPACPI_DBG_RFKILL, - "bluestooth state saved to NVRAM\n"); + "bluetooth state saved to NVRAM\n"); } static void bluetooth_exit(void) @@ -4009,8 +4143,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_bluetoothemul) { tp_features.bluetooth = 1; - printk(TPACPI_INFO - "bluetooth switch emulation enabled\n"); + pr_info("bluetooth switch emulation enabled\n"); } else #endif if (tp_features.bluetooth && @@ -4070,7 +4203,7 @@ enum { TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */ TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */ TP_ACPI_WANCARD_RESUMECTRL = 0x04, /* Wan state at resume: - off / last state */ + 0 = disable, 1 = enable */ }; #define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw" @@ -4107,10 +4240,11 @@ static int wan_set_status(enum tpacpi_rfkill_state state) } #endif - /* We make sure to set TP_ACPI_WANCARD_RESUMECTRL */ - status = TP_ACPI_WANCARD_RESUMECTRL; if (state == TPACPI_RFK_RADIO_ON) - status |= TP_ACPI_WANCARD_RADIOSSW; + status = TP_ACPI_WANCARD_RADIOSSW + | TP_ACPI_WANCARD_RESUMECTRL; + else + status = 0; if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) return -EIO; @@ -4160,8 +4294,7 @@ static void wan_shutdown(void) /* Order firmware to save current state to NVRAM */ if (!acpi_evalf(NULL, NULL, "\\WGSV", "vd", TP_ACPI_WGSV_SAVE_STATE)) - printk(TPACPI_NOTICE - "failed to save WWAN state to NVRAM\n"); + pr_notice("failed to save WWAN state to NVRAM\n"); else vdbg_printk(TPACPI_DBG_RFKILL, "WWAN state saved to NVRAM\n"); @@ -4198,8 +4331,7 @@ static int __init wan_init(struct ibm_init_struct *iibm) #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_wwanemul) { tp_features.wan = 1; - printk(TPACPI_INFO - "wwan switch emulation enabled\n"); + pr_info("wwan switch emulation enabled\n"); } else #endif if (tp_features.wan && @@ -4339,8 +4471,7 @@ static int __init uwb_init(struct ibm_init_struct *iibm) #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_uwbemul) { tp_features.uwb = 1; - printk(TPACPI_INFO - "uwb switch emulation enabled\n"); + pr_info("uwb switch emulation enabled\n"); } else #endif if (tp_features.uwb && @@ -4401,6 +4532,15 @@ static int video_orig_autosw; static int video_autosw_get(void); static int video_autosw_set(int enable); +TPACPI_HANDLE(vid, root, + "\\_SB.PCI.AGP.VGA", /* 570 */ + "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */ + "\\_SB.PCI0.VID0", /* 770e */ + "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */ + "\\_SB.PCI0.AGP.VGA", /* X100e and a few others */ + "\\_SB.PCI0.AGP.VID", /* all others */ + ); /* R30, R31 */ + TPACPI_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ static int __init video_init(struct ibm_init_struct *iibm) @@ -4410,7 +4550,8 @@ static int __init video_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n"); TPACPI_ACPIHANDLE_INIT(vid); - TPACPI_ACPIHANDLE_INIT(vid2); + if (tpacpi_is_ibm()) + TPACPI_ACPIHANDLE_INIT(vid2); if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga) /* G41, assume IVGA doesn't change */ @@ -4419,10 +4560,12 @@ static int __init video_init(struct ibm_init_struct *iibm) if (!vid_handle) /* video switching not supported on R30, R31 */ video_supported = TPACPI_VIDEO_NONE; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) + else if (tpacpi_is_ibm() && + acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) /* 570 */ video_supported = TPACPI_VIDEO_570; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) + else if (tpacpi_is_ibm() && + acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) /* 600e/x, 770e, 770x */ video_supported = TPACPI_VIDEO_770; else @@ -4441,7 +4584,7 @@ static void video_exit(void) dbg_printk(TPACPI_DBG_EXIT, "restoring original video autoswitch mode\n"); if (video_autosw_set(video_orig_autosw)) - printk(TPACPI_ERR "error while trying to restore original " + pr_err("error while trying to restore original " "video autoswitch mode\n"); } @@ -4514,8 +4657,7 @@ static int video_outputsw_set(int status) res = acpi_evalf(vid_handle, NULL, "ASWT", "vdd", status * 0x100, 0); if (!autosw && video_autosw_set(autosw)) { - printk(TPACPI_ERR - "video auto-switch left enabled due to error\n"); + pr_err("video auto-switch left enabled due to error\n"); return -EIO; } break; @@ -4584,8 +4726,7 @@ static int video_outputsw_cycle(void) return -ENOSYS; } if (!autosw && video_autosw_set(autosw)) { - printk(TPACPI_ERR - "video auto-switch left enabled due to error\n"); + pr_err("video auto-switch left enabled due to error\n"); return -EIO; } @@ -4619,6 +4760,10 @@ static int video_read(struct seq_file *m) return 0; } + /* Even reads can crash X.org, so... */ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + status = video_outputsw_get(); if (status < 0) return status; @@ -4652,6 +4797,10 @@ static int video_write(char *buf) if (video_supported == TPACPI_VIDEO_NONE) return -ENODEV; + /* Even reads can crash X.org, let alone writes... */ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + enable = 0; disable = 0; @@ -4791,8 +4940,10 @@ static int __init light_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n"); - TPACPI_ACPIHANDLE_INIT(ledb); - TPACPI_ACPIHANDLE_INIT(lght); + if (tpacpi_is_ibm()) { + TPACPI_ACPIHANDLE_INIT(ledb); + TPACPI_ACPIHANDLE_INIT(lght); + } TPACPI_ACPIHANDLE_INIT(cmos); INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker); @@ -4828,8 +4979,7 @@ static int __init light_init(struct ibm_init_struct *iibm) static void light_exit(void) { led_classdev_unregister(&tpacpi_led_thinklight.led_classdev); - if (work_pending(&tpacpi_led_thinklight.work)) - flush_workqueue(tpacpi_wq); + flush_workqueue(tpacpi_wq); } static int light_read(struct seq_file *m) @@ -4987,11 +5137,7 @@ enum { /* For TPACPI_LED_OLD */ static enum led_access_mode led_supported; -TPACPI_HANDLE(led, ec, "SLED", /* 570 */ - "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, */ - /* T20-22, X20-21 */ - "LED", /* all others */ - ); /* R30, R31 */ +static acpi_handle led_handle; #define TPACPI_LED_NUMLEDS 16 static struct tpacpi_led_classdev *tpacpi_leds; @@ -5172,6 +5318,7 @@ static void led_exit(void) led_classdev_unregister(&tpacpi_leds[i].led_classdev); } + flush_workqueue(tpacpi_wq); kfree(tpacpi_leds); } @@ -5251,6 +5398,32 @@ static const struct tpacpi_quirk led_useful_qtable[] __initconst = { #undef TPACPI_LEDQ_IBM #undef TPACPI_LEDQ_LNV +static enum led_access_mode __init led_init_detect_mode(void) +{ + acpi_status status; + + if (tpacpi_is_ibm()) { + /* 570 */ + status = acpi_get_handle(ec_handle, "SLED", &led_handle); + if (ACPI_SUCCESS(status)) + return TPACPI_LED_570; + + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + status = acpi_get_handle(ec_handle, "SYSL", &led_handle); + if (ACPI_SUCCESS(status)) + return TPACPI_LED_OLD; + } + + /* most others */ + status = acpi_get_handle(ec_handle, "LED", &led_handle); + if (ACPI_SUCCESS(status)) + return TPACPI_LED_NEW; + + /* R30, R31, and unknown firmwares */ + led_handle = NULL; + return TPACPI_LED_NONE; +} + static int __init led_init(struct ibm_init_struct *iibm) { unsigned int i; @@ -5259,20 +5432,17 @@ static int __init led_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); - TPACPI_ACPIHANDLE_INIT(led); + led_supported = led_init_detect_mode(); - if (!led_handle) - /* led not supported on R30, R31 */ - led_supported = TPACPI_LED_NONE; - else if (strlencmp(led_path, "SLED") == 0) - /* 570 */ - led_supported = TPACPI_LED_570; - else if (strlencmp(led_path, "SYSL") == 0) - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - led_supported = TPACPI_LED_OLD; - else - /* all others */ - led_supported = TPACPI_LED_NEW; + if (led_supported != TPACPI_LED_NONE) { + useful_leds = tpacpi_check_quirks(led_useful_qtable, + ARRAY_SIZE(led_useful_qtable)); + + if (!useful_leds) { + led_handle = NULL; + led_supported = TPACPI_LED_NONE; + } + } vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n", str_supported(led_supported), led_supported); @@ -5283,14 +5453,13 @@ static int __init led_init(struct ibm_init_struct *iibm) tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS, GFP_KERNEL); if (!tpacpi_leds) { - printk(TPACPI_ERR "Out of memory for LED data\n"); + pr_err("Out of memory for LED data\n"); return -ENOMEM; } - useful_leds = tpacpi_check_quirks(led_useful_qtable, - ARRAY_SIZE(led_useful_qtable)); - for (i = 0; i < TPACPI_LED_NUMLEDS; i++) { + tpacpi_leds[i].led = -1; + if (!tpacpi_is_led_restricted(i) && test_bit(i, &useful_leds)) { rc = tpacpi_init_led(i); @@ -5302,9 +5471,8 @@ static int __init led_init(struct ibm_init_struct *iibm) } #ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS - printk(TPACPI_NOTICE - "warning: userspace override of important " - "firmware LEDs is enabled\n"); + pr_notice("warning: userspace override of important " + "firmware LEDs is enabled\n"); #endif return 0; } @@ -5349,9 +5517,13 @@ static int led_write(char *buf) return -ENODEV; while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15) + if (sscanf(cmd, "%d", &led) != 1) return -EINVAL; + if (led < 0 || led > (TPACPI_LED_NUMLEDS - 1) || + tpacpi_leds[led].led < 0) + return -ENODEV; + if (strstr(cmd, "off")) { s = TPACPI_LED_OFF; } else if (strstr(cmd, "on")) { @@ -5574,17 +5746,16 @@ static void thermal_dump_all_sensors(void) if (n <= 0) return; - printk(TPACPI_NOTICE - "temperatures (Celsius):"); + pr_notice("temperatures (Celsius):"); for (i = 0; i < n; i++) { if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA) - printk(KERN_CONT " %d", (int)(t.temp[i] / 1000)); + pr_cont(" %d", (int)(t.temp[i] / 1000)); else - printk(KERN_CONT " N/A"); + pr_cont(" N/A"); } - printk(KERN_CONT "\n"); + pr_cont("\n"); } /* sysfs temp##_input -------------------------------------------------- */ @@ -5704,14 +5875,12 @@ static int __init thermal_init(struct ibm_init_struct *iibm) if (ta1 == 0) { /* This is sheer paranoia, but we handle it anyway */ if (acpi_tmp7) { - printk(TPACPI_ERR - "ThinkPad ACPI EC access misbehaving, " + pr_err("ThinkPad ACPI EC access misbehaving, " "falling back to ACPI TMPx access " "mode\n"); thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07; } else { - printk(TPACPI_ERR - "ThinkPad ACPI EC access misbehaving, " + pr_err("ThinkPad ACPI EC access misbehaving, " "disabling thermal sensors access\n"); thermal_read_mode = TPACPI_THERMAL_NONE; } @@ -5721,11 +5890,12 @@ static int __init thermal_init(struct ibm_init_struct *iibm) TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8; } } else if (acpi_tmp7) { - if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { + if (tpacpi_is_ibm() && + acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { /* 600e/x, 770e, 770x */ thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT; } else { - /* Standard ACPI TMPx access, max 8 sensors */ + /* IBM/LENOVO DSDT EC.TMPx access, max 8 sensors */ thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07; } } else { @@ -5771,7 +5941,7 @@ static void thermal_exit(void) case TPACPI_THERMAL_ACPI_TMP07: case TPACPI_THERMAL_ACPI_UPDT: sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, - &thermal_temp_input16_group); + &thermal_temp_input8_group); break; case TPACPI_THERMAL_NONE: default: @@ -5807,75 +5977,6 @@ static struct ibm_struct thermal_driver_data = { }; /************************************************************************* - * EC Dump subdriver - */ - -static u8 ecdump_regs[256]; - -static int ecdump_read(struct seq_file *m) -{ - int i, j; - u8 v; - - seq_printf(m, "EC " - " +00 +01 +02 +03 +04 +05 +06 +07" - " +08 +09 +0a +0b +0c +0d +0e +0f\n"); - for (i = 0; i < 256; i += 16) { - seq_printf(m, "EC 0x%02x:", i); - for (j = 0; j < 16; j++) { - if (!acpi_ec_read(i + j, &v)) - break; - if (v != ecdump_regs[i + j]) - seq_printf(m, " *%02x", v); - else - seq_printf(m, " %02x", v); - ecdump_regs[i + j] = v; - } - seq_putc(m, '\n'); - if (j != 16) - break; - } - - /* These are way too dangerous to advertise openly... */ -#if 0 - seq_printf(m, "commands:\t0x<offset> 0x<value>" - " (<offset> is 00-ff, <value> is 00-ff)\n"); - seq_printf(m, "commands:\t0x<offset> <value> " - " (<offset> is 00-ff, <value> is 0-255)\n"); -#endif - return 0; -} - -static int ecdump_write(char *buf) -{ - char *cmd; - int i, v; - - while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) { - /* i and v set */ - } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) { - /* i and v set */ - } else - return -EINVAL; - if (i >= 0 && i < 256 && v >= 0 && v < 256) { - if (!acpi_ec_write(i, v)) - return -EIO; - } else - return -EINVAL; - } - - return 0; -} - -static struct ibm_struct ecdump_driver_data = { - .name = "ecdump", - .read = ecdump_read, - .write = ecdump_write, - .flags.experimental = 1, -}; - -/************************************************************************* * Backlight/brightness subdriver */ @@ -5934,7 +6035,7 @@ static unsigned int tpacpi_brightness_nvram_get(void) lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS) & TP_NVRAM_MASK_LEVEL_BRIGHTNESS) >> TP_NVRAM_POS_LEVEL_BRIGHTNESS; - lnvram &= (tp_features.bright_16levels) ? 0x0f : 0x07; + lnvram &= bright_maxlvl; return lnvram; } @@ -6043,8 +6144,7 @@ static int brightness_set(unsigned int value) { int res; - if (value > ((tp_features.bright_16levels)? 15 : 7) || - value < 0) + if (value > bright_maxlvl || value < 0) return -EINVAL; vdbg_printk(TPACPI_DBG_BRGHT, @@ -6112,7 +6212,7 @@ static void tpacpi_brightness_notify_change(void) BACKLIGHT_UPDATE_HOTKEY); } -static struct backlight_ops ibm_backlight_data = { +static const struct backlight_ops ibm_backlight_data = { .get_brightness = brightness_get, .update_status = brightness_update_status, }; @@ -6120,6 +6220,61 @@ static struct backlight_ops ibm_backlight_data = { /* --------------------------------------------------------------------- */ /* + * Call _BCL method of video device. On some ThinkPads this will + * switch the firmware to the ACPI brightness control mode. + */ + +static int __init tpacpi_query_bcl_levels(acpi_handle handle) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + struct acpi_device *device, *child; + int rc; + + if (acpi_bus_get_device(handle, &device)) + return 0; + + rc = 0; + list_for_each_entry(child, &device->children, node) { + acpi_status status = acpi_evaluate_object(child->handle, "_BCL", + NULL, &buffer); + if (ACPI_FAILURE(status)) + continue; + + obj = (union acpi_object *)buffer.pointer; + if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) { + pr_err("Unknown _BCL data, please report this to %s\n", + TPACPI_MAIL); + rc = 0; + } else { + rc = obj->package.count; + } + break; + } + + kfree(buffer.pointer); + return rc; +} + + +/* + * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map + */ +static unsigned int __init tpacpi_check_std_acpi_brightness_support(void) +{ + acpi_handle video_device; + int bcl_levels = 0; + + tpacpi_acpi_handle_locate("video", NULL, &video_device); + if (video_device) + bcl_levels = tpacpi_query_bcl_levels(video_device); + + tp_features.bright_acpimode = (bcl_levels > 0); + + return (bcl_levels > 2) ? (bcl_levels - 2) : 0; +} + +/* * 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. @@ -6133,13 +6288,13 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = { 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', 'R', TPACPI_BRGHT_Q_EC), /* R50,51 T40-42 */ 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', '6', TPACPI_BRGHT_Q_EC), /* R52 */ 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', 'U', TPACPI_BRGHT_Q_NOEC), /* X40 */ TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), @@ -6149,8 +6304,47 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = { TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC), /* X41 Tablet */ }; +/* + * Returns < 0 for error, otherwise sets tp_features.bright_* + * and bright_maxlvl. + */ +static void __init tpacpi_detect_brightness_capabilities(void) +{ + unsigned int b; + + vdbg_printk(TPACPI_DBG_INIT, + "detecting firmware brightness interface capabilities\n"); + + /* we could run a quirks check here (same table used by + * brightness_init) if needed */ + + /* + * We always attempt to detect acpi support, so as to switch + * Lenovo Vista BIOS to ACPI brightness mode even if we are not + * going to publish a backlight interface + */ + b = tpacpi_check_std_acpi_brightness_support(); + switch (b) { + case 16: + bright_maxlvl = 15; + pr_info("detected a 16-level brightness capable ThinkPad\n"); + break; + case 8: + case 0: + bright_maxlvl = 7; + pr_info("detected a 8-level brightness capable ThinkPad\n"); + break; + default: + pr_err("Unsupported brightness interface, " + "please contact %s\n", TPACPI_MAIL); + tp_features.bright_unkfw = 1; + bright_maxlvl = b - 1; + } +} + static int __init brightness_init(struct ibm_init_struct *iibm) { + struct backlight_properties props; int b; unsigned long quirks; @@ -6161,34 +6355,11 @@ static int __init brightness_init(struct ibm_init_struct *iibm) 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 - * going to publish a backlight interface - */ - b = tpacpi_check_std_acpi_brightness_support(); - if (b > 0) { - - if (acpi_video_backlight_support()) { - if (brightness_enable > 1) { - printk(TPACPI_NOTICE - "Standard ACPI backlight interface " - "available, not loading native one.\n"); - return 1; - } else if (brightness_enable == 1) { - printk(TPACPI_NOTICE - "Backlight control force enabled, even if standard " - "ACPI backlight interface is available\n"); - } - } else { - if (brightness_enable > 1) { - printk(TPACPI_NOTICE - "Standard ACPI backlight interface not " - "available, thinkpad_acpi native " - "brightness control enabled\n"); - } - } - } + /* tpacpi_detect_brightness_capabilities() must have run already */ + + /* if it is unknown, we don't handle it: it wouldn't be safe */ + if (tp_features.bright_unkfw) + return 1; if (!brightness_enable) { dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT, @@ -6197,14 +6368,22 @@ static int __init brightness_init(struct ibm_init_struct *iibm) return 1; } - if (b > 16) { - printk(TPACPI_ERR - "Unsupported brightness interface, " - "please contact %s\n", TPACPI_MAIL); - return 1; + if (acpi_video_backlight_support()) { + if (brightness_enable > 1) { + pr_info("Standard ACPI backlight interface " + "available, not loading native one\n"); + return 1; + } else if (brightness_enable == 1) { + pr_warn("Cannot enable backlight brightness support, " + "ACPI is already handling it. Refer to the " + "acpi_backlight kernel parameter.\n"); + return 1; + } + } else if (tp_features.bright_acpimode && brightness_enable > 1) { + pr_notice("Standard ACPI backlight interface not " + "available, thinkpad_acpi native " + "brightness control enabled\n"); } - if (b == 16) - tp_features.bright_16levels = 1; /* * Check for module parameter bogosity, note that we @@ -6228,7 +6407,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm) } /* Safety */ - if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM && + if (!tpacpi_is_ibm() && (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM || brightness_mode == TPACPI_BRGHT_MODE_EC)) return -EINVAL; @@ -6236,34 +6415,34 @@ static int __init brightness_init(struct ibm_init_struct *iibm) if (tpacpi_brightness_get_raw(&b) < 0) return 1; - if (tp_features.bright_16levels) - printk(TPACPI_INFO - "detected a 16-level brightness capable ThinkPad\n"); - - ibm_backlight_device = backlight_device_register( - TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL, - &ibm_backlight_data); + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = bright_maxlvl; + props.brightness = b & TP_EC_BACKLIGHT_LVLMSK; + ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME, + NULL, NULL, + &ibm_backlight_data, + &props); if (IS_ERR(ibm_backlight_device)) { int rc = PTR_ERR(ibm_backlight_device); ibm_backlight_device = NULL; - printk(TPACPI_ERR "Could not register backlight device\n"); + pr_err("Could not register backlight device\n"); return rc; } 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); + pr_notice("brightness: will use unverified default: " + "brightness_mode=%d\n", brightness_mode); + pr_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; + /* Added by mistake in early 2007. Probably useless, but it could + * be working around some unknown firmware problem where the value + * read at startup doesn't match the real hardware state... so leave + * it in place just in case */ backlight_update_status(ibm_backlight_device); vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT, @@ -6271,11 +6450,11 @@ static int __init brightness_init(struct ibm_init_struct *iibm) "as change notification\n"); tpacpi_hotkey_driver_mask_set(hotkey_driver_mask | TP_ACPI_HKEY_BRGHTUP_MASK - | TP_ACPI_HKEY_BRGHTDWN_MASK);; + | TP_ACPI_HKEY_BRGHTDWN_MASK); return 0; } -static void brightness_suspend(pm_message_t state) +static void brightness_suspend(void) { tpacpi_brightness_checkpoint_nvram(); } @@ -6306,9 +6485,8 @@ static int brightness_read(struct seq_file *m) } else { seq_printf(m, "level:\t\t%d\n", level); seq_printf(m, "commands:\tup, down\n"); - seq_printf(m, "commands:\tlevel <level>" - " (<level> is 0-%d)\n", - (tp_features.bright_16levels) ? 15 : 7); + seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n", + bright_maxlvl); } return 0; @@ -6319,7 +6497,6 @@ static int brightness_write(char *buf) int level; int rc; char *cmd; - int max_level = (tp_features.bright_16levels) ? 15 : 7; level = brightness_get(NULL); if (level < 0) @@ -6327,13 +6504,13 @@ static int brightness_write(char *buf) while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "up") == 0) { - if (level < max_level) + if (level < bright_maxlvl) level++; } else if (strlencmp(cmd, "down") == 0) { if (level > 0) level--; } else if (sscanf(cmd, "level %d", &level) == 1 && - level >= 0 && level <= max_level) { + level >= 0 && level <= bright_maxlvl) { /* new level set */ } else return -EINVAL; @@ -6390,9 +6567,14 @@ static struct ibm_struct brightness_driver_data = { #define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control" #define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME -static int alsa_index = ~((1 << (SNDRV_CARDS - 3)) - 1); /* last three slots */ +#if SNDRV_CARDS <= 32 +#define DEFAULT_ALSA_IDX ~((1 << (SNDRV_CARDS - 3)) - 1) +#else +#define DEFAULT_ALSA_IDX ~((1 << (32 - 3)) - 1) +#endif +static int alsa_index = DEFAULT_ALSA_IDX; /* last three slots */ static char *alsa_id = "ThinkPadEC"; -static int alsa_enable = SNDRV_DEFAULT_ENABLE1; +static bool alsa_enable = SNDRV_DEFAULT_ENABLE1; struct tpacpi_alsa_data { struct snd_card *card; @@ -6435,7 +6617,7 @@ static enum tpacpi_volume_access_mode volume_mode = TPACPI_VOL_MODE_MAX; static enum tpacpi_volume_capabilities volume_capabilities; -static int volume_control_allowed; +static bool volume_control_allowed; /* * Used to syncronize writers to TP_EC_AUDIO and @@ -6522,7 +6704,8 @@ static int volume_set_status(const u8 status) return volume_set_status_ec(status); } -static int volume_set_mute_ec(const bool mute) +/* returns < 0 on error, 0 on no change, 1 on change */ +static int __volume_set_mute_ec(const bool mute) { int rc; u8 s, n; @@ -6537,22 +6720,37 @@ static int volume_set_mute_ec(const bool mute) n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK : s & ~TP_EC_AUDIO_MUTESW_MSK; - if (n != s) + if (n != s) { rc = volume_set_status_ec(n); + if (!rc) + rc = 1; + } unlock: mutex_unlock(&volume_mutex); return rc; } +static int volume_alsa_set_mute(const bool mute) +{ + dbg_printk(TPACPI_DBG_MIXER, "ALSA: trying to %smute\n", + (mute) ? "" : "un"); + return __volume_set_mute_ec(mute); +} + static int volume_set_mute(const bool mute) { + int rc; + dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n", (mute) ? "" : "un"); - return volume_set_mute_ec(mute); + + rc = __volume_set_mute_ec(mute); + return (rc < 0) ? rc : 0; } -static int volume_set_volume_ec(const u8 vol) +/* returns < 0 on error, 0 on no change, 1 on change */ +static int __volume_set_volume_ec(const u8 vol) { int rc; u8 s, n; @@ -6569,19 +6767,22 @@ static int volume_set_volume_ec(const u8 vol) n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol; - if (n != s) + if (n != s) { rc = volume_set_status_ec(n); + if (!rc) + rc = 1; + } unlock: mutex_unlock(&volume_mutex); return rc; } -static int volume_set_volume(const u8 vol) +static int volume_alsa_set_volume(const u8 vol) { dbg_printk(TPACPI_DBG_MIXER, - "trying to set volume level to %hu\n", vol); - return volume_set_volume_ec(vol); + "ALSA: trying to set volume level to %hu\n", vol); + return __volume_set_volume_ec(vol); } static void volume_alsa_notify_change(void) @@ -6628,7 +6829,9 @@ static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol, static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - return volume_set_volume(ucontrol->value.integer.value[0]); + tpacpi_disclose_usertask("ALSA", "set volume to %ld\n", + ucontrol->value.integer.value[0]); + return volume_alsa_set_volume(ucontrol->value.integer.value[0]); } #define volume_alsa_mute_info snd_ctl_boolean_mono_info @@ -6651,10 +6854,13 @@ static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol, static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - return volume_set_mute(!ucontrol->value.integer.value[0]); + tpacpi_disclose_usertask("ALSA", "%smute\n", + ucontrol->value.integer.value[0] ? + "un" : ""); + return volume_alsa_set_mute(!ucontrol->value.integer.value[0]); } -static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = { +static struct snd_kcontrol_new volume_alsa_control_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Console Playback Volume", .index = 0, @@ -6663,7 +6869,7 @@ static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = { .get = volume_alsa_vol_get, }; -static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = { +static struct snd_kcontrol_new volume_alsa_control_mute = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Console Playback Switch", .index = 0, @@ -6672,7 +6878,7 @@ static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = { .get = volume_alsa_mute_get, }; -static void volume_suspend(pm_message_t state) +static void volume_suspend(void) { tpacpi_volume_checkpoint_nvram(); } @@ -6705,11 +6911,11 @@ static int __init volume_create_alsa_mixer(void) struct snd_kcontrol *ctl_mute; int rc; - rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE, - sizeof(struct tpacpi_alsa_data), &card); + rc = snd_card_new(&tpacpi_pdev->dev, + alsa_index, alsa_id, THIS_MODULE, + sizeof(struct tpacpi_alsa_data), &card); if (rc < 0 || !card) { - printk(TPACPI_ERR - "Failed to create ALSA card structures: %d\n", rc); + pr_err("Failed to create ALSA card structures: %d\n", rc); return 1; } @@ -6743,9 +6949,8 @@ static int __init volume_create_alsa_mixer(void) ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL); rc = snd_ctl_add(card, ctl_vol); if (rc < 0) { - printk(TPACPI_ERR - "Failed to create ALSA volume control: %d\n", - rc); + pr_err("Failed to create ALSA volume control: %d\n", + rc); goto err_exit; } data->ctl_vol_id = &ctl_vol->id; @@ -6754,16 +6959,14 @@ static int __init volume_create_alsa_mixer(void) ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL); rc = snd_ctl_add(card, ctl_mute); if (rc < 0) { - printk(TPACPI_ERR "Failed to create ALSA mute control: %d\n", - rc); + pr_err("Failed to create ALSA mute control: %d\n", rc); goto err_exit; } data->ctl_mute_id = &ctl_mute->id; - snd_card_set_dev(card, &tpacpi_pdev->dev); rc = snd_card_register(card); if (rc < 0) { - printk(TPACPI_ERR "Failed to register ALSA card: %d\n", rc); + pr_err("Failed to register ALSA card: %d\n", rc); goto err_exit; } @@ -6819,9 +7022,8 @@ static int __init volume_init(struct ibm_init_struct *iibm) return -EINVAL; if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) { - printk(TPACPI_ERR - "UCMS step volume mode not implemented, " - "please contact %s\n", TPACPI_MAIL); + pr_err("UCMS step volume mode not implemented, " + "please contact %s\n", TPACPI_MAIL); return 1; } @@ -6885,13 +7087,11 @@ static int __init volume_init(struct ibm_init_struct *iibm) rc = volume_create_alsa_mixer(); if (rc) { - printk(TPACPI_ERR - "Could not create the ALSA mixer interface\n"); + pr_err("Could not create the ALSA mixer interface\n"); return rc; } - printk(TPACPI_INFO - "Console audio control enabled, mode: %s\n", + pr_info("Console audio control enabled, mode: %s\n", (volume_control_allowed) ? "override (read/write)" : "monitor (read only)"); @@ -6953,12 +7153,10 @@ static int volume_write(char *buf) if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) { if (unlikely(!tp_warned.volume_ctrl_forbidden)) { tp_warned.volume_ctrl_forbidden = 1; - printk(TPACPI_NOTICE - "Console audio control in monitor mode, " - "changes are not allowed.\n"); - printk(TPACPI_NOTICE - "Use the volume_control=1 module parameter " - "to enable volume control\n"); + pr_notice("Console audio control in monitor mode, " + "changes are not allowed\n"); + pr_notice("Use the volume_control=1 module parameter " + "to enable volume control\n"); } return -EPERM; } @@ -7033,8 +7231,7 @@ static void inline volume_alsa_notify_change(void) static int __init volume_init(struct ibm_init_struct *iibm) { - printk(TPACPI_INFO - "volume: disabled as there is no ALSA support in this kernel\n"); + pr_info("volume: disabled as there is no ALSA support in this kernel\n"); return 1; } @@ -7070,7 +7267,7 @@ static struct ibm_struct volume_driver_data = { * * Fan speed changes of any sort (including those caused by the * disengaged mode) are usually done slowly by the firmware as the - * maximum ammount of fan duty cycle change per second seems to be + * maximum amount of fan duty cycle change per second seems to be * limited. * * Reading is not available if GFAN exists. @@ -7097,7 +7294,7 @@ static struct ibm_struct volume_driver_data = { * TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41) * * FIRMWARE BUG: on some models, EC 0x2f might not be initialized at - * boot. Apparently the EC does not intialize it, so unless ACPI DSDT + * boot. Apparently the EC does not initialize it, so unless ACPI DSDT * does so, its initial value is meaningless (0x07). * * For firmware bugs, refer to: @@ -7198,7 +7395,7 @@ enum fan_control_commands { * and also watchdog cmd */ }; -static int fan_control_allowed; +static bool fan_control_allowed; static enum fan_status_access_mode fan_status_access_mode; static enum fan_control_access_mode fan_control_access_mode; @@ -7241,9 +7438,8 @@ TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */ static void fan_quirk1_setup(void) { if (fan_control_initial_status == 0x07) { - printk(TPACPI_NOTICE - "fan_init: initial fan status is unknown, " - "assuming it is in auto mode\n"); + pr_notice("fan_init: initial fan status is unknown, " + "assuming it is in auto mode\n"); tp_features.fan_ctrl_status_undef = 1; } } @@ -7318,17 +7514,18 @@ static int fan_get_status(u8 *status) * Add TPACPI_FAN_RD_ACPI_FANS ? */ switch (fan_status_access_mode) { - case TPACPI_FAN_RD_ACPI_GFAN: + case TPACPI_FAN_RD_ACPI_GFAN: { /* 570, 600e/x, 770e, 770x */ + int res; - if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d"))) + if (unlikely(!acpi_evalf(gfan_handle, &res, NULL, "d"))) return -EIO; if (likely(status)) - *status = s & 0x07; + *status = res & 0x07; break; - + } case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ if (unlikely(!acpi_ec_read(fan_status_offset, &s))) @@ -7616,26 +7813,15 @@ static int fan_set_speed(int speed) static void fan_watchdog_reset(void) { - static int fan_watchdog_active; - if (fan_control_access_mode == TPACPI_FAN_WR_NONE) return; - if (fan_watchdog_active) - cancel_delayed_work(&fan_watchdog_task); - if (fan_watchdog_maxinterval > 0 && - tpacpi_lifecycle != TPACPI_LIFE_EXITING) { - fan_watchdog_active = 1; - if (!queue_delayed_work(tpacpi_wq, &fan_watchdog_task, - msecs_to_jiffies(fan_watchdog_maxinterval - * 1000))) { - printk(TPACPI_ERR - "failed to queue the fan watchdog, " - "watchdog will not trigger\n"); - } - } else - fan_watchdog_active = 0; + tpacpi_lifecycle != TPACPI_LIFE_EXITING) + mod_delayed_work(tpacpi_wq, &fan_watchdog_task, + msecs_to_jiffies(fan_watchdog_maxinterval * 1000)); + else + cancel_delayed_work(&fan_watchdog_task); } static void fan_watchdog_fire(struct work_struct *ignored) @@ -7645,11 +7831,11 @@ static void fan_watchdog_fire(struct work_struct *ignored) if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING) return; - printk(TPACPI_NOTICE "fan watchdog: enabling fan\n"); + pr_notice("fan watchdog: enabling fan\n"); rc = fan_set_enable(); if (rc < 0) { - printk(TPACPI_ERR "fan watchdog: error %d while enabling fan, " - "will try again later...\n", -rc); + pr_err("fan watchdog: error %d while enabling fan, " + "will try again later...\n", -rc); /* reschedule for later */ fan_watchdog_reset(); } @@ -7927,9 +8113,11 @@ static int __init fan_init(struct ibm_init_struct *iibm) tp_features.second_fan = 0; fan_control_desired_level = 7; - TPACPI_ACPIHANDLE_INIT(fans); - TPACPI_ACPIHANDLE_INIT(gfan); - TPACPI_ACPIHANDLE_INIT(sfan); + if (tpacpi_is_ibm()) { + TPACPI_ACPIHANDLE_INIT(fans); + TPACPI_ACPIHANDLE_INIT(gfan); + TPACPI_ACPIHANDLE_INIT(sfan); + } quirks = tpacpi_check_quirks(fan_quirk_table, ARRAY_SIZE(fan_quirk_table)); @@ -7951,8 +8139,7 @@ static int __init fan_init(struct ibm_init_struct *iibm) "secondary fan support enabled\n"); } } else { - printk(TPACPI_ERR - "ThinkPad ACPI EC access misbehaving, " + pr_err("ThinkPad ACPI EC access misbehaving, " "fan status and control unavailable\n"); return 1; } @@ -8041,7 +8228,7 @@ static void fan_exit(void) flush_workqueue(tpacpi_wq); } -static void fan_suspend(pm_message_t state) +static void fan_suspend(void) { int rc; @@ -8052,9 +8239,8 @@ static void fan_suspend(pm_message_t state) fan_control_resume_level = 0; rc = fan_get_status_safe(&fan_control_resume_level); if (rc < 0) - printk(TPACPI_NOTICE - "failed to read fan level for later " - "restore during resume: %d\n", rc); + pr_notice("failed to read fan level for later " + "restore during resume: %d\n", rc); /* if it is undefined, don't attempt to restore it. * KEEP THIS LAST */ @@ -8109,13 +8295,11 @@ static void fan_resume(void) return; } if (do_set) { - printk(TPACPI_NOTICE - "restoring fan level to 0x%02x\n", - fan_control_resume_level); + pr_notice("restoring fan level to 0x%02x\n", + fan_control_resume_level); rc = fan_set_level_safe(fan_control_resume_level); if (rc < 0) - printk(TPACPI_NOTICE - "failed to restore fan level: %d\n", rc); + pr_notice("failed to restore fan level: %d\n", rc); } } @@ -8207,8 +8391,8 @@ static int fan_write_cmd_level(const char *cmd, int *rc) *rc = fan_set_level_safe(level); if (*rc == -ENXIO) - printk(TPACPI_ERR "level command accepted for unsupported " - "access mode %d", fan_control_access_mode); + pr_err("level command accepted for unsupported access mode %d\n", + fan_control_access_mode); else if (!*rc) tpacpi_disclose_usertask("procfs fan", "set level to %d\n", level); @@ -8223,8 +8407,8 @@ static int fan_write_cmd_enable(const char *cmd, int *rc) *rc = fan_set_enable(); if (*rc == -ENXIO) - printk(TPACPI_ERR "enable command accepted for unsupported " - "access mode %d", fan_control_access_mode); + pr_err("enable command accepted for unsupported access mode %d\n", + fan_control_access_mode); else if (!*rc) tpacpi_disclose_usertask("procfs fan", "enable\n"); @@ -8238,8 +8422,8 @@ static int fan_write_cmd_disable(const char *cmd, int *rc) *rc = fan_set_disable(); if (*rc == -ENXIO) - printk(TPACPI_ERR "disable command accepted for unsupported " - "access mode %d", fan_control_access_mode); + pr_err("disable command accepted for unsupported access mode %d\n", + fan_control_access_mode); else if (!*rc) tpacpi_disclose_usertask("procfs fan", "disable\n"); @@ -8258,8 +8442,8 @@ static int fan_write_cmd_speed(const char *cmd, int *rc) *rc = fan_set_speed(speed); if (*rc == -ENXIO) - printk(TPACPI_ERR "speed command accepted for unsupported " - "access mode %d", fan_control_access_mode); + pr_err("speed command accepted for unsupported access mode %d\n", + fan_control_access_mode); else if (!*rc) tpacpi_disclose_usertask("procfs fan", "set speed to %d\n", speed); @@ -8318,6 +8502,103 @@ static struct ibm_struct fan_driver_data = { .resume = fan_resume, }; +/************************************************************************* + * Mute LED subdriver + */ + + +struct tp_led_table { + acpi_string name; + int on_value; + int off_value; + int state; +}; + +static struct tp_led_table led_tables[] = { + [TPACPI_LED_MUTE] = { + .name = "SSMS", + .on_value = 1, + .off_value = 0, + }, + [TPACPI_LED_MICMUTE] = { + .name = "MMTS", + .on_value = 2, + .off_value = 0, + }, +}; + +static int mute_led_on_off(struct tp_led_table *t, bool state) +{ + acpi_handle temp; + int output; + + if (!ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) { + pr_warn("Thinkpad ACPI has no %s interface.\n", t->name); + return -EIO; + } + + if (!acpi_evalf(hkey_handle, &output, t->name, "dd", + state ? t->on_value : t->off_value)) + return -EIO; + + t->state = state; + return state; +} + +int tpacpi_led_set(int whichled, bool on) +{ + struct tp_led_table *t; + + if (whichled < 0 || whichled >= TPACPI_LED_MAX) + return -EINVAL; + + t = &led_tables[whichled]; + if (t->state < 0 || t->state == on) + return t->state; + return mute_led_on_off(t, on); +} +EXPORT_SYMBOL_GPL(tpacpi_led_set); + +static int mute_led_init(struct ibm_init_struct *iibm) +{ + acpi_handle temp; + int i; + + for (i = 0; i < TPACPI_LED_MAX; i++) { + struct tp_led_table *t = &led_tables[i]; + if (ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) + mute_led_on_off(t, false); + else + t->state = -ENODEV; + } + return 0; +} + +static void mute_led_exit(void) +{ + int i; + + for (i = 0; i < TPACPI_LED_MAX; i++) + tpacpi_led_set(i, false); +} + +static void mute_led_resume(void) +{ + int i; + + for (i = 0; i < TPACPI_LED_MAX; i++) { + struct tp_led_table *t = &led_tables[i]; + if (t->state >= 0) + mute_led_on_off(t, t->state); + } +} + +static struct ibm_struct mute_led_driver_data = { + .name = "mute_led", + .exit = mute_led_exit, + .resume = mute_led_resume, +}; + /**************************************************************************** **************************************************************************** * @@ -8374,7 +8655,7 @@ static struct proc_dir_entry *proc_dir; * Module and infrastructure proble, init and exit handling */ -static int force_load; +static bool force_load; #ifdef CONFIG_THINKPAD_ACPI_DEBUG static const char * __init str_supported(int is_supported) @@ -8399,7 +8680,6 @@ static void ibm_exit(struct ibm_struct *ibm) ibm->acpi->type, dispatch_acpi_notify); ibm->flags.acpi_notify_installed = 0; - ibm->flags.acpi_notify_installed = 0; } if (ibm->flags.proc_created) { @@ -8463,8 +8743,8 @@ static int __init ibm_init(struct ibm_init_struct *iibm) if (ibm->acpi->notify) { ret = setup_acpi_notify(ibm); if (ret == -ENODEV) { - printk(TPACPI_NOTICE "disabling subdriver %s\n", - ibm->name); + pr_notice("disabling subdriver %s\n", + ibm->name); ret = 0; goto err_out; } @@ -8477,16 +8757,16 @@ static int __init ibm_init(struct ibm_init_struct *iibm) "%s installed\n", ibm->name); if (ibm->read) { - mode_t mode; + umode_t mode = iibm->base_procfs_mode; - mode = S_IRUGO; + if (!mode) + mode = S_IRUGO; if (ibm->write) mode |= S_IWUSR; entry = proc_create_data(ibm->name, mode, proc_dir, &dispatch_proc_fops, ibm); if (!entry) { - printk(TPACPI_ERR "unable to create proc entry %s\n", - ibm->name); + pr_err("unable to create proc entry %s\n", ibm->name); ret = -ENODEV; goto err_out; } @@ -8520,10 +8800,10 @@ static bool __pure __init tpacpi_is_valid_fw_id(const char* const s, return s && strlen(s) >= 8 && tpacpi_is_fw_digit(s[0]) && tpacpi_is_fw_digit(s[1]) && - s[2] == t && s[3] == 'T' && + s[2] == t && + (s[3] == 'T' || s[3] == 'N') && tpacpi_is_fw_digit(s[4]) && - tpacpi_is_fw_digit(s[5]) && - s[6] == 'W' && s[7] == 'W'; + tpacpi_is_fw_digit(s[5]); } /* returns 0 - probe ok, or < 0 - probe error. @@ -8554,7 +8834,8 @@ static int __must_check __init get_thinkpad_model_data( return -ENOMEM; /* Really ancient ThinkPad 240X will fail this, which is fine */ - if (!tpacpi_is_valid_fw_id(tp->bios_version_str, 'E')) + if (!(tpacpi_is_valid_fw_id(tp->bios_version_str, 'E') || + tpacpi_is_valid_fw_id(tp->bios_version_str, 'C'))) return 0; tp->bios_model = tp->bios_version_str[0] @@ -8586,23 +8867,28 @@ static int __must_check __init get_thinkpad_model_data( tp->ec_release = (ec_fw_string[4] << 8) | ec_fw_string[5]; } else { - printk(TPACPI_NOTICE - "ThinkPad firmware release %s " - "doesn't match the known patterns\n", - ec_fw_string); - printk(TPACPI_NOTICE - "please report this to %s\n", - TPACPI_MAIL); + pr_notice("ThinkPad firmware release %s " + "doesn't match the known patterns\n", + ec_fw_string); + pr_notice("please report this to %s\n", + TPACPI_MAIL); } break; } } s = dmi_get_system_info(DMI_PRODUCT_VERSION); - if (s && !strnicmp(s, "ThinkPad", 8)) { + if (s && !(strnicmp(s, "ThinkPad", 8) && strnicmp(s, "Lenovo", 6))) { tp->model_str = kstrdup(s, GFP_KERNEL); if (!tp->model_str) return -ENOMEM; + } else { + s = dmi_get_system_info(DMI_BIOS_VENDOR); + if (s && !(strnicmp(s, "Lenovo", 6))) { + tp->model_str = kstrdup(s, GFP_KERNEL); + if (!tp->model_str) + return -ENOMEM; + } } s = dmi_get_system_info(DMI_PRODUCT_NAME); @@ -8620,6 +8906,10 @@ static int __init probe_for_thinkpad(void) if (acpi_disabled) return -ENODEV; + /* It would be dangerous to run the driver in this case */ + if (!tpacpi_is_ibm() && !tpacpi_is_lenovo()) + return -ENODEV; + /* * Non-ancient models have better DMI tagging, but very old models * don't. tpacpi_is_fw_known() is a cheat to help in that case. @@ -8628,12 +8918,11 @@ static int __init probe_for_thinkpad(void) (thinkpad_id.ec_model != 0) || tpacpi_is_fw_known(); - /* ec is required because many other handles are relative to it */ - TPACPI_ACPIHANDLE_INIT(ec); + /* The EC handler is required */ + tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle); if (!ec_handle) { if (is_thinkpad) - printk(TPACPI_ERR - "Not yet supported ThinkPad detected!\n"); + pr_err("Not yet supported ThinkPad detected!\n"); return -ENODEV; } @@ -8643,12 +8932,34 @@ static int __init probe_for_thinkpad(void) return 0; } +static void __init thinkpad_acpi_init_banner(void) +{ + pr_info("%s v%s\n", TPACPI_DESC, TPACPI_VERSION); + pr_info("%s\n", TPACPI_URL); + + pr_info("ThinkPad BIOS %s, EC %s\n", + (thinkpad_id.bios_version_str) ? + thinkpad_id.bios_version_str : "unknown", + (thinkpad_id.ec_version_str) ? + thinkpad_id.ec_version_str : "unknown"); + + BUG_ON(!thinkpad_id.vendor); + + if (thinkpad_id.model_str) + pr_info("%s %s, model %s\n", + (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ? + "IBM" : ((thinkpad_id.vendor == + PCI_VENDOR_ID_LENOVO) ? + "Lenovo" : "Unknown vendor"), + thinkpad_id.model_str, + (thinkpad_id.nummodel_str) ? + thinkpad_id.nummodel_str : "unknown"); +} /* Module init, exit, parameters */ static struct ibm_init_struct ibms_init[] __initdata = { { - .init = thinkpad_acpi_driver_init, .data = &thinkpad_acpi_driver_data, }, { @@ -8670,6 +8981,7 @@ static struct ibm_init_struct ibms_init[] __initdata = { #ifdef CONFIG_THINKPAD_ACPI_VIDEO { .init = video_init, + .base_procfs_mode = S_IRUSR, .data = &video_driver_data, }, #endif @@ -8694,9 +9006,6 @@ static struct ibm_init_struct ibms_init[] __initdata = { .data = &thermal_driver_data, }, { - .data = &ecdump_driver_data, - }, - { .init = brightness_init, .data = &brightness_driver_data, }, @@ -8708,6 +9017,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = fan_init, .data = &fan_driver_data, }, + { + .init = mute_led_init, + .data = &mute_led_driver_data, + }, }; static int __init set_ibm_param(const char *val, struct kernel_param *kp) @@ -8762,11 +9075,6 @@ module_param(brightness_enable, uint, 0444); MODULE_PARM_DESC(brightness_enable, "Enables backlight control when 1, disables when 0"); -module_param(hotkey_report_mode, uint, 0444); -MODULE_PARM_DESC(hotkey_report_mode, - "used for backwards compatibility with userspace, " - "see documentation"); - #ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT module_param_named(volume_mode, volume_mode, uint, 0444); MODULE_PARM_DESC(volume_mode, @@ -8804,7 +9112,6 @@ TPACPI_PARAM(light); TPACPI_PARAM(cmos); TPACPI_PARAM(led); TPACPI_PARAM(beep); -TPACPI_PARAM(ecdump); TPACPI_PARAM(brightness); TPACPI_PARAM(volume); TPACPI_PARAM(fan); @@ -8854,6 +9161,7 @@ static void thinkpad_acpi_module_exit(void) input_unregister_device(tpacpi_inputdev); else input_free_device(tpacpi_inputdev); + kfree(hotkey_keycode_map); } if (tpacpi_hwmon) @@ -8887,6 +9195,7 @@ static void thinkpad_acpi_module_exit(void) kfree(thinkpad_id.bios_version_str); kfree(thinkpad_id.ec_version_str); kfree(thinkpad_id.model_str); + kfree(thinkpad_id.nummodel_str); } @@ -8896,16 +9205,11 @@ static int __init thinkpad_acpi_module_init(void) tpacpi_lifecycle = TPACPI_LIFE_INIT; - /* Parameter checking */ - if (hotkey_report_mode > 2) - return -EINVAL; - /* Driver-level probe */ ret = get_thinkpad_model_data(&thinkpad_id); if (ret) { - printk(TPACPI_ERR - "unable to get DMI data: %d\n", ret); + pr_err("unable to get DMI data: %d\n", ret); thinkpad_acpi_module_exit(); return ret; } @@ -8917,6 +9221,9 @@ static int __init thinkpad_acpi_module_init(void) /* Driver initialization */ + thinkpad_acpi_init_banner(); + tpacpi_check_outdated_fw(); + TPACPI_ACPIHANDLE_INIT(ecrd); TPACPI_ACPIHANDLE_INIT(ecwr); @@ -8928,16 +9235,14 @@ static int __init thinkpad_acpi_module_init(void) proc_dir = proc_mkdir(TPACPI_PROC_DIR, acpi_root_dir); if (!proc_dir) { - printk(TPACPI_ERR - "unable to create proc dir " TPACPI_PROC_DIR); + pr_err("unable to create proc dir " TPACPI_PROC_DIR "\n"); thinkpad_acpi_module_exit(); return -ENODEV; } ret = platform_driver_register(&tpacpi_pdriver); if (ret) { - printk(TPACPI_ERR - "unable to register main platform driver\n"); + pr_err("unable to register main platform driver\n"); thinkpad_acpi_module_exit(); return ret; } @@ -8945,8 +9250,7 @@ static int __init thinkpad_acpi_module_init(void) ret = platform_driver_register(&tpacpi_hwmon_pdriver); if (ret) { - printk(TPACPI_ERR - "unable to register hwmon platform driver\n"); + pr_err("unable to register hwmon platform driver\n"); thinkpad_acpi_module_exit(); return ret; } @@ -8959,8 +9263,7 @@ static int __init thinkpad_acpi_module_init(void) &tpacpi_hwmon_pdriver.driver); } if (ret) { - printk(TPACPI_ERR - "unable to create sysfs driver attributes\n"); + pr_err("unable to create sysfs driver attributes\n"); thinkpad_acpi_module_exit(); return ret; } @@ -8973,7 +9276,7 @@ static int __init thinkpad_acpi_module_init(void) if (IS_ERR(tpacpi_pdev)) { ret = PTR_ERR(tpacpi_pdev); tpacpi_pdev = NULL; - printk(TPACPI_ERR "unable to register platform device\n"); + pr_err("unable to register platform device\n"); thinkpad_acpi_module_exit(); return ret; } @@ -8983,16 +9286,14 @@ static int __init thinkpad_acpi_module_init(void) if (IS_ERR(tpacpi_sensors_pdev)) { ret = PTR_ERR(tpacpi_sensors_pdev); tpacpi_sensors_pdev = NULL; - printk(TPACPI_ERR - "unable to register hwmon platform device\n"); + pr_err("unable to register hwmon platform device\n"); thinkpad_acpi_module_exit(); return ret; } ret = device_create_file(&tpacpi_sensors_pdev->dev, &dev_attr_thinkpad_acpi_pdev_name); if (ret) { - printk(TPACPI_ERR - "unable to create sysfs hwmon device attributes\n"); + pr_err("unable to create sysfs hwmon device attributes\n"); thinkpad_acpi_module_exit(); return ret; } @@ -9001,14 +9302,13 @@ static int __init thinkpad_acpi_module_init(void) if (IS_ERR(tpacpi_hwmon)) { ret = PTR_ERR(tpacpi_hwmon); tpacpi_hwmon = NULL; - printk(TPACPI_ERR "unable to register hwmon device\n"); + pr_err("unable to register hwmon device\n"); thinkpad_acpi_module_exit(); return ret; } mutex_init(&tpacpi_inputdev_send_mutex); tpacpi_inputdev = input_allocate_device(); if (!tpacpi_inputdev) { - printk(TPACPI_ERR "unable to allocate input device\n"); thinkpad_acpi_module_exit(); return -ENOMEM; } else { @@ -9016,13 +9316,16 @@ static int __init thinkpad_acpi_module_init(void) tpacpi_inputdev->name = "ThinkPad Extra Buttons"; tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0"; tpacpi_inputdev->id.bustype = BUS_HOST; - tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ? - thinkpad_id.vendor : - PCI_VENDOR_ID_IBM; + tpacpi_inputdev->id.vendor = thinkpad_id.vendor; tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT; tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION; tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev; } + + /* Init subdriver dependencies */ + tpacpi_detect_brightness_capabilities(); + + /* Init subdrivers */ for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { ret = ibm_init(&ibms_init[i]); if (ret >= 0 && *ibms_init[i].param) @@ -9032,16 +9335,18 @@ static int __init thinkpad_acpi_module_init(void) return ret; } } + + tpacpi_lifecycle = TPACPI_LIFE_RUNNING; + ret = input_register_device(tpacpi_inputdev); if (ret < 0) { - printk(TPACPI_ERR "unable to register input device\n"); + pr_err("unable to register input device\n"); thinkpad_acpi_module_exit(); return ret; } else { tp_features.input_device_registered = 1; } - tpacpi_lifecycle = TPACPI_LIFE_RUNNING; return 0; } diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index 02f3d4e9e66..e597de05e6c 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -16,8 +16,10 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> +#include <linux/slab.h> #include <linux/acpi.h> #include <linux/input.h> +#include <linux/input/sparse-keymap.h> #define ACPI_TOPSTAR_CLASS "topstar" @@ -25,52 +27,38 @@ struct topstar_hkey { struct input_dev *inputdev; }; -struct tps_key_entry { - u8 code; - u16 keycode; -}; - -static struct tps_key_entry topstar_keymap[] = { - { 0x80, KEY_BRIGHTNESSUP }, - { 0x81, KEY_BRIGHTNESSDOWN }, - { 0x83, KEY_VOLUMEUP }, - { 0x84, KEY_VOLUMEDOWN }, - { 0x85, KEY_MUTE }, - { 0x86, KEY_SWITCHVIDEOMODE }, - { 0x87, KEY_F13 }, /* touchpad enable/disable key */ - { 0x88, KEY_WLAN }, - { 0x8a, KEY_WWW }, - { 0x8b, KEY_MAIL }, - { 0x8c, KEY_MEDIA }, - { 0x96, KEY_F14 }, /* G key? */ - { } -}; - -static struct tps_key_entry *tps_get_key_by_scancode(int code) -{ - struct tps_key_entry *key; - - for (key = topstar_keymap; key->code; key++) - if (code == key->code) - return key; - - return NULL; -} +static const struct key_entry topstar_keymap[] = { + { KE_KEY, 0x80, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x83, { KEY_VOLUMEUP } }, + { KE_KEY, 0x84, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0x85, { KEY_MUTE } }, + { KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */ + { KE_KEY, 0x88, { KEY_WLAN } }, + { KE_KEY, 0x8a, { KEY_WWW } }, + { KE_KEY, 0x8b, { KEY_MAIL } }, + { KE_KEY, 0x8c, { KEY_MEDIA } }, -static struct tps_key_entry *tps_get_key_by_keycode(int code) -{ - struct tps_key_entry *key; + /* Known non hotkey events don't handled or that we don't care yet */ + { KE_IGNORE, 0x82, }, /* backlight event */ + { KE_IGNORE, 0x8e, }, + { KE_IGNORE, 0x8f, }, + { KE_IGNORE, 0x90, }, - for (key = topstar_keymap; key->code; key++) - if (code == key->keycode) - return key; + /* + * 'G key' generate two event codes, convert to only + * one event/key code for now, consider replacing by + * a switch (3G switch - SW_3G?) + */ + { KE_KEY, 0x96, { KEY_F14 } }, + { KE_KEY, 0x97, { KEY_F14 } }, - return NULL; -} + { KE_END, 0 } +}; static void acpi_topstar_notify(struct acpi_device *device, u32 event) { - struct tps_key_entry *key; static bool dup_evnt[2]; bool *dup; struct topstar_hkey *hkey = acpi_driver_data(device); @@ -85,39 +73,16 @@ static void acpi_topstar_notify(struct acpi_device *device, u32 event) *dup = true; } - /* - * 'G key' generate two event codes, convert to only - * one event/key code for now (3G switch?) - */ - if (event == 0x97) - event = 0x96; - - key = tps_get_key_by_scancode(event); - if (key) { - input_report_key(hkey->inputdev, key->keycode, 1); - input_sync(hkey->inputdev); - input_report_key(hkey->inputdev, key->keycode, 0); - input_sync(hkey->inputdev); - return; - } - - /* Known non hotkey events don't handled or that we don't care yet */ - if (event == 0x8e || event == 0x8f || event == 0x90) - return; - - pr_info("unknown event = 0x%02x\n", event); + if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true)) + pr_info("unknown event = 0x%02x\n", event); } static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) { acpi_status status; - union acpi_object fncx_params[1] = { - { .type = ACPI_TYPE_INTEGER } - }; - struct acpi_object_list fncx_arg_list = { 1, &fncx_params[0] }; - fncx_params[0].integer.value = state ? 0x86 : 0x87; - status = acpi_evaluate_object(device->handle, "FNCX", &fncx_arg_list, NULL); + status = acpi_execute_simple_method(device->handle, "FNCX", + state ? 0x86 : 0x87); if (ACPI_FAILURE(status)) { pr_err("Unable to switch FNCX notifications\n"); return -ENODEV; @@ -126,63 +91,39 @@ static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) return 0; } -static int topstar_getkeycode(struct input_dev *dev, int scancode, int *keycode) -{ - struct tps_key_entry *key = tps_get_key_by_scancode(scancode); - - if (!key) - return -EINVAL; - - *keycode = key->keycode; - return 0; -} - -static int topstar_setkeycode(struct input_dev *dev, int scancode, int keycode) +static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) { - struct tps_key_entry *key; - int old_keycode; - - if (keycode < 0 || keycode > KEY_MAX) - return -EINVAL; - - key = tps_get_key_by_scancode(scancode); + struct input_dev *input; + int error; - if (!key) - return -EINVAL; - - old_keycode = key->keycode; - key->keycode = keycode; - set_bit(keycode, dev->keybit); - if (!tps_get_key_by_keycode(old_keycode)) - clear_bit(old_keycode, dev->keybit); - return 0; -} + input = input_allocate_device(); + if (!input) + return -ENOMEM; -static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) -{ - struct tps_key_entry *key; + input->name = "Topstar Laptop extra buttons"; + input->phys = "topstar/input0"; + input->id.bustype = BUS_HOST; - hkey->inputdev = input_allocate_device(); - if (!hkey->inputdev) { - pr_err("Unable to allocate input device\n"); - return -ENODEV; - } - hkey->inputdev->name = "Topstar Laptop extra buttons"; - hkey->inputdev->phys = "topstar/input0"; - hkey->inputdev->id.bustype = BUS_HOST; - hkey->inputdev->getkeycode = topstar_getkeycode; - hkey->inputdev->setkeycode = topstar_setkeycode; - for (key = topstar_keymap; key->code; key++) { - set_bit(EV_KEY, hkey->inputdev->evbit); - set_bit(key->keycode, hkey->inputdev->keybit); + error = sparse_keymap_setup(input, topstar_keymap, NULL); + if (error) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; } - if (input_register_device(hkey->inputdev)) { + + error = input_register_device(input); + if (error) { pr_err("Unable to register input device\n"); - input_free_device(hkey->inputdev); - return -ENODEV; + goto err_free_keymap; } + hkey->inputdev = input; return 0; + + err_free_keymap: + sparse_keymap_free(input); + err_free_dev: + input_free_device(input); + return error; } static int acpi_topstar_add(struct acpi_device *device) @@ -210,12 +151,13 @@ add_err: return -ENODEV; } -static int acpi_topstar_remove(struct acpi_device *device, int type) +static int acpi_topstar_remove(struct acpi_device *device) { struct topstar_hkey *tps_hkey = acpi_driver_data(device); acpi_topstar_fncx_switch(device, false); + sparse_keymap_free(tps_hkey->inputdev); input_unregister_device(tps_hkey->inputdev); kfree(tps_hkey); @@ -238,27 +180,7 @@ static struct acpi_driver acpi_topstar_driver = { .notify = acpi_topstar_notify, }, }; - -static int __init topstar_laptop_init(void) -{ - int ret; - - ret = acpi_bus_register_driver(&acpi_topstar_driver); - if (ret < 0) - return ret; - - printk(KERN_INFO "Topstar Laptop ACPI extras driver loaded\n"); - - return 0; -} - -static void __exit topstar_laptop_exit(void) -{ - acpi_bus_unregister_driver(&acpi_topstar_driver); -} - -module_init(topstar_laptop_init); -module_exit(topstar_laptop_exit); +module_acpi_driver(acpi_topstar_driver); MODULE_AUTHOR("Herton Ronaldo Krzesinski"); MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 77bf5d8f893..76441dcbe5f 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -4,6 +4,8 @@ * * Copyright (C) 2002-2004 John Belmonte * Copyright (C) 2008 Philip Langdale + * Copyright (C) 2010 Pierre Ducroquet + * Copyright (C) 2014 Azael Avalos * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,7 +36,9 @@ * */ -#define TOSHIBA_ACPI_VERSION "0.19" +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define TOSHIBA_ACPI_VERSION "0.20" #define PROC_INTERFACE_VERSION 1 #include <linux/kernel.h> @@ -44,26 +48,27 @@ #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/backlight.h> -#include <linux/platform_device.h> #include <linux/rfkill.h> - +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/i8042.h> +#include <linux/acpi.h> +#include <linux/dmi.h> #include <asm/uaccess.h> -#include <acpi/acpi_drivers.h> - MODULE_AUTHOR("John Belmonte"); MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); MODULE_LICENSE("GPL"); -#define MY_LOGPREFIX "toshiba_acpi: " -#define MY_ERR KERN_ERR MY_LOGPREFIX -#define MY_NOTICE KERN_NOTICE MY_LOGPREFIX -#define MY_INFO KERN_INFO MY_LOGPREFIX +#define TOSHIBA_WMI_EVENT_GUID "59142400-C6A3-40FA-BADB-8A2652834100" + +/* Scan code for Fn key on TOS1900 models */ +#define TOS1900_FN_SCAN 0x6e /* Toshiba ACPI method paths */ -#define METHOD_LCD_BRIGHTNESS "\\_SB_.PCI0.VGA_.LCD_._BCM" -#define METHOD_HCI_1 "\\_SB_.VALD.GHCI" -#define METHOD_HCI_2 "\\_SB_.VALZ.GHCI" #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" /* Toshiba HCI interface definitions @@ -74,6 +79,9 @@ MODULE_LICENSE("GPL"); * However the ACPI methods seem to be incomplete in some areas (for * example they allow setting, but not reading, the LCD brightness value), * so this is still useful. + * + * SCI stands for "System Configuration Interface" which aim is to + * conceal differences in hardware between different models. */ #define HCI_WORDS 6 @@ -81,25 +89,48 @@ MODULE_LICENSE("GPL"); /* operations */ #define HCI_SET 0xff00 #define HCI_GET 0xfe00 +#define SCI_OPEN 0xf100 +#define SCI_CLOSE 0xf200 +#define SCI_GET 0xf300 +#define SCI_SET 0xf400 /* return codes */ #define HCI_SUCCESS 0x0000 #define HCI_FAILURE 0x1000 #define HCI_NOT_SUPPORTED 0x8000 #define HCI_EMPTY 0x8c00 +#define HCI_DATA_NOT_AVAILABLE 0x8d20 +#define HCI_NOT_INITIALIZED 0x8d50 +#define SCI_OPEN_CLOSE_OK 0x0044 +#define SCI_ALREADY_OPEN 0x8100 +#define SCI_NOT_OPENED 0x8200 +#define SCI_INPUT_DATA_ERROR 0x8300 +#define SCI_NOT_PRESENT 0x8600 /* registers */ #define HCI_FAN 0x0004 +#define HCI_TR_BACKLIGHT 0x0005 #define HCI_SYSTEM_EVENT 0x0016 #define HCI_VIDEO_OUT 0x001c #define HCI_HOTKEY_EVENT 0x001e #define HCI_LCD_BRIGHTNESS 0x002a #define HCI_WIRELESS 0x0056 +#define HCI_ACCELEROMETER 0x006d +#define HCI_KBD_ILLUMINATION 0x0095 +#define HCI_ECO_MODE 0x0097 +#define HCI_ACCELEROMETER2 0x00a6 +#define SCI_ILLUMINATION 0x014e +#define SCI_KBD_ILLUM_STATUS 0x015c +#define SCI_TOUCHPAD 0x050e /* field definitions */ +#define HCI_ACCEL_MASK 0x7fff +#define HCI_HOTKEY_DISABLE 0x0b +#define HCI_HOTKEY_ENABLE 0x09 #define HCI_LCD_BRIGHTNESS_BITS 3 #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) #define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) +#define HCI_MISC_SHIFT 0x10 #define HCI_VIDEO_OUT_LCD 0x1 #define HCI_VIDEO_OUT_CRT 0x2 #define HCI_VIDEO_OUT_TV 0x4 @@ -107,6 +138,44 @@ MODULE_LICENSE("GPL"); #define HCI_WIRELESS_BT_PRESENT 0x0f #define HCI_WIRELESS_BT_ATTACH 0x40 #define HCI_WIRELESS_BT_POWER 0x80 +#define SCI_KBD_MODE_FNZ 0x1 +#define SCI_KBD_MODE_AUTO 0x2 + +struct toshiba_acpi_dev { + struct acpi_device *acpi_dev; + const char *method_hci; + struct rfkill *bt_rfk; + struct input_dev *hotkey_dev; + struct work_struct hotkey_work; + struct backlight_device *backlight_dev; + struct led_classdev led_dev; + struct led_classdev kbd_led; + struct led_classdev eco_led; + + int force_fan; + int last_key_event; + int key_event_valid; + int kbd_mode; + int kbd_time; + + unsigned int illumination_supported:1; + unsigned int video_supported:1; + unsigned int fan_supported:1; + unsigned int system_event_supported:1; + unsigned int ntfy_supported:1; + unsigned int info_supported:1; + unsigned int tr_backlight_supported:1; + unsigned int kbd_illum_supported:1; + unsigned int kbd_led_registered:1; + unsigned int touchpad_supported:1; + unsigned int eco_supported:1; + unsigned int accelerometer_supported:1; + unsigned int sysfs_created:1; + + struct mutex mutex; +}; + +static struct toshiba_acpi_dev *toshiba_acpi; static const struct acpi_device_id toshiba_device_ids[] = { {"TOS6200", 0}, @@ -116,6 +185,59 @@ static const struct acpi_device_id toshiba_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, toshiba_device_ids); +static const struct key_entry toshiba_acpi_keymap[] = { + { KE_KEY, 0x9e, { KEY_RFKILL } }, + { KE_KEY, 0x101, { KEY_MUTE } }, + { KE_KEY, 0x102, { KEY_ZOOMOUT } }, + { KE_KEY, 0x103, { KEY_ZOOMIN } }, + { KE_KEY, 0x12c, { KEY_KBDILLUMTOGGLE } }, + { KE_KEY, 0x139, { KEY_ZOOMRESET } }, + { KE_KEY, 0x13b, { KEY_COFFEE } }, + { KE_KEY, 0x13c, { KEY_BATTERY } }, + { KE_KEY, 0x13d, { KEY_SLEEP } }, + { KE_KEY, 0x13e, { KEY_SUSPEND } }, + { KE_KEY, 0x13f, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 0x140, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x141, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x142, { KEY_WLAN } }, + { KE_KEY, 0x143, { KEY_TOUCHPAD_TOGGLE } }, + { KE_KEY, 0x17f, { KEY_FN } }, + { KE_KEY, 0xb05, { KEY_PROG2 } }, + { KE_KEY, 0xb06, { KEY_WWW } }, + { KE_KEY, 0xb07, { KEY_MAIL } }, + { KE_KEY, 0xb30, { KEY_STOP } }, + { KE_KEY, 0xb31, { KEY_PREVIOUSSONG } }, + { KE_KEY, 0xb32, { KEY_NEXTSONG } }, + { KE_KEY, 0xb33, { KEY_PLAYPAUSE } }, + { KE_KEY, 0xb5a, { KEY_MEDIA } }, + { KE_IGNORE, 0x1430, { KEY_RESERVED } }, + { KE_END, 0 }, +}; + +/* alternative keymap */ +static const struct dmi_system_id toshiba_alt_keymap_dmi[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "Satellite M840"), + }, + }, + {} +}; + +static const struct key_entry toshiba_acpi_alt_keymap[] = { + { KE_KEY, 0x157, { KEY_MUTE } }, + { KE_KEY, 0x102, { KEY_ZOOMOUT } }, + { KE_KEY, 0x103, { KEY_ZOOMIN } }, + { KE_KEY, 0x139, { KEY_ZOOMRESET } }, + { KE_KEY, 0x13e, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 0x13c, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x13d, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0x158, { KEY_WLAN } }, + { KE_KEY, 0x13f, { KEY_TOUCHPAD_TOGGLE } }, + { KE_END, 0 }, +}; + /* utility */ @@ -127,53 +249,19 @@ static __inline__ void _set_bit(u32 * word, u32 mask, int value) /* acpi interface wrappers */ -static int is_valid_acpi_path(const char *methodName) -{ - acpi_handle handle; - acpi_status status; - - status = acpi_get_handle(NULL, (char *)methodName, &handle); - return !ACPI_FAILURE(status); -} - static int write_acpi_int(const char *methodName, int val) { - struct acpi_object_list params; - union acpi_object in_objs[1]; - acpi_status status; - - params.count = ARRAY_SIZE(in_objs); - params.pointer = in_objs; - in_objs[0].type = ACPI_TYPE_INTEGER; - in_objs[0].integer.value = val; - - status = acpi_evaluate_object(NULL, (char *)methodName, ¶ms, NULL); - return (status == AE_OK); -} - -#if 0 -static int read_acpi_int(const char *methodName, int *pVal) -{ - struct acpi_buffer results; - union acpi_object out_objs[1]; acpi_status status; - results.length = sizeof(out_objs); - results.pointer = out_objs; - - status = acpi_evaluate_object(0, (char *)methodName, 0, &results); - *pVal = out_objs[0].integer.value; - - return (status == AE_OK) && (out_objs[0].type == ACPI_TYPE_INTEGER); + status = acpi_execute_simple_method(NULL, (char *)methodName, val); + return (status == AE_OK) ? 0 : -EIO; } -#endif - -static const char *method_hci /*= 0*/ ; /* Perform a raw HCI call. Here we don't care about input or output buffer * format. */ -static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) +static acpi_status hci_raw(struct toshiba_acpi_dev *dev, + const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) { struct acpi_object_list params; union acpi_object in_objs[HCI_WORDS]; @@ -192,7 +280,8 @@ static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) results.length = sizeof(out_objs); results.pointer = out_objs; - status = acpi_evaluate_object(NULL, (char *)method_hci, ¶ms, + status = acpi_evaluate_object(dev->acpi_dev->handle, + (char *)dev->method_hci, ¶ms, &results); if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) { for (i = 0; i < out_objs->package.count; ++i) { @@ -209,82 +298,435 @@ static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) * may be useful (such as "not supported"). */ -static acpi_status hci_write1(u32 reg, u32 in1, u32 * result) +static acpi_status hci_write1(struct toshiba_acpi_dev *dev, u32 reg, + u32 in1, u32 *result) { u32 in[HCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; u32 out[HCI_WORDS]; - acpi_status status = hci_raw(in, out); + acpi_status status = hci_raw(dev, in, out); *result = (status == AE_OK) ? out[0] : HCI_FAILURE; return status; } -static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result) +static acpi_status hci_read1(struct toshiba_acpi_dev *dev, u32 reg, + u32 *out1, u32 *result) { u32 in[HCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; u32 out[HCI_WORDS]; - acpi_status status = hci_raw(in, out); + acpi_status status = hci_raw(dev, in, out); *out1 = out[2]; *result = (status == AE_OK) ? out[0] : HCI_FAILURE; return status; } -static acpi_status hci_write2(u32 reg, u32 in1, u32 in2, u32 *result) +static acpi_status hci_write2(struct toshiba_acpi_dev *dev, u32 reg, + u32 in1, u32 in2, u32 *result) { u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 }; u32 out[HCI_WORDS]; - acpi_status status = hci_raw(in, out); + acpi_status status = hci_raw(dev, in, out); *result = (status == AE_OK) ? out[0] : HCI_FAILURE; return status; } -static acpi_status hci_read2(u32 reg, u32 *out1, u32 *out2, u32 *result) +static acpi_status hci_read2(struct toshiba_acpi_dev *dev, u32 reg, + u32 *out1, u32 *out2, u32 *result) { u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; u32 out[HCI_WORDS]; - acpi_status status = hci_raw(in, out); + acpi_status status = hci_raw(dev, in, out); *out1 = out[2]; *out2 = out[3]; *result = (status == AE_OK) ? out[0] : HCI_FAILURE; return status; } -struct toshiba_acpi_dev { - struct platform_device *p_dev; - struct rfkill *bt_rfk; +/* common sci tasks + */ - const char *bt_name; +static int sci_open(struct toshiba_acpi_dev *dev) +{ + u32 in[HCI_WORDS] = { SCI_OPEN, 0, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; - struct mutex mutex; -}; + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == HCI_FAILURE) { + pr_err("ACPI call to open SCI failed\n"); + return 0; + } -static struct toshiba_acpi_dev toshiba_acpi = { - .bt_name = "Toshiba Bluetooth", -}; + if (out[0] == SCI_OPEN_CLOSE_OK) { + return 1; + } else if (out[0] == SCI_ALREADY_OPEN) { + pr_info("Toshiba SCI already opened\n"); + return 1; + } else if (out[0] == SCI_NOT_PRESENT) { + pr_info("Toshiba SCI is not present\n"); + } + + return 0; +} + +static void sci_close(struct toshiba_acpi_dev *dev) +{ + u32 in[HCI_WORDS] = { SCI_CLOSE, 0, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == HCI_FAILURE) { + pr_err("ACPI call to close SCI failed\n"); + return; + } + + if (out[0] == SCI_OPEN_CLOSE_OK) + return; + else if (out[0] == SCI_NOT_OPENED) + pr_info("Toshiba SCI not opened\n"); + else if (out[0] == SCI_NOT_PRESENT) + pr_info("Toshiba SCI is not present\n"); +} + +static acpi_status sci_read(struct toshiba_acpi_dev *dev, u32 reg, + u32 *out1, u32 *result) +{ + u32 in[HCI_WORDS] = { SCI_GET, reg, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(dev, in, out); + *out1 = out[2]; + *result = (ACPI_SUCCESS(status)) ? out[0] : HCI_FAILURE; + return status; +} + +static acpi_status sci_write(struct toshiba_acpi_dev *dev, u32 reg, + u32 in1, u32 *result) +{ + u32 in[HCI_WORDS] = { SCI_SET, reg, in1, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status = hci_raw(dev, in, out); + *result = (ACPI_SUCCESS(status)) ? out[0] : HCI_FAILURE; + return status; +} + +/* Illumination support */ +static int toshiba_illumination_available(struct toshiba_acpi_dev *dev) +{ + u32 in[HCI_WORDS] = { SCI_GET, SCI_ILLUMINATION, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + if (!sci_open(dev)) + return 0; + + status = hci_raw(dev, in, out); + sci_close(dev); + if (ACPI_FAILURE(status) || out[0] == HCI_FAILURE) { + pr_err("ACPI call to query Illumination support failed\n"); + return 0; + } else if (out[0] == HCI_NOT_SUPPORTED || out[1] != 1) { + pr_info("Illumination device not available\n"); + return 0; + } + + return 1; +} + +static void toshiba_illumination_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, led_dev); + u32 state, result; + acpi_status status; + + /* First request : initialize communication. */ + if (!sci_open(dev)) + return; + + /* Switch the illumination on/off */ + state = brightness ? 1 : 0; + status = sci_write(dev, SCI_ILLUMINATION, state, &result); + sci_close(dev); + if (ACPI_FAILURE(status)) { + pr_err("ACPI call for illumination failed\n"); + return; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Illumination not supported\n"); + return; + } +} + +static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, led_dev); + u32 state, result; + acpi_status status; + + /* First request : initialize communication. */ + if (!sci_open(dev)) + return LED_OFF; + + /* Check the illumination */ + status = sci_read(dev, SCI_ILLUMINATION, &state, &result); + sci_close(dev); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call for illumination failed\n"); + return LED_OFF; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Illumination not supported\n"); + return LED_OFF; + } + + return state ? LED_FULL : LED_OFF; +} + +/* KBD Illumination */ +static int toshiba_kbd_illum_status_set(struct toshiba_acpi_dev *dev, u32 time) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_write(dev, SCI_KBD_ILLUM_STATUS, time, &result); + sci_close(dev); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to set KBD backlight status failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight status not supported\n"); + return -ENODEV; + } + + return 0; +} + +static int toshiba_kbd_illum_status_get(struct toshiba_acpi_dev *dev, u32 *time) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_read(dev, SCI_KBD_ILLUM_STATUS, time, &result); + sci_close(dev); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to get KBD backlight status failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight status not supported\n"); + return -ENODEV; + } + + return 0; +} + +static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, kbd_led); + u32 state, result; + acpi_status status; + + /* Check the keyboard backlight state */ + status = hci_read1(dev, HCI_KBD_ILLUMINATION, &state, &result); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to get the keyboard backlight failed\n"); + return LED_OFF; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight not supported\n"); + return LED_OFF; + } + + return state ? LED_FULL : LED_OFF; +} + +static void toshiba_kbd_backlight_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, kbd_led); + u32 state, result; + acpi_status status; + + /* Set the keyboard backlight state */ + state = brightness ? 1 : 0; + status = hci_write1(dev, HCI_KBD_ILLUMINATION, state, &result); + if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to set KBD Illumination mode failed\n"); + return; + } else if (result == HCI_NOT_SUPPORTED) { + pr_info("Keyboard backlight not supported\n"); + return; + } +} + +/* TouchPad support */ +static int toshiba_touchpad_set(struct toshiba_acpi_dev *dev, u32 state) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_write(dev, SCI_TOUCHPAD, state, &result); + sci_close(dev); + if (ACPI_FAILURE(status)) { + pr_err("ACPI call to set the touchpad failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + return -ENODEV; + } + + return 0; +} + +static int toshiba_touchpad_get(struct toshiba_acpi_dev *dev, u32 *state) +{ + u32 result; + acpi_status status; + + if (!sci_open(dev)) + return -EIO; + + status = sci_read(dev, SCI_TOUCHPAD, state, &result); + sci_close(dev); + if (ACPI_FAILURE(status)) { + pr_err("ACPI call to query the touchpad failed\n"); + return -EIO; + } else if (result == HCI_NOT_SUPPORTED) { + return -ENODEV; + } + + return 0; +} + +/* Eco Mode support */ +static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) +{ + acpi_status status; + u32 in[HCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 1, 0, 0 }; + u32 out[HCI_WORDS]; + + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_info("ACPI call to get ECO led failed\n"); + return 0; + } + + return 1; +} + +static enum led_brightness toshiba_eco_mode_get_status(struct led_classdev *cdev) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, eco_led); + u32 in[HCI_WORDS] = { HCI_GET, HCI_ECO_MODE, 0, 1, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to get ECO led failed\n"); + return LED_OFF; + } + + return out[2] ? LED_FULL : LED_OFF; +} + +static void toshiba_eco_mode_set_status(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct toshiba_acpi_dev *dev = container_of(cdev, + struct toshiba_acpi_dev, eco_led); + u32 in[HCI_WORDS] = { HCI_SET, HCI_ECO_MODE, 0, 1, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + /* Switch the Eco Mode led on/off */ + in[2] = (brightness) ? 1 : 0; + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to set ECO led failed\n"); + return; + } +} + +/* Accelerometer support */ +static int toshiba_accelerometer_supported(struct toshiba_acpi_dev *dev) +{ + u32 in[HCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER2, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + /* Check if the accelerometer call exists, + * this call also serves as initialization + */ + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to query the accelerometer failed\n"); + return -EIO; + } else if (out[0] == HCI_DATA_NOT_AVAILABLE || + out[0] == HCI_NOT_INITIALIZED) { + pr_err("Accelerometer not initialized\n"); + return -EIO; + } else if (out[0] == HCI_NOT_SUPPORTED) { + pr_info("Accelerometer not supported\n"); + return -ENODEV; + } + + return 0; +} + +static int toshiba_accelerometer_get(struct toshiba_acpi_dev *dev, + u32 *xy, u32 *z) +{ + u32 in[HCI_WORDS] = { HCI_GET, HCI_ACCELEROMETER, 0, 1, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + /* Check the Accelerometer status */ + status = hci_raw(dev, in, out); + if (ACPI_FAILURE(status) || out[0] == SCI_INPUT_DATA_ERROR) { + pr_err("ACPI call to query the accelerometer failed\n"); + return -EIO; + } + + *xy = out[2]; + *z = out[4]; + + return 0; +} /* Bluetooth rfkill handlers */ -static u32 hci_get_bt_present(bool *present) +static u32 hci_get_bt_present(struct toshiba_acpi_dev *dev, bool *present) { u32 hci_result; u32 value, value2; value = 0; value2 = 0; - hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); + hci_read2(dev, HCI_WIRELESS, &value, &value2, &hci_result); if (hci_result == HCI_SUCCESS) *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false; return hci_result; } -static u32 hci_get_radio_state(bool *radio_state) +static u32 hci_get_radio_state(struct toshiba_acpi_dev *dev, bool *radio_state) { u32 hci_result; u32 value, value2; value = 0; value2 = 0x0001; - hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); + hci_read2(dev, HCI_WIRELESS, &value, &value2, &hci_result); *radio_state = value & HCI_WIRELESS_KILL_SWITCH; return hci_result; @@ -301,8 +743,8 @@ static int bt_rfkill_set_block(void *data, bool blocked) value = (blocked == false); mutex_lock(&dev->mutex); - if (hci_get_radio_state(&radio_state) != HCI_SUCCESS) { - err = -EBUSY; + if (hci_get_radio_state(dev, &radio_state) != HCI_SUCCESS) { + err = -EIO; goto out; } @@ -311,11 +753,11 @@ static int bt_rfkill_set_block(void *data, bool blocked) goto out; } - hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1); - hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2); + hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1); + hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2); if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) - err = -EBUSY; + err = -EIO; else err = 0; out: @@ -332,7 +774,7 @@ static void bt_rfkill_poll(struct rfkill *rfkill, void *data) mutex_lock(&dev->mutex); - hci_result = hci_get_radio_state(&value); + hci_result = hci_get_radio_state(dev, &value); if (hci_result != HCI_SUCCESS) { /* Can't do anything useful */ mutex_unlock(&dev->mutex); @@ -352,68 +794,115 @@ static const struct rfkill_ops toshiba_rfk_ops = { .poll = bt_rfkill_poll, }; +static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled) +{ + u32 hci_result; + u32 status; + + hci_read1(dev, HCI_TR_BACKLIGHT, &status, &hci_result); + *enabled = !status; + return hci_result == HCI_SUCCESS ? 0 : -EIO; +} + +static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable) +{ + u32 hci_result; + u32 value = !enable; + + hci_write1(dev, HCI_TR_BACKLIGHT, value, &hci_result); + return hci_result == HCI_SUCCESS ? 0 : -EIO; +} + static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ; -static struct backlight_device *toshiba_backlight_device; -static int force_fan; -static int last_key_event; -static int key_event_valid; -static int get_lcd(struct backlight_device *bd) +static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) { u32 hci_result; u32 value; + int brightness = 0; - hci_read1(HCI_LCD_BRIGHTNESS, &value, &hci_result); - if (hci_result == HCI_SUCCESS) { - return (value >> HCI_LCD_BRIGHTNESS_SHIFT); - } else - return -EFAULT; + if (dev->tr_backlight_supported) { + bool enabled; + int ret = get_tr_backlight_status(dev, &enabled); + if (ret) + return ret; + if (enabled) + return 0; + brightness++; + } + + hci_read1(dev, HCI_LCD_BRIGHTNESS, &value, &hci_result); + if (hci_result == HCI_SUCCESS) + return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT); + + return -EIO; +} + +static int get_lcd_brightness(struct backlight_device *bd) +{ + struct toshiba_acpi_dev *dev = bl_get_data(bd); + return __get_lcd_brightness(dev); } static int lcd_proc_show(struct seq_file *m, void *v) { - int value = get_lcd(NULL); + struct toshiba_acpi_dev *dev = m->private; + int value; + int levels; + + if (!dev->backlight_dev) + return -ENODEV; + levels = dev->backlight_dev->props.max_brightness + 1; + value = get_lcd_brightness(dev->backlight_dev); if (value >= 0) { seq_printf(m, "brightness: %d\n", value); - seq_printf(m, "brightness_levels: %d\n", - HCI_LCD_BRIGHTNESS_LEVELS); - } else { - printk(MY_ERR "Error reading LCD brightness\n"); + seq_printf(m, "brightness_levels: %d\n", levels); + return 0; } - return 0; + pr_err("Error reading LCD brightness\n"); + return -EIO; } static int lcd_proc_open(struct inode *inode, struct file *file) { - return single_open(file, lcd_proc_show, NULL); + return single_open(file, lcd_proc_show, PDE_DATA(inode)); } -static int set_lcd(int value) +static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value) { u32 hci_result; - value = value << HCI_LCD_BRIGHTNESS_SHIFT; - hci_write1(HCI_LCD_BRIGHTNESS, value, &hci_result); - if (hci_result != HCI_SUCCESS) - return -EFAULT; + if (dev->tr_backlight_supported) { + bool enable = !value; + int ret = set_tr_backlight_status(dev, enable); + if (ret) + return ret; + if (value) + value--; + } - return 0; + value = value << HCI_LCD_BRIGHTNESS_SHIFT; + hci_write1(dev, HCI_LCD_BRIGHTNESS, value, &hci_result); + return hci_result == HCI_SUCCESS ? 0 : -EIO; } static int set_lcd_status(struct backlight_device *bd) { - return set_lcd(bd->props.brightness); + struct toshiba_acpi_dev *dev = bl_get_data(bd); + return set_lcd_brightness(dev, bd->props.brightness); } static ssize_t lcd_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { + struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file)); char cmd[42]; size_t len; int value; int ret; + int levels = dev->backlight_dev->props.max_brightness + 1; len = min(count, sizeof(cmd) - 1); if (copy_from_user(cmd, buf, len)) @@ -421,8 +910,8 @@ static ssize_t lcd_proc_write(struct file *file, const char __user *buf, cmd[len] = '\0'; if (sscanf(cmd, " brightness : %i", &value) == 1 && - value >= 0 && value < HCI_LCD_BRIGHTNESS_LEVELS) { - ret = set_lcd(value); + value >= 0 && value < levels) { + ret = set_lcd_brightness(dev, value); if (ret == 0) ret = count; } else { @@ -440,41 +929,49 @@ static const struct file_operations lcd_proc_fops = { .write = lcd_proc_write, }; -static int video_proc_show(struct seq_file *m, void *v) +static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status) { u32 hci_result; + + hci_read1(dev, HCI_VIDEO_OUT, status, &hci_result); + return hci_result == HCI_SUCCESS ? 0 : -EIO; +} + +static int video_proc_show(struct seq_file *m, void *v) +{ + struct toshiba_acpi_dev *dev = m->private; u32 value; + int ret; - hci_read1(HCI_VIDEO_OUT, &value, &hci_result); - if (hci_result == HCI_SUCCESS) { + ret = get_video_status(dev, &value); + if (!ret) { int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0; seq_printf(m, "lcd_out: %d\n", is_lcd); seq_printf(m, "crt_out: %d\n", is_crt); seq_printf(m, "tv_out: %d\n", is_tv); - } else { - printk(MY_ERR "Error reading video out status\n"); } - return 0; + return ret; } static int video_proc_open(struct inode *inode, struct file *file) { - return single_open(file, video_proc_show, NULL); + return single_open(file, video_proc_show, PDE_DATA(inode)); } static ssize_t video_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { + struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file)); char *cmd, *buffer; + int ret; int value; int remain = count; int lcd_out = -1; int crt_out = -1; int tv_out = -1; - u32 hci_result; u32 video_out; cmd = kmalloc(count + 1, GFP_KERNEL); @@ -509,8 +1006,8 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, kfree(cmd); - hci_read1(HCI_VIDEO_OUT, &video_out, &hci_result); - if (hci_result == HCI_SUCCESS) { + ret = get_video_status(dev, &video_out); + if (!ret) { unsigned int new_video_out = video_out; if (lcd_out != -1) _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); @@ -521,12 +1018,10 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, /* To avoid unnecessary video disruption, only write the new * video setting if something changed. */ if (new_video_out != video_out) - write_acpi_int(METHOD_VIDEO_OUT, new_video_out); - } else { - return -EFAULT; + ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out); } - return count; + return ret ? ret : count; } static const struct file_operations video_proc_fops = { @@ -538,30 +1033,38 @@ static const struct file_operations video_proc_fops = { .write = video_proc_write, }; -static int fan_proc_show(struct seq_file *m, void *v) +static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status) { u32 hci_result; + + hci_read1(dev, HCI_FAN, status, &hci_result); + return hci_result == HCI_SUCCESS ? 0 : -EIO; +} + +static int fan_proc_show(struct seq_file *m, void *v) +{ + struct toshiba_acpi_dev *dev = m->private; + int ret; u32 value; - hci_read1(HCI_FAN, &value, &hci_result); - if (hci_result == HCI_SUCCESS) { + ret = get_fan_status(dev, &value); + if (!ret) { seq_printf(m, "running: %d\n", (value > 0)); - seq_printf(m, "force_on: %d\n", force_fan); - } else { - printk(MY_ERR "Error reading fan status\n"); + seq_printf(m, "force_on: %d\n", dev->force_fan); } - return 0; + return ret; } static int fan_proc_open(struct inode *inode, struct file *file) { - return single_open(file, fan_proc_show, NULL); + return single_open(file, fan_proc_show, PDE_DATA(inode)); } static ssize_t fan_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { + struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file)); char cmd[42]; size_t len; int value; @@ -574,11 +1077,11 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf, if (sscanf(cmd, " force_on : %i", &value) == 1 && value >= 0 && value <= 1) { - hci_write1(HCI_FAN, value, &hci_result); + hci_write1(dev, HCI_FAN, value, &hci_result); if (hci_result != HCI_SUCCESS) - return -EFAULT; + return -EIO; else - force_fan = value; + dev->force_fan = value; } else { return -EINVAL; } @@ -597,42 +1100,43 @@ static const struct file_operations fan_proc_fops = { static int keys_proc_show(struct seq_file *m, void *v) { + struct toshiba_acpi_dev *dev = m->private; u32 hci_result; u32 value; - if (!key_event_valid) { - hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); + if (!dev->key_event_valid && dev->system_event_supported) { + hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result); if (hci_result == HCI_SUCCESS) { - key_event_valid = 1; - last_key_event = value; + dev->key_event_valid = 1; + dev->last_key_event = value; } else if (hci_result == HCI_EMPTY) { /* better luck next time */ } else if (hci_result == HCI_NOT_SUPPORTED) { /* This is a workaround for an unresolved issue on * some machines where system events sporadically * become disabled. */ - hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); - printk(MY_NOTICE "Re-enabled hotkeys\n"); + hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result); + pr_notice("Re-enabled hotkeys\n"); } else { - printk(MY_ERR "Error reading hotkey status\n"); - goto end; + pr_err("Error reading hotkey status\n"); + return -EIO; } } - seq_printf(m, "hotkey_ready: %d\n", key_event_valid); - seq_printf(m, "hotkey: 0x%04x\n", last_key_event); -end: + seq_printf(m, "hotkey_ready: %d\n", dev->key_event_valid); + seq_printf(m, "hotkey: 0x%04x\n", dev->last_key_event); return 0; } static int keys_proc_open(struct inode *inode, struct file *file) { - return single_open(file, keys_proc_show, NULL); + return single_open(file, keys_proc_show, PDE_DATA(inode)); } static ssize_t keys_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { + struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file)); char cmd[42]; size_t len; int value; @@ -643,7 +1147,7 @@ static ssize_t keys_proc_write(struct file *file, const char __user *buf, cmd[len] = '\0'; if (sscanf(cmd, " hotkey_ready : %i", &value) == 1 && value == 0) { - key_event_valid = 0; + dev->key_event_valid = 0; } else { return -EINVAL; } @@ -669,7 +1173,7 @@ static int version_proc_show(struct seq_file *m, void *v) static int version_proc_open(struct inode *inode, struct file *file) { - return single_open(file, version_proc_show, PDE(inode)->data); + return single_open(file, version_proc_show, PDE_DATA(inode)); } static const struct file_operations version_proc_fops = { @@ -685,142 +1189,711 @@ static const struct file_operations version_proc_fops = { #define PROC_TOSHIBA "toshiba" -static acpi_status __init add_device(void) +static void create_toshiba_proc_entries(struct toshiba_acpi_dev *dev) { - proc_create("lcd", S_IRUGO | S_IWUSR, toshiba_proc_dir, &lcd_proc_fops); - proc_create("video", S_IRUGO | S_IWUSR, toshiba_proc_dir, &video_proc_fops); - proc_create("fan", S_IRUGO | S_IWUSR, toshiba_proc_dir, &fan_proc_fops); - proc_create("keys", S_IRUGO | S_IWUSR, toshiba_proc_dir, &keys_proc_fops); - proc_create("version", S_IRUGO, toshiba_proc_dir, &version_proc_fops); - - return AE_OK; + if (dev->backlight_dev) + proc_create_data("lcd", S_IRUGO | S_IWUSR, toshiba_proc_dir, + &lcd_proc_fops, dev); + if (dev->video_supported) + proc_create_data("video", S_IRUGO | S_IWUSR, toshiba_proc_dir, + &video_proc_fops, dev); + if (dev->fan_supported) + proc_create_data("fan", S_IRUGO | S_IWUSR, toshiba_proc_dir, + &fan_proc_fops, dev); + if (dev->hotkey_dev) + proc_create_data("keys", S_IRUGO | S_IWUSR, toshiba_proc_dir, + &keys_proc_fops, dev); + proc_create_data("version", S_IRUGO, toshiba_proc_dir, + &version_proc_fops, dev); } -static acpi_status remove_device(void) +static void remove_toshiba_proc_entries(struct toshiba_acpi_dev *dev) { - remove_proc_entry("lcd", toshiba_proc_dir); - remove_proc_entry("video", toshiba_proc_dir); - remove_proc_entry("fan", toshiba_proc_dir); - remove_proc_entry("keys", toshiba_proc_dir); + if (dev->backlight_dev) + remove_proc_entry("lcd", toshiba_proc_dir); + if (dev->video_supported) + remove_proc_entry("video", toshiba_proc_dir); + if (dev->fan_supported) + remove_proc_entry("fan", toshiba_proc_dir); + if (dev->hotkey_dev) + remove_proc_entry("keys", toshiba_proc_dir); remove_proc_entry("version", toshiba_proc_dir); - return AE_OK; } -static struct backlight_ops toshiba_backlight_data = { - .get_brightness = get_lcd, - .update_status = set_lcd_status, +static const struct backlight_ops toshiba_backlight_data = { + .options = BL_CORE_SUSPENDRESUME, + .get_brightness = get_lcd_brightness, + .update_status = set_lcd_status, }; -static void toshiba_acpi_exit(void) +/* + * Sysfs files + */ + +static ssize_t toshiba_kbd_bl_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { - if (toshiba_acpi.bt_rfk) { - rfkill_unregister(toshiba_acpi.bt_rfk); - rfkill_destroy(toshiba_acpi.bt_rfk); + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int mode = -1; + int time = -1; + + if (sscanf(buf, "%i", &mode) != 1 && (mode != 2 || mode != 1)) + return -EINVAL; + + /* Set the Keyboard Backlight Mode where: + * Mode - Auto (2) | FN-Z (1) + * Auto - KBD backlight turns off automatically in given time + * FN-Z - KBD backlight "toggles" when hotkey pressed + */ + if (mode != -1 && toshiba->kbd_mode != mode) { + time = toshiba->kbd_time << HCI_MISC_SHIFT; + time = time + toshiba->kbd_mode; + if (toshiba_kbd_illum_status_set(toshiba, time) < 0) + return -EIO; + toshiba->kbd_mode = mode; } - if (toshiba_backlight_device) - backlight_device_unregister(toshiba_backlight_device); + return count; +} - remove_device(); +static ssize_t toshiba_kbd_bl_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 time; - if (toshiba_proc_dir) - remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); + if (toshiba_kbd_illum_status_get(toshiba, &time) < 0) + return -EIO; - platform_device_unregister(toshiba_acpi.p_dev); + return sprintf(buf, "%i\n", time & 0x07); +} - return; +static ssize_t toshiba_kbd_bl_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int time = -1; + + if (sscanf(buf, "%i", &time) != 1 && (time < 0 || time > 60)) + return -EINVAL; + + /* Set the Keyboard Backlight Timeout: 0-60 seconds */ + if (time != -1 && toshiba->kbd_time != time) { + time = time << HCI_MISC_SHIFT; + time = (toshiba->kbd_mode == SCI_KBD_MODE_AUTO) ? + time + 1 : time + 2; + if (toshiba_kbd_illum_status_set(toshiba, time) < 0) + return -EIO; + toshiba->kbd_time = time >> HCI_MISC_SHIFT; + } + + return count; } -static int __init toshiba_acpi_init(void) +static ssize_t toshiba_kbd_bl_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - acpi_status status = AE_OK; - u32 hci_result; - bool bt_present; - int ret = 0; + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 time; - if (acpi_disabled) - return -ENODEV; + if (toshiba_kbd_illum_status_get(toshiba, &time) < 0) + return -EIO; - /* simple device detection: look for HCI method */ - if (is_valid_acpi_path(METHOD_HCI_1)) - method_hci = METHOD_HCI_1; - else if (is_valid_acpi_path(METHOD_HCI_2)) - method_hci = METHOD_HCI_2; - else - return -ENODEV; + return sprintf(buf, "%i\n", time >> HCI_MISC_SHIFT); +} - printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", - TOSHIBA_ACPI_VERSION); - printk(MY_INFO " HCI method: %s\n", method_hci); +static ssize_t toshiba_touchpad_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + int state; + + /* Set the TouchPad on/off, 0 - Disable | 1 - Enable */ + if (sscanf(buf, "%i", &state) == 1 && (state == 0 || state == 1)) { + if (toshiba_touchpad_set(toshiba, state) < 0) + return -EIO; + } + + return count; +} + +static ssize_t toshiba_touchpad_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 state; + int ret; + + ret = toshiba_touchpad_get(toshiba, &state); + if (ret < 0) + return ret; + + return sprintf(buf, "%i\n", state); +} - mutex_init(&toshiba_acpi.mutex); +static ssize_t toshiba_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); + u32 xyval, zval, tmp; + u16 x, y, z; + int ret; - toshiba_acpi.p_dev = platform_device_register_simple("toshiba_acpi", - -1, NULL, 0); - if (IS_ERR(toshiba_acpi.p_dev)) { - ret = PTR_ERR(toshiba_acpi.p_dev); - printk(MY_ERR "unable to register platform device\n"); - toshiba_acpi.p_dev = NULL; - toshiba_acpi_exit(); + xyval = zval = 0; + ret = toshiba_accelerometer_get(toshiba, &xyval, &zval); + if (ret < 0) return ret; + + x = xyval & HCI_ACCEL_MASK; + tmp = xyval >> HCI_MISC_SHIFT; + y = tmp & HCI_ACCEL_MASK; + z = zval & HCI_ACCEL_MASK; + + return sprintf(buf, "%d %d %d\n", x, y, z); +} + +static DEVICE_ATTR(kbd_backlight_mode, S_IRUGO | S_IWUSR, + toshiba_kbd_bl_mode_show, toshiba_kbd_bl_mode_store); +static DEVICE_ATTR(kbd_backlight_timeout, S_IRUGO | S_IWUSR, + toshiba_kbd_bl_timeout_show, toshiba_kbd_bl_timeout_store); +static DEVICE_ATTR(touchpad, S_IRUGO | S_IWUSR, + toshiba_touchpad_show, toshiba_touchpad_store); +static DEVICE_ATTR(position, S_IRUGO, toshiba_position_show, NULL); + +static struct attribute *toshiba_attributes[] = { + &dev_attr_kbd_backlight_mode.attr, + &dev_attr_kbd_backlight_timeout.attr, + &dev_attr_touchpad.attr, + &dev_attr_position.attr, + NULL, +}; + +static umode_t toshiba_sysfs_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct toshiba_acpi_dev *drv = dev_get_drvdata(dev); + bool exists = true; + + if (attr == &dev_attr_kbd_backlight_mode.attr) + exists = (drv->kbd_illum_supported) ? true : false; + else if (attr == &dev_attr_kbd_backlight_timeout.attr) + exists = (drv->kbd_mode == SCI_KBD_MODE_AUTO) ? true : false; + else if (attr == &dev_attr_touchpad.attr) + exists = (drv->touchpad_supported) ? true : false; + else if (attr == &dev_attr_position.attr) + exists = (drv->accelerometer_supported) ? true : false; + + return exists ? attr->mode : 0; +} + +static struct attribute_group toshiba_attr_group = { + .is_visible = toshiba_sysfs_is_visible, + .attrs = toshiba_attributes, +}; + +static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + if (str & 0x20) + return false; + + if (unlikely(data == 0xe0)) + return false; + + if ((data & 0x7f) == TOS1900_FN_SCAN) { + schedule_work(&toshiba_acpi->hotkey_work); + return true; } - force_fan = 0; - key_event_valid = 0; + return false; +} - /* enable event fifo */ - hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); +static void toshiba_acpi_hotkey_work(struct work_struct *work) +{ + acpi_handle ec_handle = ec_get_handle(); + acpi_status status; - toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); - if (!toshiba_proc_dir) { - toshiba_acpi_exit(); - return -ENODEV; - } else { - status = add_device(); - if (ACPI_FAILURE(status)) { - toshiba_acpi_exit(); - return -ENODEV; + if (!ec_handle) + return; + + status = acpi_evaluate_object(ec_handle, "NTFY", NULL, NULL); + if (ACPI_FAILURE(status)) + pr_err("ACPI NTFY method execution failed\n"); +} + +/* + * Returns hotkey scancode, or < 0 on failure. + */ +static int toshiba_acpi_query_hotkey(struct toshiba_acpi_dev *dev) +{ + unsigned long long value; + acpi_status status; + + status = acpi_evaluate_integer(dev->acpi_dev->handle, "INFO", + NULL, &value); + if (ACPI_FAILURE(status)) { + pr_err("ACPI INFO method execution failed\n"); + return -EIO; + } + + return value; +} + +static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev, + int scancode) +{ + if (scancode == 0x100) + return; + + /* act on key press; ignore key release */ + if (scancode & 0x80) + return; + + if (!sparse_keymap_report_event(dev->hotkey_dev, scancode, 1, true)) + pr_info("Unknown key %x\n", scancode); +} + +static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) +{ + acpi_status status; + acpi_handle ec_handle; + int error; + u32 hci_result; + const struct key_entry *keymap = toshiba_acpi_keymap; + + dev->hotkey_dev = input_allocate_device(); + if (!dev->hotkey_dev) + return -ENOMEM; + + dev->hotkey_dev->name = "Toshiba input device"; + dev->hotkey_dev->phys = "toshiba_acpi/input0"; + dev->hotkey_dev->id.bustype = BUS_HOST; + + if (dmi_check_system(toshiba_alt_keymap_dmi)) + keymap = toshiba_acpi_alt_keymap; + error = sparse_keymap_setup(dev->hotkey_dev, keymap, NULL); + if (error) + goto err_free_dev; + + /* + * For some machines the SCI responsible for providing hotkey + * notification doesn't fire. We can trigger the notification + * whenever the Fn key is pressed using the NTFY method, if + * supported, so if it's present set up an i8042 key filter + * for this purpose. + */ + status = AE_ERROR; + ec_handle = ec_get_handle(); + if (ec_handle && acpi_has_method(ec_handle, "NTFY")) { + INIT_WORK(&dev->hotkey_work, toshiba_acpi_hotkey_work); + + error = i8042_install_filter(toshiba_acpi_i8042_filter); + if (error) { + pr_err("Error installing key filter\n"); + goto err_free_keymap; } + + dev->ntfy_supported = 1; + } + + /* + * Determine hotkey query interface. Prefer using the INFO + * method when it is available. + */ + if (acpi_has_method(dev->acpi_dev->handle, "INFO")) + dev->info_supported = 1; + else { + hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result); + if (hci_result == HCI_SUCCESS) + dev->system_event_supported = 1; + } + + if (!dev->info_supported && !dev->system_event_supported) { + pr_warn("No hotkey query interface found\n"); + goto err_remove_filter; + } + + status = acpi_evaluate_object(dev->acpi_dev->handle, "ENAB", NULL, NULL); + if (ACPI_FAILURE(status)) { + pr_info("Unable to enable hotkeys\n"); + error = -ENODEV; + goto err_remove_filter; + } + + error = input_register_device(dev->hotkey_dev); + if (error) { + pr_info("Unable to register input device\n"); + goto err_remove_filter; } - toshiba_backlight_device = backlight_device_register("toshiba", - &toshiba_acpi.p_dev->dev, - NULL, - &toshiba_backlight_data); - if (IS_ERR(toshiba_backlight_device)) { - ret = PTR_ERR(toshiba_backlight_device); + hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &hci_result); + return 0; + + err_remove_filter: + if (dev->ntfy_supported) + i8042_remove_filter(toshiba_acpi_i8042_filter); + err_free_keymap: + sparse_keymap_free(dev->hotkey_dev); + err_free_dev: + input_free_device(dev->hotkey_dev); + dev->hotkey_dev = NULL; + return error; +} + +static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) +{ + struct backlight_properties props; + int brightness; + int ret; + bool enabled; - printk(KERN_ERR "Could not register toshiba backlight device\n"); - toshiba_backlight_device = NULL; - toshiba_acpi_exit(); + /* + * Some machines don't support the backlight methods at all, and + * others support it read-only. Either of these is pretty useless, + * so only register the backlight device if the backlight method + * supports both reads and writes. + */ + brightness = __get_lcd_brightness(dev); + if (brightness < 0) + return 0; + ret = set_lcd_brightness(dev, brightness); + if (ret) { + pr_debug("Backlight method is read-only, disabling backlight support\n"); + return 0; + } + + /* Determine whether or not BIOS supports transflective backlight */ + ret = get_tr_backlight_status(dev, &enabled); + dev->tr_backlight_supported = !ret; + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; + + /* adding an extra level and having 0 change to transflective mode */ + if (dev->tr_backlight_supported) + props.max_brightness++; + + dev->backlight_dev = backlight_device_register("toshiba", + &dev->acpi_dev->dev, + dev, + &toshiba_backlight_data, + &props); + if (IS_ERR(dev->backlight_dev)) { + ret = PTR_ERR(dev->backlight_dev); + pr_err("Could not register toshiba backlight device\n"); + dev->backlight_dev = NULL; return ret; } - toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; + + dev->backlight_dev->props.brightness = brightness; + return 0; +} + +static int toshiba_acpi_remove(struct acpi_device *acpi_dev) +{ + struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); + + remove_toshiba_proc_entries(dev); + + if (dev->sysfs_created) + sysfs_remove_group(&dev->acpi_dev->dev.kobj, + &toshiba_attr_group); + + if (dev->ntfy_supported) { + i8042_remove_filter(toshiba_acpi_i8042_filter); + cancel_work_sync(&dev->hotkey_work); + } + + if (dev->hotkey_dev) { + input_unregister_device(dev->hotkey_dev); + sparse_keymap_free(dev->hotkey_dev); + } + + if (dev->bt_rfk) { + rfkill_unregister(dev->bt_rfk); + rfkill_destroy(dev->bt_rfk); + } + + if (dev->backlight_dev) + backlight_device_unregister(dev->backlight_dev); + + if (dev->illumination_supported) + led_classdev_unregister(&dev->led_dev); + + if (dev->kbd_led_registered) + led_classdev_unregister(&dev->kbd_led); + + if (dev->eco_supported) + led_classdev_unregister(&dev->eco_led); + + if (toshiba_acpi) + toshiba_acpi = NULL; + + kfree(dev); + + return 0; +} + +static const char *find_hci_method(acpi_handle handle) +{ + if (acpi_has_method(handle, "GHCI")) + return "GHCI"; + + if (acpi_has_method(handle, "SPFC")) + return "SPFC"; + + return NULL; +} + +static int toshiba_acpi_add(struct acpi_device *acpi_dev) +{ + struct toshiba_acpi_dev *dev; + const char *hci_method; + u32 dummy; + bool bt_present; + int ret = 0; + + if (toshiba_acpi) + return -EBUSY; + + pr_info("Toshiba Laptop ACPI Extras version %s\n", + TOSHIBA_ACPI_VERSION); + + hci_method = find_hci_method(acpi_dev->handle); + if (!hci_method) { + pr_err("HCI interface not found\n"); + return -ENODEV; + } + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->acpi_dev = acpi_dev; + dev->method_hci = hci_method; + acpi_dev->driver_data = dev; + dev_set_drvdata(&acpi_dev->dev, dev); + + if (toshiba_acpi_setup_keyboard(dev)) + pr_info("Unable to activate hotkeys\n"); + + mutex_init(&dev->mutex); + + ret = toshiba_acpi_setup_backlight(dev); + if (ret) + goto error; /* Register rfkill switch for Bluetooth */ - if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) { - toshiba_acpi.bt_rfk = rfkill_alloc(toshiba_acpi.bt_name, - &toshiba_acpi.p_dev->dev, - RFKILL_TYPE_BLUETOOTH, - &toshiba_rfk_ops, - &toshiba_acpi); - if (!toshiba_acpi.bt_rfk) { - printk(MY_ERR "unable to allocate rfkill device\n"); - toshiba_acpi_exit(); - return -ENOMEM; + if (hci_get_bt_present(dev, &bt_present) == HCI_SUCCESS && bt_present) { + dev->bt_rfk = rfkill_alloc("Toshiba Bluetooth", + &acpi_dev->dev, + RFKILL_TYPE_BLUETOOTH, + &toshiba_rfk_ops, + dev); + if (!dev->bt_rfk) { + pr_err("unable to allocate rfkill device\n"); + ret = -ENOMEM; + goto error; } - ret = rfkill_register(toshiba_acpi.bt_rfk); + ret = rfkill_register(dev->bt_rfk); if (ret) { - printk(MY_ERR "unable to register rfkill device\n"); - rfkill_destroy(toshiba_acpi.bt_rfk); - toshiba_acpi_exit(); - return ret; + pr_err("unable to register rfkill device\n"); + rfkill_destroy(dev->bt_rfk); + goto error; } } + if (toshiba_illumination_available(dev)) { + dev->led_dev.name = "toshiba::illumination"; + dev->led_dev.max_brightness = 1; + dev->led_dev.brightness_set = toshiba_illumination_set; + dev->led_dev.brightness_get = toshiba_illumination_get; + if (!led_classdev_register(&acpi_dev->dev, &dev->led_dev)) + dev->illumination_supported = 1; + } + + if (toshiba_eco_mode_available(dev)) { + dev->eco_led.name = "toshiba::eco_mode"; + dev->eco_led.max_brightness = 1; + dev->eco_led.brightness_set = toshiba_eco_mode_set_status; + dev->eco_led.brightness_get = toshiba_eco_mode_get_status; + if (!led_classdev_register(&dev->acpi_dev->dev, &dev->eco_led)) + dev->eco_supported = 1; + } + + ret = toshiba_kbd_illum_status_get(dev, &dummy); + if (!ret) { + dev->kbd_time = dummy >> HCI_MISC_SHIFT; + dev->kbd_mode = dummy & 0x07; + } + dev->kbd_illum_supported = !ret; + /* + * Only register the LED if KBD illumination is supported + * and the keyboard backlight operation mode is set to FN-Z + */ + if (dev->kbd_illum_supported && dev->kbd_mode == SCI_KBD_MODE_FNZ) { + dev->kbd_led.name = "toshiba::kbd_backlight"; + dev->kbd_led.max_brightness = 1; + dev->kbd_led.brightness_set = toshiba_kbd_backlight_set; + dev->kbd_led.brightness_get = toshiba_kbd_backlight_get; + if (!led_classdev_register(&dev->acpi_dev->dev, &dev->kbd_led)) + dev->kbd_led_registered = 1; + } + + ret = toshiba_touchpad_get(dev, &dummy); + dev->touchpad_supported = !ret; + + ret = toshiba_accelerometer_supported(dev); + dev->accelerometer_supported = !ret; + + /* Determine whether or not BIOS supports fan and video interfaces */ + + ret = get_video_status(dev, &dummy); + dev->video_supported = !ret; + + ret = get_fan_status(dev, &dummy); + dev->fan_supported = !ret; + + ret = sysfs_create_group(&dev->acpi_dev->dev.kobj, + &toshiba_attr_group); + if (ret) { + dev->sysfs_created = 0; + goto error; + } + dev->sysfs_created = !ret; + + create_toshiba_proc_entries(dev); + + toshiba_acpi = dev; + + return 0; + +error: + toshiba_acpi_remove(acpi_dev); + return ret; +} + +static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) +{ + struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); + u32 hci_result, value; + int retries = 3; + int scancode; + + if (event != 0x80) + return; + + if (dev->info_supported) { + scancode = toshiba_acpi_query_hotkey(dev); + if (scancode < 0) + pr_err("Failed to query hotkey event\n"); + else if (scancode != 0) + toshiba_acpi_report_hotkey(dev, scancode); + } else if (dev->system_event_supported) { + do { + hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result); + switch (hci_result) { + case HCI_SUCCESS: + toshiba_acpi_report_hotkey(dev, (int)value); + break; + case HCI_NOT_SUPPORTED: + /* + * This is a workaround for an unresolved + * issue on some machines where system events + * sporadically become disabled. + */ + hci_write1(dev, HCI_SYSTEM_EVENT, 1, + &hci_result); + pr_notice("Re-enabled hotkeys\n"); + /* fall through */ + default: + retries--; + break; + } + } while (retries && hci_result != HCI_EMPTY); + } +} + +#ifdef CONFIG_PM_SLEEP +static int toshiba_acpi_suspend(struct device *device) +{ + struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device)); + u32 result; + + if (dev->hotkey_dev) + hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE, &result); + return 0; } +static int toshiba_acpi_resume(struct device *device) +{ + struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device)); + u32 result; + + if (dev->hotkey_dev) + hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &result); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(toshiba_acpi_pm, + toshiba_acpi_suspend, toshiba_acpi_resume); + +static struct acpi_driver toshiba_acpi_driver = { + .name = "Toshiba ACPI driver", + .owner = THIS_MODULE, + .ids = toshiba_device_ids, + .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, + .ops = { + .add = toshiba_acpi_add, + .remove = toshiba_acpi_remove, + .notify = toshiba_acpi_notify, + }, + .drv.pm = &toshiba_acpi_pm, +}; + +static int __init toshiba_acpi_init(void) +{ + int ret; + + /* + * Machines with this WMI guid aren't supported due to bugs in + * their AML. This check relies on wmi initializing before + * toshiba_acpi to guarantee guids have been identified. + */ + if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) + return -ENODEV; + + toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); + if (!toshiba_proc_dir) { + pr_err("Unable to create proc dir " PROC_TOSHIBA "\n"); + return -ENODEV; + } + + ret = acpi_bus_register_driver(&toshiba_acpi_driver); + if (ret) { + pr_err("Failed to register ACPI driver: %d\n", ret); + remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); + } + + return ret; +} + +static void __exit toshiba_acpi_exit(void) +{ + acpi_bus_unregister_driver(&toshiba_acpi_driver); + if (toshiba_proc_dir) + remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); +} + module_init(toshiba_acpi_init); module_exit(toshiba_acpi_exit); diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index a350418e87e..2cb1ea62b4a 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -17,22 +17,21 @@ * delivered. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/acpi.h> MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>"); MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver"); MODULE_LICENSE("GPL"); - static int toshiba_bt_rfkill_add(struct acpi_device *device); -static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type); +static int toshiba_bt_rfkill_remove(struct acpi_device *device); static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event); -static int toshiba_bt_resume(struct acpi_device *device); static const struct acpi_device_id bt_device_ids[] = { { "TOS6205", 0}, @@ -40,6 +39,11 @@ static const struct acpi_device_id bt_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, bt_device_ids); +#ifdef CONFIG_PM_SLEEP +static int toshiba_bt_resume(struct device *dev); +#endif +static SIMPLE_DEV_PM_OPS(toshiba_bt_pm, NULL, toshiba_bt_resume); + static struct acpi_driver toshiba_bt_rfkill_driver = { .name = "Toshiba BT", .class = "Toshiba", @@ -48,16 +52,16 @@ static struct acpi_driver toshiba_bt_rfkill_driver = { .add = toshiba_bt_rfkill_add, .remove = toshiba_bt_rfkill_remove, .notify = toshiba_bt_rfkill_notify, - .resume = toshiba_bt_resume, }, .owner = THIS_MODULE, + .drv.pm = &toshiba_bt_pm, }; static int toshiba_bluetooth_enable(acpi_handle handle) { acpi_status res1, res2; - acpi_integer result; + u64 result; /* * Query ACPI to verify RFKill switch is set to 'on'. @@ -70,14 +74,13 @@ static int toshiba_bluetooth_enable(acpi_handle handle) if (!(result & 0x01)) return 0; - printk(KERN_INFO "toshiba_bluetooth: Re-enabling Toshiba Bluetooth\n"); + pr_info("Re-enabling Toshiba Bluetooth\n"); res1 = acpi_evaluate_object(handle, "AUSB", NULL, NULL); res2 = acpi_evaluate_object(handle, "BTPO", NULL, NULL); if (!ACPI_FAILURE(res1) || !ACPI_FAILURE(res2)) return 0; - printk(KERN_WARNING "toshiba_bluetooth: Failed to re-enable " - "Toshiba Bluetooth\n"); + pr_warn("Failed to re-enable Toshiba Bluetooth\n"); return -ENODEV; } @@ -87,15 +90,17 @@ static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) toshiba_bluetooth_enable(device->handle); } -static int toshiba_bt_resume(struct acpi_device *device) +#ifdef CONFIG_PM_SLEEP +static int toshiba_bt_resume(struct device *dev) { - return toshiba_bluetooth_enable(device->handle); + return toshiba_bluetooth_enable(to_acpi_device(dev)->handle); } +#endif static int toshiba_bt_rfkill_add(struct acpi_device *device) { acpi_status status; - acpi_integer bt_present; + u64 bt_present; int result = -ENODEV; /* @@ -107,38 +112,18 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) &bt_present); if (!ACPI_FAILURE(status) && bt_present) { - printk(KERN_INFO "Detected Toshiba ACPI Bluetooth device - " - "installing RFKill handler\n"); + pr_info("Detected Toshiba ACPI Bluetooth device - " + "installing RFKill handler\n"); result = toshiba_bluetooth_enable(device->handle); } return result; } -static int __init toshiba_bt_rfkill_init(void) -{ - int result; - - result = acpi_bus_register_driver(&toshiba_bt_rfkill_driver); - if (result < 0) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Error registering driver\n")); - return result; - } - - return 0; -} - -static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type) +static int toshiba_bt_rfkill_remove(struct acpi_device *device) { /* clean up */ return 0; } -static void __exit toshiba_bt_rfkill_exit(void) -{ - acpi_bus_unregister_driver(&toshiba_bt_rfkill_driver); -} - -module_init(toshiba_bt_rfkill_init); -module_exit(toshiba_bt_rfkill_exit); +module_acpi_driver(toshiba_bt_rfkill_driver); diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index b104302fea0..43d13295e63 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -27,14 +27,16 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/init.h> #include <linux/types.h> #include <linux/device.h> #include <linux/list.h> #include <linux/acpi.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/slab.h> +#include <linux/module.h> ACPI_MODULE_NAME("wmi"); MODULE_AUTHOR("Carlos Corbacho"); @@ -43,9 +45,8 @@ MODULE_LICENSE("GPL"); #define ACPI_WMI_CLASS "wmi" -#define PREFIX "ACPI: WMI: " - static DEFINE_MUTEX(wmi_data_lock); +static LIST_HEAD(wmi_block_list); struct guid_block { char guid[16]; @@ -66,10 +67,9 @@ struct wmi_block { acpi_handle handle; wmi_notify_handler handler; void *handler_data; - struct device *dev; + struct device dev; }; -static struct wmi_block wmi_blocks; /* * If the GUID data block is marked as expensive, we must enable and @@ -80,7 +80,17 @@ static struct wmi_block wmi_blocks; #define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ #define ACPI_WMI_EVENT 0x8 /* GUID is an event */ -static int acpi_wmi_remove(struct acpi_device *device, int type); +static bool debug_event; +module_param(debug_event, bool, 0444); +MODULE_PARM_DESC(debug_event, + "Log WMI Events [0/1]"); + +static bool debug_dump_wdg; +module_param(debug_dump_wdg, bool, 0444); +MODULE_PARM_DESC(debug_dump_wdg, + "Dump available WMI interfaces [0/1]"); + +static int acpi_wmi_remove(struct acpi_device *device); static int acpi_wmi_add(struct acpi_device *device); static void acpi_wmi_notify(struct acpi_device *device, u32 event); @@ -99,7 +109,7 @@ static struct acpi_driver acpi_wmi_driver = { .add = acpi_wmi_add, .remove = acpi_wmi_remove, .notify = acpi_wmi_notify, - }, + }, }; /* @@ -117,30 +127,18 @@ static struct acpi_driver acpi_wmi_driver = { */ static int wmi_parse_hexbyte(const u8 *src) { - unsigned int x; /* For correct wrapping */ int h; + int value; /* high part */ - x = src[0]; - if (x - '0' <= '9' - '0') { - h = x - '0'; - } else if (x - 'a' <= 'f' - 'a') { - h = x - 'a' + 10; - } else if (x - 'A' <= 'F' - 'A') { - h = x - 'A' + 10; - } else { + h = value = hex_to_bin(src[0]); + if (value < 0) return -1; - } - h <<= 4; /* low part */ - x = src[1]; - if (x - '0' <= '9' - '0') - return h | (x - '0'); - if (x - 'a' <= 'f' - 'a') - return h | (x - 'a' + 10); - if (x - 'A' <= 'F' - 'A') - return h | (x - 'A' + 10); + value = hex_to_bin(src[1]); + if (value >= 0) + return (h << 4) | value; return -1; } @@ -221,7 +219,7 @@ static int wmi_gtoa(const char *in, char *out) for (i = 10; i <= 15; i++) out += sprintf(out, "%02X", in[i] & 0xFF); - out = '\0'; + *out = '\0'; return 0; } @@ -235,7 +233,7 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) wmi_parse_guid(guid_string, tmp); wmi_swap_bytes(tmp, guid_input); - list_for_each(p, &wmi_blocks.list) { + list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); block = &wblock->gblock; @@ -252,8 +250,6 @@ static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) { struct guid_block *block = NULL; char method[5]; - struct acpi_object_list input; - union acpi_object params[1]; acpi_status status; acpi_handle handle; @@ -263,13 +259,9 @@ static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) if (!block) return AE_NOT_EXIST; - input.count = 1; - input.pointer = params; - params[0].type = ACPI_TYPE_INTEGER; - params[0].integer.value = enable; snprintf(method, 5, "WE%02X", block->notify_id); - status = acpi_evaluate_object(handle, method, &input, NULL); + status = acpi_execute_simple_method(handle, method, enable); if (status != AE_OK && status != AE_NOT_FOUND) return status; @@ -353,10 +345,10 @@ struct acpi_buffer *out) { struct guid_block *block = NULL; struct wmi_block *wblock = NULL; - acpi_handle handle, wc_handle; + acpi_handle handle; acpi_status status, wc_status = AE_ERROR; - struct acpi_object_list input, wc_input; - union acpi_object wc_params[1], wq_params[1]; + struct acpi_object_list input; + union acpi_object wq_params[1]; char method[5]; char wc_method[5] = "WC"; @@ -386,11 +378,6 @@ struct acpi_buffer *out) * enable collection. */ if (block->flags & ACPI_WMI_EXPENSIVE) { - wc_input.count = 1; - wc_input.pointer = wc_params; - wc_params[0].type = ACPI_TYPE_INTEGER; - wc_params[0].integer.value = 1; - strncat(wc_method, block->object_id, 2); /* @@ -398,10 +385,9 @@ struct acpi_buffer *out) * expensive, but have no corresponding WCxx method. So we * should not fail if this happens. */ - wc_status = acpi_get_handle(handle, wc_method, &wc_handle); - if (ACPI_SUCCESS(wc_status)) - wc_status = acpi_evaluate_object(handle, wc_method, - &wc_input, NULL); + if (acpi_has_method(handle, wc_method)) + wc_status = acpi_execute_simple_method(handle, + wc_method, 1); } strcpy(method, "WQ"); @@ -414,9 +400,7 @@ struct acpi_buffer *out) * the WQxx method failed - we should disable collection anyway. */ if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { - wc_params[0].integer.value = 0; - status = acpi_evaluate_object(handle, - wc_method, &wc_input, NULL); + status = acpi_execute_simple_method(handle, wc_method, 0); } return status; @@ -476,6 +460,69 @@ const struct acpi_buffer *in) } EXPORT_SYMBOL_GPL(wmi_set_block); +static void wmi_dump_wdg(const struct guid_block *g) +{ + char guid_string[37]; + + wmi_gtoa(g->guid, guid_string); + + pr_info("%s:\n", guid_string); + pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]); + pr_info("\tnotify_id: %02X\n", g->notify_id); + pr_info("\treserved: %02X\n", g->reserved); + pr_info("\tinstance_count: %d\n", g->instance_count); + pr_info("\tflags: %#x", g->flags); + if (g->flags) { + if (g->flags & ACPI_WMI_EXPENSIVE) + pr_cont(" ACPI_WMI_EXPENSIVE"); + if (g->flags & ACPI_WMI_METHOD) + pr_cont(" ACPI_WMI_METHOD"); + if (g->flags & ACPI_WMI_STRING) + pr_cont(" ACPI_WMI_STRING"); + if (g->flags & ACPI_WMI_EVENT) + pr_cont(" ACPI_WMI_EVENT"); + } + pr_cont("\n"); + +} + +static void wmi_notify_debug(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_info("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + + if (!obj) + return; + + pr_info("DEBUG Event "); + switch(obj->type) { + case ACPI_TYPE_BUFFER: + pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length); + break; + case ACPI_TYPE_STRING: + pr_cont("STRING_TYPE - %s\n", obj->string.pointer); + break; + case ACPI_TYPE_INTEGER: + pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value); + break; + case ACPI_TYPE_PACKAGE: + pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count); + break; + default: + pr_cont("object type 0x%X\n", obj->type); + } + kfree(obj); +} + /** * wmi_install_notify_handler - Register handler for WMI events * @handler: Function to handle notifications @@ -487,21 +534,34 @@ acpi_status wmi_install_notify_handler(const char *guid, wmi_notify_handler handler, void *data) { struct wmi_block *block; - acpi_status status; + acpi_status status = AE_NOT_EXIST; + char tmp[16], guid_input[16]; + struct list_head *p; if (!guid || !handler) return AE_BAD_PARAMETER; - if (!find_guid(guid, &block)) - return AE_NOT_EXIST; + wmi_parse_guid(guid, tmp); + wmi_swap_bytes(tmp, guid_input); + + list_for_each(p, &wmi_block_list) { + acpi_status wmi_status; + block = list_entry(p, struct wmi_block, list); - if (block->handler) - return AE_ALREADY_ACQUIRED; + if (memcmp(block->gblock.guid, guid_input, 16) == 0) { + if (block->handler && + block->handler != wmi_notify_debug) + return AE_ALREADY_ACQUIRED; - block->handler = handler; - block->handler_data = data; + block->handler = handler; + block->handler_data = data; - status = wmi_method_enable(block, 1); + wmi_status = wmi_method_enable(block, 1); + if ((wmi_status != AE_OK) || + ((wmi_status == AE_OK) && (status == AE_NOT_EXIST))) + status = wmi_status; + } + } return status; } @@ -515,21 +575,39 @@ EXPORT_SYMBOL_GPL(wmi_install_notify_handler); acpi_status wmi_remove_notify_handler(const char *guid) { struct wmi_block *block; - acpi_status status; + acpi_status status = AE_NOT_EXIST; + char tmp[16], guid_input[16]; + struct list_head *p; if (!guid) return AE_BAD_PARAMETER; - if (!find_guid(guid, &block)) - return AE_NOT_EXIST; - - if (!block->handler) - return AE_NULL_ENTRY; - - status = wmi_method_enable(block, 0); + wmi_parse_guid(guid, tmp); + wmi_swap_bytes(tmp, guid_input); - block->handler = NULL; - block->handler_data = NULL; + list_for_each(p, &wmi_block_list) { + acpi_status wmi_status; + block = list_entry(p, struct wmi_block, list); + + if (memcmp(block->gblock.guid, guid_input, 16) == 0) { + if (!block->handler || + block->handler == wmi_notify_debug) + return AE_NULL_ENTRY; + + if (debug_event) { + block->handler = wmi_notify_debug; + status = AE_OK; + } else { + wmi_status = wmi_method_enable(block, 0); + block->handler = NULL; + block->handler_data = NULL; + if ((wmi_status != AE_OK) || + ((wmi_status == AE_OK) && + (status == AE_NOT_EXIST))) + status = wmi_status; + } + } + } return status; } @@ -556,7 +634,7 @@ acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) params[0].type = ACPI_TYPE_INTEGER; params[0].integer.value = event; - list_for_each(p, &wmi_blocks.list) { + list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); gblock = &wblock->gblock; @@ -585,21 +663,29 @@ EXPORT_SYMBOL_GPL(wmi_has_guid); /* * sysfs interface */ -static ssize_t show_modalias(struct device *dev, struct device_attribute *attr, +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { char guid_string[37]; struct wmi_block *wblock; wblock = dev_get_drvdata(dev); - if (!wblock) - return -ENOMEM; + if (!wblock) { + strcat(buf, "\n"); + return strlen(buf); + } wmi_gtoa(wblock->gblock.guid, guid_string); return sprintf(buf, "wmi:%s\n", guid_string); } -static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL); +static DEVICE_ATTR_RO(modalias); + +static struct attribute *wmi_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(wmi); static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) { @@ -625,169 +711,126 @@ static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) static void wmi_dev_free(struct device *dev) { - kfree(dev); + struct wmi_block *wmi_block = container_of(dev, struct wmi_block, dev); + + kfree(wmi_block); } static struct class wmi_class = { .name = "wmi", .dev_release = wmi_dev_free, .dev_uevent = wmi_dev_uevent, + .dev_groups = wmi_groups, }; -static int wmi_create_devs(void) +static int wmi_create_device(const struct guid_block *gblock, + struct wmi_block *wblock, acpi_handle handle) { - int result; char guid_string[37]; - struct guid_block *gblock; - struct wmi_block *wblock; - struct list_head *p; - struct device *guid_dev; - /* Create devices for all the GUIDs */ - list_for_each(p, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - - guid_dev = kzalloc(sizeof(struct device), GFP_KERNEL); - if (!guid_dev) - return -ENOMEM; - - wblock->dev = guid_dev; + wblock->dev.class = &wmi_class; - guid_dev->class = &wmi_class; - dev_set_drvdata(guid_dev, wblock); + wmi_gtoa(gblock->guid, guid_string); + dev_set_name(&wblock->dev, "%s", guid_string); - gblock = &wblock->gblock; - - wmi_gtoa(gblock->guid, guid_string); - dev_set_name(guid_dev, guid_string); + dev_set_drvdata(&wblock->dev, wblock); - result = device_register(guid_dev); - if (result) - return result; - - result = device_create_file(guid_dev, &dev_attr_modalias); - if (result) - return result; - } - - return 0; + return device_register(&wblock->dev); } -static void wmi_remove_devs(void) +static void wmi_free_devices(void) { - struct guid_block *gblock; - struct wmi_block *wblock; - struct list_head *p; - struct device *guid_dev; + struct wmi_block *wblock, *next; /* Delete devices for all the GUIDs */ - list_for_each(p, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - - guid_dev = wblock->dev; - gblock = &wblock->gblock; - - device_remove_file(guid_dev, &dev_attr_modalias); - - device_unregister(guid_dev); + list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { + list_del(&wblock->list); + if (wblock->dev.class) + device_unregister(&wblock->dev); + else + kfree(wblock); } } -static void wmi_class_exit(void) -{ - wmi_remove_devs(); - class_unregister(&wmi_class); -} - -static int wmi_class_init(void) -{ - int ret; - - ret = class_register(&wmi_class); - if (ret) - return ret; - - ret = wmi_create_devs(); - if (ret) - wmi_class_exit(); - - return ret; -} - static bool guid_already_parsed(const char *guid_string) { - struct guid_block *gblock; struct wmi_block *wblock; - struct list_head *p; - list_for_each(p, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - gblock = &wblock->gblock; - - if (strncmp(gblock->guid, guid_string, 16) == 0) + list_for_each_entry(wblock, &wmi_block_list, list) + if (memcmp(wblock->gblock.guid, guid_string, 16) == 0) return true; - } + return false; } /* * Parse the _WDG method for the GUID data blocks */ -static __init acpi_status parse_wdg(acpi_handle handle) +static int parse_wdg(acpi_handle handle) { struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; union acpi_object *obj; - struct guid_block *gblock; + const struct guid_block *gblock; struct wmi_block *wblock; - char guid_string[37]; acpi_status status; + int retval; u32 i, total; status = acpi_evaluate_object(handle, "_WDG", NULL, &out); - if (ACPI_FAILURE(status)) - return status; + return -ENXIO; obj = (union acpi_object *) out.pointer; + if (!obj) + return -ENXIO; - if (obj->type != ACPI_TYPE_BUFFER) - return AE_ERROR; + if (obj->type != ACPI_TYPE_BUFFER) { + retval = -ENXIO; + goto out_free_pointer; + } + gblock = (const struct guid_block *)obj->buffer.pointer; total = obj->buffer.length / sizeof(struct guid_block); - gblock = kzalloc(obj->buffer.length, GFP_KERNEL); - if (!gblock) - return AE_NO_MEMORY; + for (i = 0; i < total; i++) { + if (debug_dump_wdg) + wmi_dump_wdg(&gblock[i]); + + wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); + if (!wblock) + return -ENOMEM; - memcpy(gblock, obj->buffer.pointer, obj->buffer.length); + wblock->handle = handle; + wblock->gblock = gblock[i]; - for (i = 0; i < total; i++) { /* Some WMI devices, like those for nVidia hooks, have a duplicate GUID. It's not clear what we should do in this - case yet, so for now, we'll just ignore the duplicate. - Anyone who wants to add support for that device can come - up with a better workaround for the mess then. + case yet, so for now, we'll just ignore the duplicate + for device creation. */ - if (guid_already_parsed(gblock[i].guid) == true) { - wmi_gtoa(gblock[i].guid, guid_string); - printk(KERN_INFO PREFIX "Skipping duplicate GUID %s\n", - guid_string); - continue; + if (!guid_already_parsed(gblock[i].guid)) { + retval = wmi_create_device(&gblock[i], wblock, handle); + if (retval) { + wmi_free_devices(); + goto out_free_pointer; + } } - wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); - if (!wblock) - return AE_NO_MEMORY; - wblock->gblock = gblock[i]; - wblock->handle = handle; - list_add_tail(&wblock->list, &wmi_blocks.list); + list_add_tail(&wblock->list, &wmi_block_list); + + if (debug_event) { + wblock->handler = wmi_notify_debug; + wmi_method_enable(wblock, 1); + } } + retval = 0; + +out_free_pointer: kfree(out.pointer); - kfree(gblock); - return status; + return retval; } /* @@ -796,7 +839,7 @@ static __init acpi_status parse_wdg(acpi_handle handle) */ static acpi_status acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, - u32 bits, acpi_integer * value, + u32 bits, u64 *value, void *handler_context, void *region_context) { int result = 0, i = 0; @@ -813,7 +856,7 @@ acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, if (function == ACPI_READ) { result = ec_read(address, &temp); - (*value) |= ((acpi_integer)temp) << i; + (*value) |= ((u64)temp) << i; } else { temp = 0xff & ((*value) >> i); result = ec_write(address, temp); @@ -839,8 +882,9 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) struct guid_block *block; struct wmi_block *wblock; struct list_head *p; + char guid_string[37]; - list_for_each(p, &wmi_blocks.list) { + list_for_each(p, &wmi_block_list) { wblock = list_entry(p, struct wmi_block, list); block = &wblock->gblock; @@ -848,6 +892,10 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) (block->notify_id == event)) { if (wblock->handler) wblock->handler(event, wblock->handler_data); + if (debug_event) { + wmi_gtoa(wblock->gblock.guid, guid_string); + pr_info("DEBUG Event GUID: %s\n", guid_string); + } acpi_bus_generate_netlink_event( device->pnp.device_class, dev_name(&device->dev), @@ -857,79 +905,69 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event) } } -static int acpi_wmi_remove(struct acpi_device *device, int type) +static int acpi_wmi_remove(struct acpi_device *device) { acpi_remove_address_space_handler(device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); + wmi_free_devices(); return 0; } -static int __init acpi_wmi_add(struct acpi_device *device) +static int acpi_wmi_add(struct acpi_device *device) { acpi_status status; - int result = 0; + int error; status = acpi_install_address_space_handler(device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler, NULL, NULL); - if (ACPI_FAILURE(status)) - return -ENODEV; - - status = parse_wdg(device->handle); if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX "Error installing EC region handler\n"); + pr_err("Error installing EC region handler\n"); return -ENODEV; } - return result; + error = parse_wdg(device->handle); + if (error) { + acpi_remove_address_space_handler(device->handle, + ACPI_ADR_SPACE_EC, + &acpi_wmi_ec_space_handler); + pr_err("Failed to parse WDG method\n"); + return error; + } + + return 0; } static int __init acpi_wmi_init(void) { - int result; - - INIT_LIST_HEAD(&wmi_blocks.list); + int error; if (acpi_disabled) return -ENODEV; - result = acpi_bus_register_driver(&acpi_wmi_driver); + error = class_register(&wmi_class); + if (error) + return error; - if (result < 0) { - printk(KERN_INFO PREFIX "Error loading mapper\n"); - return -ENODEV; + error = acpi_bus_register_driver(&acpi_wmi_driver); + if (error) { + pr_err("Error loading mapper\n"); + class_unregister(&wmi_class); + return error; } - result = wmi_class_init(); - if (result) { - acpi_bus_unregister_driver(&acpi_wmi_driver); - return result; - } - - printk(KERN_INFO PREFIX "Mapper loaded\n"); - - return result; + pr_info("Mapper loaded\n"); + return 0; } static void __exit acpi_wmi_exit(void) { - struct list_head *p, *tmp; - struct wmi_block *wblock; - - wmi_class_exit(); - acpi_bus_unregister_driver(&acpi_wmi_driver); + class_unregister(&wmi_class); - list_for_each_safe(p, tmp, &wmi_blocks.list) { - wblock = list_entry(p, struct wmi_block, list); - - list_del(p); - kfree(wblock); - } - - printk(KERN_INFO PREFIX "Mapper unloaded\n"); + pr_info("Mapper unloaded\n"); } subsys_initcall(acpi_wmi_init); diff --git a/drivers/platform/x86/xo1-rfkill.c b/drivers/platform/x86/xo1-rfkill.c new file mode 100644 index 00000000000..4bd17248dfc --- /dev/null +++ b/drivers/platform/x86/xo1-rfkill.c @@ -0,0 +1,84 @@ +/* + * Support for rfkill through the OLPC XO-1 laptop embedded controller + * + * Copyright (C) 2010 One Laptop per Child + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rfkill.h> +#include <linux/olpc-ec.h> + +static bool card_blocked; + +static int rfkill_set_block(void *data, bool blocked) +{ + unsigned char cmd; + int r; + + if (blocked == card_blocked) + return 0; + + if (blocked) + cmd = EC_WLAN_ENTER_RESET; + else + cmd = EC_WLAN_LEAVE_RESET; + + r = olpc_ec_cmd(cmd, NULL, 0, NULL, 0); + if (r == 0) + card_blocked = blocked; + + return r; +} + +static const struct rfkill_ops rfkill_ops = { + .set_block = rfkill_set_block, +}; + +static int xo1_rfkill_probe(struct platform_device *pdev) +{ + struct rfkill *rfk; + int r; + + rfk = rfkill_alloc(pdev->name, &pdev->dev, RFKILL_TYPE_WLAN, + &rfkill_ops, NULL); + if (!rfk) + return -ENOMEM; + + r = rfkill_register(rfk); + if (r) { + rfkill_destroy(rfk); + return r; + } + + platform_set_drvdata(pdev, rfk); + return 0; +} + +static int xo1_rfkill_remove(struct platform_device *pdev) +{ + struct rfkill *rfk = platform_get_drvdata(pdev); + rfkill_unregister(rfk); + rfkill_destroy(rfk); + return 0; +} + +static struct platform_driver xo1_rfkill_driver = { + .driver = { + .name = "xo1-rfkill", + .owner = THIS_MODULE, + }, + .probe = xo1_rfkill_probe, + .remove = xo1_rfkill_remove, +}; + +module_platform_driver(xo1_rfkill_driver); + +MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:xo1-rfkill"); diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c new file mode 100644 index 00000000000..49cbccec6e2 --- /dev/null +++ b/drivers/platform/x86/xo15-ebook.c @@ -0,0 +1,172 @@ +/* + * OLPC XO-1.5 ebook switch driver + * (based on generic ACPI button driver) + * + * Copyright (C) 2009 Paul Fox <pgf@laptop.org> + * Copyright (C) 2010 One Laptop per Child + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/input.h> +#include <linux/acpi.h> + +#define MODULE_NAME "xo15-ebook" + +#define XO15_EBOOK_CLASS MODULE_NAME +#define XO15_EBOOK_TYPE_UNKNOWN 0x00 +#define XO15_EBOOK_NOTIFY_STATUS 0x80 + +#define XO15_EBOOK_SUBCLASS "ebook" +#define XO15_EBOOK_HID "XO15EBK" +#define XO15_EBOOK_DEVICE_NAME "EBook Switch" + +ACPI_MODULE_NAME(MODULE_NAME); + +MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); +MODULE_LICENSE("GPL"); + +static const struct acpi_device_id ebook_device_ids[] = { + { XO15_EBOOK_HID, 0 }, + { "", 0 }, +}; +MODULE_DEVICE_TABLE(acpi, ebook_device_ids); + +struct ebook_switch { + struct input_dev *input; + char phys[32]; /* for input device */ +}; + +static int ebook_send_state(struct acpi_device *device) +{ + struct ebook_switch *button = acpi_driver_data(device); + unsigned long long state; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "EBK", NULL, &state); + if (ACPI_FAILURE(status)) + return -EIO; + + /* input layer checks if event is redundant */ + input_report_switch(button->input, SW_TABLET_MODE, !state); + input_sync(button->input); + return 0; +} + +static void ebook_switch_notify(struct acpi_device *device, u32 event) +{ + switch (event) { + case ACPI_FIXED_HARDWARE_EVENT: + case XO15_EBOOK_NOTIFY_STATUS: + ebook_send_state(device); + break; + default: + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "Unsupported event [0x%x]\n", event)); + break; + } +} + +#ifdef CONFIG_PM_SLEEP +static int ebook_switch_resume(struct device *dev) +{ + return ebook_send_state(to_acpi_device(dev)); +} +#endif + +static SIMPLE_DEV_PM_OPS(ebook_switch_pm, NULL, ebook_switch_resume); + +static int ebook_switch_add(struct acpi_device *device) +{ + struct ebook_switch *button; + struct input_dev *input; + const char *hid = acpi_device_hid(device); + char *name, *class; + int error; + + button = kzalloc(sizeof(struct ebook_switch), GFP_KERNEL); + if (!button) + return -ENOMEM; + + device->driver_data = button; + + button->input = input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto err_free_button; + } + + name = acpi_device_name(device); + class = acpi_device_class(device); + + if (strcmp(hid, XO15_EBOOK_HID)) { + pr_err("Unsupported hid [%s]\n", hid); + error = -ENODEV; + goto err_free_input; + } + + strcpy(name, XO15_EBOOK_DEVICE_NAME); + sprintf(class, "%s/%s", XO15_EBOOK_CLASS, XO15_EBOOK_SUBCLASS); + + snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid); + + input->name = name; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + input->dev.parent = &device->dev; + + input->evbit[0] = BIT_MASK(EV_SW); + set_bit(SW_TABLET_MODE, input->swbit); + + error = input_register_device(input); + if (error) + goto err_free_input; + + ebook_send_state(device); + + if (device->wakeup.flags.valid) { + /* Button's GPE is run-wake GPE */ + acpi_enable_gpe(device->wakeup.gpe_device, + device->wakeup.gpe_number); + device_set_wakeup_enable(&device->dev, true); + } + + return 0; + + err_free_input: + input_free_device(input); + err_free_button: + kfree(button); + return error; +} + +static int ebook_switch_remove(struct acpi_device *device) +{ + struct ebook_switch *button = acpi_driver_data(device); + + input_unregister_device(button->input); + kfree(button); + return 0; +} + +static struct acpi_driver xo15_ebook_driver = { + .name = MODULE_NAME, + .class = XO15_EBOOK_CLASS, + .ids = ebook_device_ids, + .ops = { + .add = ebook_switch_add, + .remove = ebook_switch_remove, + .notify = ebook_switch_notify, + }, + .drv.pm = &ebook_switch_pm, +}; +module_acpi_driver(xo15_ebook_driver); |
