diff options
Diffstat (limited to 'drivers/platform/x86')
56 files changed, 18741 insertions, 5780 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index faec777b1ed..172f26ce59a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -18,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. @@ -52,41 +53,56 @@ 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" @@ -99,6 +115,29 @@ config DELL_WMI  	  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 @@ -126,16 +165,69 @@ 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 @@ -154,7 +246,8 @@ config MSI_LAPTOP  	depends on ACPI  	depends on BACKLIGHT_CLASS_DEVICE  	depends on RFKILL -	depends on SERIO_I8042 +	depends on INPUT && SERIO_I8042 +	select INPUT_SPARSEKMAP  	---help---  	  This is a driver for laptops built by MSI (MICRO-STAR  	  INTERNATIONAL): @@ -182,23 +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" @@ -225,9 +316,13 @@ config SONYPI_COMPAT  config IDEAPAD_LAPTOP  	tristate "Lenovo IdeaPad Laptop Extras"  	depends on ACPI -	depends on RFKILL +	depends on RFKILL && INPUT +	depends on SERIO_I8042 +	depends on BACKLIGHT_CLASS_DEVICE +	select INPUT_SPARSEKMAP  	help -	  This is a driver for the rfkill switches on Lenovo IdeaPad netbooks. +	  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" @@ -399,10 +494,9 @@ 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 @@ -417,20 +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 EEEPC_WMI -	tristate "Eee PC WMI Hotkey Driver (EXPERIMENTAL)" +config ASUS_WMI +	tristate "ASUS WMI Driver"  	depends on ACPI_WMI  	depends on INPUT -	depends on EXPERIMENTAL +	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 want to support WMI-based hotkeys on Eee PC laptops. +	  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 eeepc-wmi. +	  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" @@ -465,38 +592,6 @@ 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 @@ -510,11 +605,13 @@ config TOPSTAR_LAPTOP  config ACPI_TOSHIBA  	tristate "Toshiba Laptop Extras"  	depends on ACPI -	depends on LEDS_CLASS -	depends on NEW_LEDS +	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 INPUT_SPARSEKMAP  	---help--- @@ -569,13 +666,22 @@ config ACPI_CMPC  config INTEL_SCU_IPC  	bool "Intel SCU IPC Support" -	depends on X86_MRST +	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 @@ -583,27 +689,20 @@ config GPIO_INTEL_PMIC  	  Say Y here to support GPIO via the SCU IPC interface  	  on Intel MID platforms. -config RAR_REGISTER -	bool "Restricted Access Region Register Driver" -	depends on PCI && X86_MRST -	default n -	---help--- -	  This driver allows other kernel drivers access to the -	  contents of the restricted access region control registers. - -	  The restricted access region control registers -	  (rar_registers) are used to pass address and -	  locking information on restricted access regions -	  to other drivers that use restricted access regions. +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. -	  The restricted access regions are regions of memory -	  on the Intel MID Platform that are not accessible to -	  the x86 processor, but are accessible to dedicated -	  processors on board peripheral devices. +	  If unsure, say N. -	  The purpose of the restricted access regions is to -	  protect sensitive data from compromise by unauthorized -	  programs running on the x86 processor. +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" @@ -633,10 +732,110 @@ config IBM_RTL  config XO1_RFKILL  	tristate "OLPC XO-1 software RF kill switch" -	depends on OLPC +	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 9950ccc940b..c4ca428fd3d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -3,6 +3,8 @@  # 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 @@ -10,26 +12,48 @@ 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_RAR_REGISTER)	+= intel_rar_register.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 c8c65375bfe..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> @@ -37,18 +39,14 @@  #include <linux/workqueue.h>  #include <linux/debugfs.h>  #include <linux/slab.h> - -#include <acpi/acpi_drivers.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 -  /*   * Magic Number   * Meaning is unknown - this number is required for writing to ACPI for AMW0 @@ -80,11 +78,123 @@ MODULE_LICENSE("GPL");   */  #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 @@ -94,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)  /* @@ -103,6 +214,7 @@ enum interface_flags {  	ACER_AMW0,  	ACER_AMW0_V2,  	ACER_WMID, +	ACER_WMID_v2,  };  #define ACER_DEFAULT_WIRELESS  0 @@ -116,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; @@ -140,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 { @@ -212,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 = { @@ -358,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"), +		}, +	},  	{}  }; @@ -419,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; @@ -450,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) @@ -484,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; @@ -556,6 +781,33 @@ 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; @@ -569,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;  	} @@ -649,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); @@ -658,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) @@ -673,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; @@ -710,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; @@ -753,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}; @@ -765,21 +1221,27 @@ 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; @@ -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,22 +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;  	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);  	} @@ -949,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); @@ -958,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); @@ -968,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));  } @@ -975,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;  } @@ -991,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, @@ -998,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;  } @@ -1050,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"); @@ -1061,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"); @@ -1078,6 +1692,8 @@ 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");  	} @@ -1085,6 +1701,273 @@ static ssize_t show_interface(struct device *dev, struct device_attribute *attr,  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   */ @@ -1100,9 +1983,14 @@ static u32 get_wmid_devices(void)  		return 0;  	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; +		}  	}  	kfree(out.pointer); @@ -1112,7 +2000,7 @@ static u32 get_wmid_devices(void)  /*   * Platform device   */ -static int __devinit acer_platform_probe(struct platform_device *device) +static int acer_platform_probe(struct platform_device *device)  {  	int err; @@ -1155,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; @@ -1166,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;  	} @@ -1177,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; @@ -1190,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; @@ -1249,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;  	} @@ -1270,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;  	} @@ -1289,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;  	} @@ -1304,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;  		}  	} @@ -1314,22 +2223,47 @@ static int __init acer_wmi_init(void)  		AMW0_find_mailled();  	if (!interface) { -		printk(ACER_INFO "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 (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) { -		printk(ACER_ERR "Unable to register platform driver.\n"); +		pr_err("Unable to register platform driver\n");  		goto error_platform_register;  	} @@ -1368,17 +2302,28 @@ error_device_add:  error_device_alloc:  	platform_driver_unregister(&acer_platform_driver);  error_platform_register: +	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_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 60f9cfcac93..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.24" +#define DRV_VER "0.5.26"  /*   * According to the Atom N270 datasheet, @@ -85,8 +83,8 @@ 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]; @@ -152,6 +150,8 @@ static const struct bios_settings_t bios_tbl[] = {  	{"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.3108", 0x55, 0x58, {0x9e, 0x00} },  	{"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, @@ -163,6 +163,7 @@ static const struct bios_settings_t bios_tbl[] = {  	{"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.3108", 0x55, 0x58, {0x9e, 0x00} },  	{"Acer", "Aspire 1810T",  "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, @@ -184,29 +185,45 @@ static const struct bios_settings_t bios_tbl[] = {  	{"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, 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", "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, 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", "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", "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} }  }; @@ -245,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;  	} @@ -265,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;  	} @@ -314,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;  	} @@ -499,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;  } @@ -588,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;  	} @@ -599,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; @@ -646,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; @@ -705,15 +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*:pnDOTMA*:"); +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 60a5a5c6b50..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   */ @@ -48,10 +49,11 @@  #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 <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> +#include <linux/dmi.h> +#include <linux/acpi.h>  #define ASUS_LAPTOP_VERSION	"0.42" @@ -69,36 +71,68 @@ 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 =) + * 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"); +		 "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"); +		 "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	/* (event & ~ATKD_BR_UP) = brightness level */ -#define ATKD_BR_DOWN	0x20	/* (event & ~ATKD_BR_DOWN) = britghness level */ -#define ATKD_BR_MIN	ATKD_BR_UP -#define ATKD_BR_MAX	(ATKD_BR_DOWN | 0xF)	/* 0x2f */ +#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 @@ -114,6 +148,13 @@ MODULE_PARM_DESC(bluetooth_status, "Set the wireless status on boot "   */  #define WL_RSTS		0x01	/* internal Wifi */  #define BT_RSTS		0x02	/* internal Bluetooth */ +#define WM_RSTS		0x08    /* internal wimax */ +#define WW_RSTS		0x20    /* internal wwan */ + +/* WLED and BLED type */ +#define TYPE_UNKNOWN	0 +#define TYPE_LED	1 +#define TYPE_RFKILL	2  /* LED */  #define METHOD_MLED		"MLED" @@ -132,52 +173,20 @@ MODULE_PARM_DESC(bluetooth_status, "Set the wireless status on boot "   */  #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 */  #define METHOD_BRIGHTNESS_SET	"SPLV"  #define METHOD_BRIGHTNESS_GET	"GPLV" -/* Backlight */ -static acpi_handle lcd_switch_handle; -static char *lcd_switch_paths[] = { -  "\\_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 */ -  /* Display */  #define METHOD_SWITCH_DISPLAY	"SDSP" -static acpi_handle display_get_handle; -static char *display_get_paths[] = { -  /* 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"}; -  #define METHOD_ALS_CONTROL	"ALSC" /* Z71A Z71V */  #define METHOD_ALS_LEVEL	"ALSL" /* Z71A Z71V */ @@ -191,6 +200,29 @@ static char *display_get_paths[] = {  #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 +  /*   * Define a specific led structure to keep the main structure clean   */ @@ -203,6 +235,16 @@ struct asus_led {  };  /* + * Same thing for rfkill + */ +struct asus_rfkill { +	/* type of control. Maps to PEGA_* values or *_RSTS  */ +	int control_id; +	struct rfkill *rfkill; +	struct asus_laptop *asus; +}; + +/*   * This is the main structure, we can use it to store anything interesting   * about the hotk device   */ @@ -216,7 +258,10 @@ struct asus_laptop {  	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; @@ -225,11 +270,21 @@ struct asus_laptop {  	struct asus_led kled;  	struct workqueue_struct *led_workqueue; +	int wled_type; +	int bled_type;  	int wireless_status;  	bool have_rsts; -	int lcd_state; - -	struct rfkill *gps_rfkill; +	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;  	acpi_handle handle;	/* the handle of the hotk device */  	u32 ledd_status;	/* status of the LED display */ @@ -243,41 +298,69 @@ static const struct key_entry asus_keymap[] = {  	{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_SWITCHVIDEOMODE } }, -	{KE_KEY, 0x34, { KEY_SWITCHVIDEOMODE } }, +	{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 } }, +	{KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */  	{KE_KEY, 0x45, { KEY_PLAYPAUSE } }, -	{KE_KEY, 0x4c, { KEY_MEDIA } }, +	{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 } }, -	{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, 0x7E, { KEY_BLUETOOTH } }, -	{KE_KEY, 0x7D, { KEY_BLUETOOTH } }, +	{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_WLAN  } }, -	{KE_KEY, 0x8A, { KEY_PROG1 } }, +	{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, 0xc4, { KEY_KBDILLUMUP } }, -	{KE_KEY, 0xc5, { KEY_KBDILLUMDOWN } }, -	{KE_KEY, 0xb5, { KEY_CALC } }, +	{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},  }; @@ -336,12 +419,133 @@ static int acpi_check_handle(acpi_handle handle, const char *method,  	if (status != AE_OK) {  		if (ret) -			pr_warning("Error finding %s\n", method); +			pr_warn("Error finding %s\n", method);  		return -ENODEV;  	}  	return 0;  } +static bool asus_check_pega_lucid(struct asus_laptop *asus) +{ +	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); +} + +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); +} + +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); +} + +static void pega_accel_poll(struct input_polled_dev *ipd) +{ +	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; +	} + +	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 pega_accel_exit(struct asus_laptop *asus) +{ +	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; + +	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) @@ -401,7 +605,7 @@ static int asus_kled_lvl(struct asus_laptop *asus)  	rv = acpi_evaluate_integer(asus->handle, METHOD_KBD_LIGHT_GET,  				   ¶ms, &kblv);  	if (ACPI_FAILURE(rv)) { -		pr_warning("Error reading kled level\n"); +		pr_warn("Error reading kled level\n");  		return -ENODEV;  	}  	return kblv; @@ -415,7 +619,7 @@ static int asus_kled_set(struct asus_laptop *asus, int kblv)  		kblv = 0;  	if (write_acpi_int(asus->handle, METHOD_KBD_LIGHT_SET, kblv)) { -		pr_warning("Keyboard LED display write failed\n"); +		pr_warn("Keyboard LED display write failed\n");  		return -EINVAL;  	}  	return 0; @@ -449,17 +653,21 @@ static enum led_brightness asus_kled_cdev_get(struct led_classdev *led_cdev)  static void asus_led_exit(struct asus_laptop *asus)  { -	if (asus->mled.led.dev) +	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 (asus->tled.led.dev) +	if (!IS_ERR_OR_NULL(asus->tled.led.dev))  		led_classdev_unregister(&asus->tled.led); -	if (asus->pled.led.dev) +	if (!IS_ERR_OR_NULL(asus->pled.led.dev))  		led_classdev_unregister(&asus->pled.led); -	if (asus->rled.led.dev) +	if (!IS_ERR_OR_NULL(asus->rled.led.dev))  		led_classdev_unregister(&asus->rled.led); -	if (asus->gled.led.dev) +	if (!IS_ERR_OR_NULL(asus->gled.led.dev))  		led_classdev_unregister(&asus->gled.led); -	if (asus->kled.led.dev) +	if (!IS_ERR_OR_NULL(asus->kled.led.dev))  		led_classdev_unregister(&asus->kled.led);  	if (asus->led_workqueue) {  		destroy_workqueue(asus->led_workqueue); @@ -490,7 +698,14 @@ static int asus_led_register(struct asus_laptop *asus,  static int asus_led_init(struct asus_laptop *asus)  { -	int r; +	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;  	/*  	 * Functions that actually update the LED's are called from a @@ -502,6 +717,16 @@ static int asus_led_init(struct asus_laptop *asus)  	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; @@ -540,48 +765,6 @@ error:  /*   * Backlight device   */ -static int asus_lcd_status(struct asus_laptop *asus) -{ -	return asus->lcd_state; -} - -static int asus_lcd_set(struct asus_laptop *asus, int value) -{ -	int lcd = 0; -	acpi_status status = 0; - -	lcd = !!value; - -	if (lcd == asus_lcd_status(asus)) -		return 0; - -	if (!lcd_switch_handle) -		return -ENODEV; - -	status = acpi_evaluate_object(lcd_switch_handle, -				      NULL, NULL, NULL); - -	if (ACPI_FAILURE(status)) { -		pr_warning("Error switching LCD\n"); -		return -ENODEV; -	} - -	asus->lcd_state = lcd; -	return 0; -} - -static void lcd_blank(struct asus_laptop *asus, int blank) -{ -	struct backlight_device *bd = asus->backlight_device; - -	asus->lcd_state = (blank == FB_BLANK_UNBLANK); - -	if (bd) { -		bd->props.power = blank; -		backlight_update_status(bd); -	} -} -  static int asus_read_brightness(struct backlight_device *bd)  {  	struct asus_laptop *asus = bl_get_data(bd); @@ -591,7 +774,7 @@ static int asus_read_brightness(struct backlight_device *bd)  	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;  } @@ -601,7 +784,7 @@ static int asus_set_brightness(struct backlight_device *bd, int value)  	struct asus_laptop *asus = bl_get_data(bd);  	if (write_acpi_int(asus->handle, METHOD_BRIGHTNESS_SET, value)) { -		pr_warning("Error changing brightness\n"); +		pr_warn("Error changing brightness\n");  		return -EIO;  	}  	return 0; @@ -609,19 +792,12 @@ static int asus_set_brightness(struct backlight_device *bd, int value)  static int update_bl_status(struct backlight_device *bd)  { -	struct asus_laptop *asus = bl_get_data(bd); -	int rv;  	int value = bd->props.brightness; -	rv = asus_set_brightness(bd, value); -	if (rv) -		return rv; - -	value = (bd->props.power == FB_BLANK_UNBLANK) ? 1 : 0; -	return asus_lcd_set(asus, value); +	return asus_set_brightness(bd, value);  } -static struct backlight_ops asusbl_ops = { +static const struct backlight_ops asusbl_ops = {  	.get_brightness = asus_read_brightness,  	.update_status = update_bl_status,  }; @@ -642,12 +818,12 @@ static int asus_backlight_init(struct asus_laptop *asus)  	struct backlight_properties props;  	if (acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_GET, NULL) || -	    acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_SET, NULL) || -	    !lcd_switch_handle) +	    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, @@ -710,12 +886,14 @@ static ssize_t show_infos(struct device *dev,  	/*  	 * 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(asus->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 @@ -797,7 +975,7 @@ static ssize_t store_ledd(struct device *dev, struct device_attribute *attr,  	rv = parse_arg(buf, count, &value);  	if (rv > 0) {  		if (write_acpi_int(asus->handle, METHOD_LEDD, value)) { -			pr_warning("LED display write failed\n"); +			pr_warn("LED display write failed\n");  			return -ENODEV;  		}  		asus->ledd_status = (u32) value; @@ -819,7 +997,7 @@ static int asus_wireless_status(struct asus_laptop *asus, int mask)  	rv = acpi_evaluate_integer(asus->handle, METHOD_WL_STATUS,  				   NULL, &status);  	if (ACPI_FAILURE(rv)) { -		pr_warning("Error reading Wireless status\n"); +		pr_warn("Error reading Wireless status\n");  		return -EINVAL;  	}  	return !!(status & mask); @@ -831,7 +1009,7 @@ static int asus_wireless_status(struct asus_laptop *asus, int mask)  static int asus_wlan_set(struct asus_laptop *asus, int status)  {  	if (write_acpi_int(asus->handle, METHOD_WLAN, !!status)) { -		pr_warning("Error setting wlan status to %d", status); +		pr_warn("Error setting wlan status to %d\n", status);  		return -EIO;  	}  	return 0; @@ -853,13 +1031,13 @@ static ssize_t store_wlan(struct device *dev, struct device_attribute *attr,  	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_warning("Error setting bluetooth status to %d", status); +		pr_warn("Error setting bluetooth status to %d\n", status);  		return -EIO;  	}  	return 0; @@ -883,49 +1061,72 @@ static ssize_t store_bluetooth(struct device *dev,  }  /* - * Display + * Wimax   */ -static void asus_set_display(struct asus_laptop *asus, int value) +static int asus_wimax_set(struct asus_laptop *asus, int status)  { -	/* no sanity check needed for now */ -	if (write_acpi_int(asus->handle, METHOD_SWITCH_DISPLAY, value)) -		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(struct asus_laptop *asus) +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)); +} -	value &= 0x0F; /* needed for some models, shouldn't hurt others */ +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 value; +	return sysfs_acpi_set(asus, buf, count, METHOD_WIMAX);  }  /* - * 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() + * Wwan   */ -static ssize_t show_disp(struct device *dev, -			 struct device_attribute *attr, char *buf) +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; +} + +static ssize_t show_wwan(struct device *dev, +			      struct device_attribute *attr, char *buf)  {  	struct asus_laptop *asus = dev_get_drvdata(dev); -	if (!display_get_handle) -		return -ENODEV; -	return sprintf(buf, "%d\n", read_display(asus)); +	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); +} + +/* + * Display + */ +static void asus_set_display(struct asus_laptop *asus, int value) +{ +	/* no sanity check needed for now */ +	if (write_acpi_int(asus->handle, METHOD_SWITCH_DISPLAY, value)) +		pr_warn("Error setting display\n"); +	return;  }  /* @@ -952,8 +1153,18 @@ static ssize_t store_disp(struct device *dev, struct device_attribute *attr,   */  static void asus_als_switch(struct asus_laptop *asus, int value)  { -	if (write_acpi_int(asus->handle, METHOD_ALS_CONTROL, value)) +	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"); +  	asus->light_switch = value;  } @@ -981,7 +1192,7 @@ static ssize_t store_lssw(struct device *dev, struct device_attribute *attr,  static void asus_als_level(struct asus_laptop *asus, int value)  {  	if (write_acpi_int(asus->handle, METHOD_ALS_LEVEL, value)) -		pr_warning("Error setting light sensor level\n"); +		pr_warn("Error setting light sensor level\n");  	asus->light_level = value;  } @@ -1009,6 +1220,35 @@ static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr,  	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   */ @@ -1020,7 +1260,7 @@ static int asus_gps_status(struct asus_laptop *asus)  	rv = acpi_evaluate_integer(asus->handle, METHOD_GPS_STATUS,  				   NULL, &status);  	if (ACPI_FAILURE(rv)) { -		pr_warning("Error reading GPS status\n"); +		pr_warn("Error reading GPS status\n");  		return -ENODEV;  	}  	return !!status; @@ -1056,7 +1296,7 @@ static ssize_t store_gps(struct device *dev, struct device_attribute *attr,  	ret = asus_gps_switch(asus, !!value);  	if (ret)  		return ret; -	rfkill_set_sw_state(asus->gps_rfkill, !value); +	rfkill_set_sw_state(asus->gps.rfkill, !value);  	return rv;  } @@ -1074,46 +1314,177 @@ 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 const struct rfkill_ops asus_rfkill_ops = { +	.set_block = asus_rfkill_set, +}; + +static void asus_rfkill_terminate(struct asus_rfkill *rfk) +{ +	if (!rfk->rfkill) +		return ; + +	rfkill_unregister(rfk->rfkill); +	rfkill_destroy(rfk->rfkill); +	rfk->rfkill = NULL; +} +  static void asus_rfkill_exit(struct asus_laptop *asus)  { -	if (asus->gps_rfkill) { -		rfkill_unregister(asus->gps_rfkill); -		rfkill_destroy(asus->gps_rfkill); -		asus->gps_rfkill = NULL; -	} +	asus_rfkill_terminate(&asus->wwan); +	asus_rfkill_terminate(&asus->bluetooth); +	asus_rfkill_terminate(&asus->wlan); +	asus_rfkill_terminate(&asus->gps);  } -static int asus_rfkill_init(struct asus_laptop *asus) +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)  {  	int result; -	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)) -		return 0; - -	asus->gps_rfkill = rfkill_alloc("asus-gps", &asus->platform_device->dev, -					RFKILL_TYPE_GPS, -					&asus_gps_rfkill_ops, asus); -	if (!asus->gps_rfkill) +	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(asus->gps_rfkill); +	result = rfkill_register(rfk->rfkill);  	if (result) { -		rfkill_destroy(asus->gps_rfkill); -		asus->gps_rfkill = NULL; +		rfkill_destroy(rfk->rfkill); +		rfk->rfkill = NULL;  	}  	return result;  } +static int asus_rfkill_init(struct asus_laptop *asus) +{ +	int result = 0; + +	if (asus->is_pega_lucid) +		return -ENODEV; + +	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; + + +	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 int pega_rfkill_set(void *data, bool blocked) +{ +	struct asus_rfkill *rfk = data; + +	int ret = asus_pega_lucid_set(rfk->asus, rfk->control_id, !blocked); +	return ret; +} + +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) -		sparse_keymap_report_event(asus->inputdev, event, 1, true); +	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) @@ -1122,10 +1493,9 @@ static int asus_input_init(struct asus_laptop *asus)  	int error;  	input = input_allocate_device(); -	if (!input) { -		pr_info("Unable to allocate input device\n"); +	if (!input)  		return -ENOMEM; -	} +  	input->name = "Asus Laptop extra buttons";  	input->phys = ASUS_LAPTOP_FILE "/input0";  	input->id.bustype = BUS_HOST; @@ -1138,7 +1508,7 @@ static int asus_input_init(struct asus_laptop *asus)  	}  	error = input_register_device(input);  	if (error) { -		pr_info("Unable to register input device\n"); +		pr_warn("Unable to register input device\n");  		goto err_free_keymap;  	} @@ -1169,32 +1539,34 @@ static void asus_acpi_notify(struct acpi_device *device, u32 event)  	struct asus_laptop *asus = acpi_driver_data(device);  	u16 count; -	/* -	 * We need to tell the backlight device when the backlight power is -	 * switched -	 */ -	if (event == ATKD_LCD_ON) -		lcd_blank(asus, FB_BLANK_UNBLANK); -	else if (event == ATKD_LCD_OFF) -		lcd_blank(asus, FB_BLANK_POWERDOWN); -  	/* TODO Find a better way to handle events count. */  	count = asus->event_count[event % 128]++; -	acpi_bus_generate_proc_event(asus->device, event, count);  	acpi_bus_generate_netlink_event(asus->device->pnp.device_class,  					dev_name(&asus->device->dev), event,  					count); -	/* Brightness events are special */ -	if (event >= ATKD_BR_MIN && event <= ATKD_BR_MAX) { +	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; -		/* Ignore them completely if the acpi video driver is used */ +	/* 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 ;  		} +	} + +	/* Accelerometer "coarse orientation change" event */ +	if (asus->pega_accel_poll && event == 0xEA) { +		kobject_uevent(&asus->pega_accel_poll->input->dev.kobj, +			       KOBJ_CHANGE);  		return ;  	} +  	asus_input_notify(asus, event);  } @@ -1202,8 +1574,11 @@ 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(display, S_IRUGO | S_IWUSR, show_disp, store_disp); +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); @@ -1212,15 +1587,18 @@ 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_value.attr,  	&dev_attr_ls_level.attr,  	&dev_attr_ls_switch.attr,  	&dev_attr_gps.attr,  	NULL  }; -static mode_t asus_sysfs_is_visible(struct kobject *kobj, +static umode_t asus_sysfs_is_visible(struct kobject *kobj,  				    struct attribute *attr,  				    int idx)  { @@ -1230,6 +1608,19 @@ static mode_t asus_sysfs_is_visible(struct kobject *kobj,  	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; + +		return supported; +	} + +normal:  	if (attr == &dev_attr_wlan.attr) {  		supported = !acpi_check_handle(handle, METHOD_WLAN, NULL); @@ -1239,14 +1630,22 @@ static mode_t asus_sysfs_is_visible(struct kobject *kobj,  	} else if (attr == &dev_attr_display.attr) {  		supported = !acpi_check_handle(handle, METHOD_SWITCH_DISPLAY, NULL); +	} else if (attr == &dev_attr_wimax.attr) { +		supported = +			!acpi_check_handle(asus->handle, METHOD_WIMAX, NULL); + +	} else if (attr == &dev_attr_wwan.attr) { +		supported = !acpi_check_handle(asus->handle, METHOD_WWAN, NULL); +  	} else if (attr == &dev_attr_ledd.attr) {  		supported = !acpi_check_handle(handle, METHOD_LEDD, NULL);  	} 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); - +			!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) && @@ -1301,29 +1700,9 @@ static struct platform_driver platform_driver = {  	.driver = {  		.name = ASUS_LAPTOP_FILE,  		.owner = THIS_MODULE, -	} +	},  }; -static int asus_handle_init(char *name, acpi_handle * handle, -			    char **paths, int num_paths) -{ -	int i; -	acpi_status status; - -	for (i = 0; i < num_paths; i++) { -		status = acpi_get_handle(NULL, paths[i], handle); -		if (ACPI_SUCCESS(status)) -			return 0; -	} - -	*handle = NULL; -	return -ENODEV; -} - -#define ASUS_HANDLE_INIT(object)					\ -	asus_handle_init(#object, &object##_handle, object##_paths,	\ -			 ARRAY_SIZE(object##_paths)) -  /*   * 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 @@ -1333,7 +1712,7 @@ 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; @@ -1346,7 +1725,7 @@ static int asus_laptop_get_info(struct asus_laptop *asus)  	 */  	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_ret(asus->handle, "INIT", 0, &buffer)) { @@ -1358,7 +1737,7 @@ static int asus_laptop_get_info(struct asus_laptop *asus)  	status =  	    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); @@ -1392,32 +1771,18 @@ static int asus_laptop_get_info(struct asus_laptop *asus)  		return -ENOMEM;  	} -	if (*string) +	if (string)  		pr_notice("  %s model detected\n", string); -	/* -	 * 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. -	 */ -	status = -	    acpi_evaluate_integer(asus->handle, "HRWS", NULL, &hwrs_result); -	if (!ACPI_FAILURE(status)) -		pr_notice("  HRWS returned %x", (int)hwrs_result); -  	if (!acpi_check_handle(asus->handle, METHOD_WL_STATUS, NULL))  		asus->have_rsts = true; -	/* Scheduled for removal */ -	ASUS_HANDLE_INIT(lcd_switch); -	ASUS_HANDLE_INIT(display_get); -  	kfree(model);  	return AE_OK;  } -static int __devinit asus_acpi_init(struct asus_laptop *asus) +static int asus_acpi_init(struct asus_laptop *asus)  {  	int result = 0; @@ -1433,13 +1798,28 @@ static int __devinit asus_acpi_init(struct asus_laptop *asus)  	if (result)  		return result; -	/* WLED and BLED are on by default */ +	if (!strcmp(bled_type, "led")) +		asus->bled_type = TYPE_LED; +	else if (!strcmp(bled_type, "rfkill")) +		asus->bled_type = TYPE_RFKILL; + +	if (!strcmp(wled_type, "led")) +		asus->wled_type = TYPE_LED; +	else if (!strcmp(wled_type, "rfkill")) +		asus->wled_type = TYPE_RFKILL; +  	if (bluetooth_status >= 0)  		asus_bluetooth_set(asus, !!bluetooth_status);  	if (wlan_status >= 0)  		asus_wlan_set(asus, !!wlan_status); +	if (wimax_status >= 0) +		asus_wimax_set(asus, !!wimax_status); + +	if (wwan_status >= 0) +		asus_wwan_set(asus, !!wwan_status); +  	/* Keyboard Backlight is on by default */  	if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL))  		asus_kled_set(asus, 1); @@ -1448,22 +1828,37 @@ static int __devinit asus_acpi_init(struct asus_laptop *asus)  	asus->ledd_status = 0xFFF;  	/* Set initial values of light sensor and level */ -	asus->light_switch = 0;	/* Default to light sensor disabled */ +	asus->light_switch = !!als_status;  	asus->light_level = 5;	/* level 5 for sensor sensitivity */ -	if (!acpi_check_handle(asus->handle, METHOD_ALS_CONTROL, NULL) && -	    !acpi_check_handle(asus->handle, METHOD_ALS_LEVEL, NULL)) { +	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);  	} -	asus->lcd_state = 1; /* LCD should be on when the module load */  	return result;  } +static void asus_dmi_check(void) +{ +	const char *model; + +	model = dmi_get_system_info(DMI_PRODUCT_NAME); +	if (!model) +		return; + +	/* On L1400B WLED control the sound card, don't mess with it ... */ +	if (strncmp(model, "L1400B", 6) == 0) { +		wlan_status = -1; +	} +} +  static bool asus_device_present; -static int __devinit asus_acpi_add(struct acpi_device *device) +static int asus_acpi_add(struct acpi_device *device)  {  	struct asus_laptop *asus;  	int result; @@ -1479,14 +1874,17 @@ static int __devinit asus_acpi_add(struct acpi_device *device)  	device->driver_data = asus;  	asus->device = device; +	asus_dmi_check(); +  	result = asus_acpi_init(asus);  	if (result)  		goto fail_platform;  	/* -	 * Register the platform device first.  It is used as a parent for the -	 * sub-devices below. +	 * 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; @@ -1507,12 +1905,24 @@ static int __devinit asus_acpi_add(struct acpi_device *device)  		goto fail_led;  	result = asus_rfkill_init(asus); -	if (result) +	if (result && result != -ENODEV)  		goto fail_rfkill; +	result = pega_accel_init(asus); +	if (result && result != -ENODEV) +		goto fail_pega_accel; + +	result = pega_rfkill_init(asus); +	if (result && result != -ENODEV) +		goto fail_pega_rfkill; +  	asus_device_present = true;  	return 0; +fail_pega_rfkill: +	pega_accel_exit(asus); +fail_pega_accel: +	asus_rfkill_exit(asus);  fail_rfkill:  	asus_led_exit(asus);  fail_led: @@ -1522,13 +1932,12 @@ fail_input:  fail_backlight:  	asus_platform_exit(asus);  fail_platform: -	kfree(asus->name);  	kfree(asus);  	return result;  } -static int asus_acpi_remove(struct acpi_device *device, int type) +static int asus_acpi_remove(struct acpi_device *device)  {  	struct asus_laptop *asus = acpi_driver_data(device); @@ -1536,6 +1945,7 @@ static int asus_acpi_remove(struct acpi_device *device, int type)  	asus_rfkill_exit(asus);  	asus_led_exit(asus);  	asus_input_exit(asus); +	pega_accel_exit(asus);  	asus_platform_exit(asus);  	kfree(asus->name); 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 ca05aefd03b..00000000000 --- a/drivers/platform/x86/asus_acpi.c +++ /dev/null @@ -1,1531 +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/slab.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, "S1N", 3) == 0 || -		 strncmp(model, "S5N", 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"); -			hotk->methods = &model_conf[hotk->model]; -			kfree(model); -			return 0; -		} 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]; -	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 0; -} - -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) -{ -	struct backlight_properties props; -	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; -	} - -	memset(&props, 0, sizeof(struct backlight_properties)); -	props.max_brightness = 15; -	asus_backlight_device = backlight_device_register("asus", NULL, NULL, -							  &asus_backlight_data, -							  &props); -	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; -	} - -	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 341cbfef93e..70d355a9ae2 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -21,25 +21,30 @@  #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_SENSITIVITY_DEFAULT		5 +#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" +#define CMPC_KEYS_HID		"FNBT0000"  /*   * Generic input device code. @@ -76,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)  { @@ -198,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; @@ -276,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; @@ -333,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); +		}  	}  } @@ -347,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) @@ -357,19 +752,26 @@ 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[] = {  	{CMPC_TABLET_HID, 0}, @@ -384,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,  }; @@ -522,18 +924,20 @@ 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; -	if (blocked) -		state &= ~1; -	else -		state |= 1; -	status = cmpc_set_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;  } @@ -562,6 +966,7 @@ static int cmpc_ipml_add(struct acpi_device *acpi)  		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, @@ -594,7 +999,7 @@ out_bd:  	return retval;  } -static int cmpc_ipml_remove(struct acpi_device *acpi, int type) +static int cmpc_ipml_remove(struct acpi_device *acpi)  {  	struct ipml200_dev *ipml; @@ -653,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) @@ -672,7 +1078,7 @@ 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);  } @@ -719,8 +1125,15 @@ 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); @@ -736,6 +1149,7 @@ 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_ipml_acpi_driver); @@ -747,6 +1161,7 @@ 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}, diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index 097083cac41..7297df2ebf5 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c @@ -68,6 +68,8 @@   * 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> @@ -171,8 +173,7 @@  /* ======= */  struct compal_data{  	/* Fan control */ -	struct device *hwmon_dev; -	int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by moterboard */ +	int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by motherboard */  	unsigned char curr_pwm;  	/* Power supply */ @@ -187,7 +188,7 @@ struct compal_data{  /* =============== */  /* General globals */  /* =============== */ -static int force; +static bool force;  module_param(force, bool, 0);  MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); @@ -200,8 +201,8 @@ static bool extra_features;   * 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, 0); - * That address is labled "fan1 table information" in the service manual. + *	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 @@ -275,7 +276,7 @@ static int set_backlight_level(int level)  	ec_write(BACKLIGHT_LEVEL_ADDR, level); -	return 1; +	return 0;  }  static int get_backlight_level(void) @@ -286,7 +287,7 @@ static int get_backlight_level(void)  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, 0); +	ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0);  } @@ -294,24 +295,24 @@ static void set_backlight_state(bool on)  static void pwm_enable_control(void)  {  	unsigned char writeData = PWM_ENABLE_DATA; -	ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0, 0); +	ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0);  }  static void pwm_disable_control(void)  {  	unsigned char writeData = PWM_DISABLE_DATA; -	ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0, 0); +	ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0);  }  static void set_pwm(int pwm)  { -	ec_transaction(PWM_ADDRESS, &pwm_lookup_table[pwm], 1, NULL, 0, 0); +	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, 0); +	ec_transaction(FAN_ADDRESS, &data, 1, &value, 1);  	return 100 * (int)value;  } @@ -400,15 +401,6 @@ 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) - -/* General hwmon interface */ -static ssize_t hwmon_name_show(struct device *dev, -		struct device_attribute *attr, char *buf) -{ -	return sprintf(buf, "%s\n", DRIVER_NAME); -} - -  /* Fan control interface */  static ssize_t pwm_enable_show(struct device *dev,  		struct device_attribute *attr, char *buf) @@ -423,7 +415,8 @@ static ssize_t pwm_enable_store(struct device *dev,  	struct compal_data *data = dev_get_drvdata(dev);  	long val;  	int err; -	err = strict_strtol(buf, 10, &val); + +	err = kstrtol(buf, 10, &val);  	if (err)  		return err;  	if (val < 0) @@ -461,7 +454,8 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,  	struct compal_data *data = dev_get_drvdata(dev);  	long val;  	int err; -	err = strict_strtol(buf, 10, &val); + +	err = kstrtol(buf, 10, &val);  	if (err)  		return err;  	if (val < 0 || val > 255) @@ -661,65 +655,65 @@ static DEVICE_ATTR(wake_up_key,  static DEVICE_ATTR(wake_up_mouse,  		0644, wake_up_mouse_show,	wake_up_mouse_store); -static SENSOR_DEVICE_ATTR(name,        S_IRUGO, hwmon_name_show,   NULL, 1); -static SENSOR_DEVICE_ATTR(fan1_input,  S_IRUGO, fan_show,          NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp_cpu,          NULL, 1); -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, temp_cpu_local,    NULL, 1); -static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, temp_cpu_DTS,      NULL, 1); -static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, temp_northbridge,  NULL, 1); -static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, temp_vga,          NULL, 1); -static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, temp_SKIN,         NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, label_cpu,         NULL, 1); -static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, label_cpu_local,   NULL, 1); -static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, label_cpu_DTS,     NULL, 1); -static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, label_northbridge, NULL, 1); -static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, label_vga,         NULL, 1); -static SENSOR_DEVICE_ATTR(temp6_label, S_IRUGO, label_SKIN,        NULL, 1); -static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, pwm_show, pwm_store, 1); -static SENSOR_DEVICE_ATTR(pwm1_enable, -		S_IRUGO | S_IWUSR, pwm_enable_show, pwm_enable_store, 0); - -static struct attribute *compal_attributes[] = { +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, -	/* Maybe put the sensor-stuff in a separate hwmon-driver? That way, -	 * the hwmon sysfs won't be cluttered with the above files. */ -	&sensor_dev_attr_name.dev_attr.attr, -	&sensor_dev_attr_pwm1_enable.dev_attr.attr, -	&sensor_dev_attr_pwm1.dev_attr.attr, -	&sensor_dev_attr_fan1_input.dev_attr.attr, -	&sensor_dev_attr_temp1_input.dev_attr.attr, -	&sensor_dev_attr_temp2_input.dev_attr.attr, -	&sensor_dev_attr_temp3_input.dev_attr.attr, -	&sensor_dev_attr_temp4_input.dev_attr.attr, -	&sensor_dev_attr_temp5_input.dev_attr.attr, -	&sensor_dev_attr_temp6_input.dev_attr.attr, -	&sensor_dev_attr_temp1_label.dev_attr.attr, -	&sensor_dev_attr_temp2_label.dev_attr.attr, -	&sensor_dev_attr_temp3_label.dev_attr.attr, -	&sensor_dev_attr_temp4_label.dev_attr.attr, -	&sensor_dev_attr_temp5_label.dev_attr.attr, -	&sensor_dev_attr_temp6_label.dev_attr.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 __devinit compal_probe(struct platform_device *); -static int __devexit compal_remove(struct platform_device *); +static int compal_probe(struct platform_device *); +static int compal_remove(struct platform_device *);  static struct platform_driver compal_driver = {  	.driver = {  		.name = DRIVER_NAME,  		.owner = THIS_MODULE,  	},  	.probe	= compal_probe, -	.remove	= __devexit_p(compal_remove) +	.remove	= compal_remove,  };  static enum power_supply_property compal_bat_properties[] = { @@ -760,19 +754,17 @@ static struct rfkill *bt_rfkill;  static int dmi_check_cb(const struct dmi_system_id *id)  { -	printk(KERN_INFO DRIVER_NAME": Identified laptop model '%s'\n", -		id->ident); +	pr_info("Identified laptop model '%s'\n", id->ident);  	extra_features = false; -	return 0; +	return 1;  }  static int dmi_check_cb_extra(const struct dmi_system_id *id)  { -	printk(KERN_INFO DRIVER_NAME": Identified laptop model '%s', " -		"enabling extra features\n", +	pr_info("Identified laptop model '%s', enabling extra features\n",  		id->ident);  	extra_features = true; -	return 0; +	return 1;  }  static struct dmi_system_id __initdata compal_dmi_table[] = { @@ -872,8 +864,17 @@ static struct dmi_system_id __initdata compal_dmi_table[] = {  		},  		.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)  { @@ -948,20 +949,19 @@ static int __init compal_init(void)  	int ret;  	if (acpi_disabled) { -		printk(KERN_ERR DRIVER_NAME": ACPI needs to be enabled for " -						"this driver to work!\n"); +		pr_err("ACPI needs to be enabled for this driver to work!\n");  		return -ENODEV;  	}  	if (!force && !dmi_check_system(compal_dmi_table)) { -		printk(KERN_ERR DRIVER_NAME": Motherboard not recognized (You " -				"could try the module's force-parameter)"); +		pr_err("Motherboard not recognized (You could try the module's force-parameter)\n");  		return -ENODEV;  	}  	if (!acpi_video_backlight_support()) {  		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, @@ -989,8 +989,7 @@ static int __init compal_init(void)  	if (ret)  		goto err_rfkill; -	printk(KERN_INFO DRIVER_NAME": Driver "DRIVER_VERSION -						" successfully loaded\n"); +	pr_info("Driver " DRIVER_VERSION " successfully loaded\n");  	return 0;  err_rfkill: @@ -1008,32 +1007,32 @@ err_backlight:  	return ret;  } -static int __devinit compal_probe(struct platform_device *pdev) +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 = kzalloc(sizeof(struct compal_data), GFP_KERNEL); +	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_attribute_group); +	err = sysfs_create_group(&pdev->dev.kobj, &compal_platform_attr_group);  	if (err)  		return err; -	data->hwmon_dev = hwmon_device_register(&pdev->dev); -	if (IS_ERR(data->hwmon_dev)) { -		err = PTR_ERR(data->hwmon_dev); -		sysfs_remove_group(&pdev->dev.kobj, -				&compal_attribute_group); -		kfree(data); -		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 */ @@ -1043,6 +1042,10 @@ static int __devinit compal_probe(struct platform_device *pdev)  	platform_set_drvdata(pdev, data);  	return 0; + +remove: +	sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group); +	return err;  }  static void __exit compal_cleanup(void) @@ -1055,28 +1058,23 @@ static void __exit compal_cleanup(void)  	rfkill_destroy(wifi_rfkill);  	rfkill_destroy(bt_rfkill); -	printk(KERN_INFO DRIVER_NAME": Driver unloaded\n"); +	pr_info("Driver unloaded\n");  } -static int __devexit compal_remove(struct platform_device *pdev) +static int compal_remove(struct platform_device *pdev)  {  	struct compal_data *data;  	if (!extra_features)  		return 0; -	printk(KERN_INFO DRIVER_NAME": Unloading: resetting fan control " -							"to motherboard\n"); +	pr_info("Unloading: resetting fan control to motherboard\n");  	pwm_disable_control();  	data = platform_get_drvdata(pdev); -	hwmon_device_unregister(data->hwmon_dev);  	power_supply_unregister(&data->psy); -	platform_set_drvdata(pdev, NULL); -	kfree(data); - -	sysfs_remove_group(&pdev->dev.kobj, &compal_attribute_group); +	sysfs_remove_group(&pdev->dev.kobj, &compal_platform_attr_group);  	return 0;  } @@ -1090,16 +1088,3 @@ MODULE_AUTHOR("Roald Frederickx (roald.frederickx@gmail.com)");  MODULE_DESCRIPTION("Compal Laptop Support");  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:*"); -MODULE_ALIAS("dmi:*:rnJHL90:rvrREFERENCE:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron910:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1010:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1011:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1012:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1110:*"); -MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1210:*"); diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index cf8a89a0d8f..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> @@ -58,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; @@ -75,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; -static const struct dmi_system_id __initdata dell_device_table[] = { +module_param(force_rfkill, bool, 0444); +MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); + +static const struct dmi_system_id dell_device_table[] __initconst = {  	{  		.ident = "Dell laptop",  		.matches = { @@ -99,52 +121,154 @@ static const struct dmi_system_id __initdata dell_device_table[] = {  	},  	{ }  }; +MODULE_DEVICE_TABLE(dmi, dell_device_table); -static struct dmi_system_id __devinitdata dell_blacklist[] = { -	/* Supported by compal-laptop */ +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, +	},  	{ -		.ident = "Dell Mini 9", +		.callback = dmi_matched, +		.ident = "Dell Vostro 3450",  		.matches = {  			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"), +			DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"),  		}, +		.driver_data = &quirk_dell_vostro_v130,  	},  	{ -		.ident = "Dell Mini 10", +		.callback = dmi_matched, +		.ident = "Dell Inspiron 5420",  		.matches = {  			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"), +			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"),  		}, +		.driver_data = &quirk_dell_vostro_v130,  	},  	{ -		.ident = "Dell Mini 10v", +		.callback = dmi_matched, +		.ident = "Dell Inspiron 5520",  		.matches = {  			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"), +			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"),  		}, +		.driver_data = &quirk_dell_vostro_v130,  	},  	{ -		.ident = "Dell Mini 1012", +		.callback = dmi_matched, +		.ident = "Dell Inspiron 5720",  		.matches = {  			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"), +			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"),  		}, +		.driver_data = &quirk_dell_vostro_v130,  	},  	{ -		.ident = "Dell Inspiron 11z", +		.callback = dmi_matched, +		.ident = "Dell Inspiron 7420",  		.matches = {  			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"), +			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"),  		}, +		.driver_data = &quirk_dell_vostro_v130,  	},  	{ -		.ident = "Dell Mini 12", +		.callback = dmi_matched, +		.ident = "Dell Inspiron 7520",  		.matches = {  			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"), +			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; @@ -168,6 +292,7 @@ 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); @@ -180,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); @@ -197,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 */ @@ -284,42 +408,56 @@ static int dell_rfkill_set(void *data, bool blocked)  	int disable = blocked ? 1 : 0;  	unsigned long radio = (unsigned long)data;  	int hwswitch_bit = (unsigned long)data - 1; -	int ret = 0;  	get_buffer();  	dell_send_request(buffer, 17, 11);  	/* If the hardware switch controls this radio, and the hardware -	   switch is disabled, don't allow changing the software state */ +	   switch is disabled, always disable the radio */  	if ((hwswitch_state & BIT(hwswitch_bit)) && -	    !(buffer->output[1] & BIT(16))) { -		ret = -EINVAL; -		goto out; -	} +	    !(buffer->output[1] & BIT(16))) +		disable = 1;  	buffer->input[0] = (1 | (radio<<8) | (disable << 16));  	dell_send_request(buffer, 17, 11); -out:  	release_buffer(); -	return ret; +	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)  {  	int status; -	int bit = (unsigned long)data + 16; -	int hwswitch_bit = (unsigned long)data - 1;  	get_buffer();  	dell_send_request(buffer, 17, 11);  	status = buffer->output[1]; -	release_buffer(); -	rfkill_set_sw_state(rfkill, !!(status & BIT(bit))); +	dell_rfkill_update_hw_state(rfkill, (unsigned long)data, status); -	if (hwswitch_state & (BIT(hwswitch_bit))) -		rfkill_set_hw_state(rfkill, !(status & BIT(16))); +	release_buffer();  }  static const struct rfkill_ops dell_rfkill_ops = { @@ -398,26 +536,69 @@ static const struct file_operations dell_debugfs_fops = {  static void dell_update_rfkill(struct work_struct *ignored)  { -	if (wifi_rfkill) -		dell_rfkill_query(wifi_rfkill, (void *)1); -	if (bluetooth_rfkill) -		dell_rfkill_query(bluetooth_rfkill, (void *)2); -	if (wwan_rfkill) -		dell_rfkill_query(wwan_rfkill, (void *)3); +	int status; + +	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; -	int ret; +	int status, ret, whitelisted; +	const char *product; -	if (dmi_check_system(dell_blacklist)) { -		printk(KERN_INFO "dell-laptop: Blacklisted hardware detected - " -				"not enabling rfkill\n"); +	/* +	 * 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); @@ -427,6 +608,16 @@ static int __init dell_setup_rfkill(void)  	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,  					   RFKILL_TYPE_WLAN, @@ -468,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) @@ -519,7 +719,7 @@ static int dell_send_intensity(struct backlight_device *bd)  out:  	release_buffer(); -	return 0; +	return ret;  }  static int dell_get_intensity(struct backlight_device *bd) @@ -539,40 +739,55 @@ static int dell_get_intensity(struct backlight_device *bd)  	else  		dell_send_request(buffer, 0, 1); +	ret = buffer->output[1]; +  out:  	release_buffer(); -	if (ret) -		return ret; -	return buffer->output[1]; +	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 bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, -			      struct serio *port) +static void touchpad_led_on(void)  { -	static bool extended; +	int command = 0x97; +	char data = 1; +	i8042_command(&data, command | 1 << 12); +} -	if (str & 0x20) -		return false; +static void touchpad_led_off(void) +{ +	int command = 0x97; +	char data = 2; +	i8042_command(&data, command | 1 << 12); +} -	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)); -			break; -		} -		extended = false; -	} +static void touchpad_led_set(struct led_classdev *led_cdev, +	enum led_brightness value) +{ +	if (value > 0) +		touchpad_led_on(); +	else +		touchpad_led_off(); +} -	return false; +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) @@ -583,10 +798,14 @@ static int __init dell_init(void)  	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;  	} @@ -607,25 +826,21 @@ static int __init dell_init(void)  	 * is passed to SMI handler.  	 */  	bufferpage = alloc_page(GFP_KERNEL | GFP_DMA32); - -	if (!bufferpage) +	if (!bufferpage) { +		ret = -ENOMEM;  		goto fail_buffer; +	}  	buffer = page_address(bufferpage); -	mutex_init(&buffer_mutex);  	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;  	} -	ret = i8042_install_filter(dell_laptop_i8042_filter); -	if (ret) { -		printk(KERN_WARNING -		       "dell-laptop: Unable to install key filter\n"); -		goto fail_filter; -	} +	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) @@ -651,6 +866,7 @@ static int __init dell_init(void)  	if (max_intensity) {  		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, @@ -674,7 +890,6 @@ static int __init dell_init(void)  fail_backlight:  	i8042_remove_filter(dell_laptop_i8042_filter);  	cancel_delayed_work_sync(&dell_rfkill_work); -fail_filter:  	dell_cleanup_rfkill();  fail_rfkill:  	free_page((unsigned long)bufferpage); @@ -692,6 +907,8 @@ 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); @@ -710,6 +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:*"); -MODULE_ALIAS("dmi:*svnDellInc.:*:ct9:*"); -MODULE_ALIAS("dmi:*svnDellComputerCorporation.:*: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 77f1d55414c..390e8e33d5e 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -23,6 +23,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> @@ -30,7 +32,6 @@  #include <linux/types.h>  #include <linux/input.h>  #include <linux/input/sparse-keymap.h> -#include <acpi/acpi_drivers.h>  #include <linux/acpi.h>  #include <linux/string.h>  #include <linux/dmi.h> @@ -52,6 +53,8 @@ MODULE_ALIAS("wmi:"DELL_EVENT_GUID);   */  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 } }, @@ -83,6 +86,11 @@ static const struct key_entry dell_wmi_legacy_keymap[] __initconst = {  	{ 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 } }, @@ -90,6 +98,9 @@ static const struct key_entry dell_wmi_legacy_keymap[] __initconst = {  	{ 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 }  }; @@ -118,7 +129,9 @@ static const u16 bios_to_linux_keycode[256] __initconst = {  	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, @@ -127,8 +140,7 @@ static const u16 bios_to_linux_keycode[256] __initconst = {  	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 input_dev *dell_wmi_input_dev; @@ -141,7 +153,7 @@ static void dell_wmi_notify(u32 value, void *context)  	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;  	} @@ -153,8 +165,8 @@ static void dell_wmi_notify(u32 value, void *context)  		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;  		} @@ -167,8 +179,7 @@ static void dell_wmi_notify(u32 value, void *context)  		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 @@ -275,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;  	} @@ -290,9 +301,7 @@ static int __init dell_wmi_init(void)  					 dell_wmi_notify, NULL);  	if (ACPI_FAILURE(status)) {  		dell_wmi_input_destroy(); -		printk(KERN_ERR -			"dell-wmi: Unable to register notify handler - %d\n", -			status); +		pr_err("Unable to register notify handler - %d\n", status);  		return -ENODEV;  	} diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index b2edfdcdcb8..9b0c57cd1d4 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -28,8 +28,7 @@  #include <linux/hwmon.h>  #include <linux/hwmon-sysfs.h>  #include <linux/slab.h> -#include <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> +#include <linux/acpi.h>  #include <linux/uaccess.h>  #include <linux/input.h>  #include <linux/input/sparse-keymap.h> @@ -166,7 +165,6 @@ struct eeepc_laptop {  	struct platform_device *platform_device;  	struct acpi_device *device;		/* the device we are in */ -	struct device *hwmon_device;  	struct backlight_device *backlight_device;  	struct input_dev *inputdev; @@ -190,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);  } @@ -228,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;  } @@ -243,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;  } @@ -261,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; @@ -417,7 +409,7 @@ static ssize_t store_cpufv_disabled(struct device *dev,  	switch (value) {  	case 0:  		if (eeepc->cpufv_disabled) -			pr_warning("cpufv enabled (not officially supported " +			pr_warn("cpufv enabled (not officially supported "  				"on this model)\n");  		eeepc->cpufv_disabled = false;  		return rv; @@ -529,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; @@ -543,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, @@ -557,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); @@ -574,8 +577,9 @@ 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); @@ -586,28 +590,37 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc)  		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_unlock; +			goto out_put_dev;  		} +  		absent = (l == 0xffffffff);  		if (blocked != absent) { -			pr_warning("BIOS says wireless lan is %s, " -					"but the pci device is %s\n", +			pr_warn("BIOS says wireless lan is %s, " +				"but the pci device is %s\n",  				blocked ? "blocked" : "unblocked",  				absent ? "absent" : "present"); -			pr_warning("skipped wireless hotplug as probably " -					"inappropriate for this model\n"); -			goto out_unlock; +			pr_warn("skipped wireless hotplug as probably " +				"inappropriate for this model\n"); +			goto out_put_dev;  		}  		if (!blocked) { @@ -615,27 +628,40 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc)  			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; @@ -643,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, @@ -660,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; @@ -682,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);  	}  } @@ -805,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); @@ -878,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) @@ -917,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, @@ -968,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);  } @@ -1034,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, @@ -1042,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;  }  /* @@ -1115,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,  }; @@ -1136,6 +1139,7 @@ static int eeepc_backlight_init(struct eeepc_laptop *eeepc)  	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, @@ -1169,10 +1173,8 @@ static int eeepc_input_init(struct eeepc_laptop *eeepc)  	int error;  	input = input_allocate_device(); -	if (!input) { -		pr_info("Unable to allocate input device\n"); +	if (!input)  		return -ENOMEM; -	}  	input->name = "Asus EeePC extra buttons";  	input->phys = EEEPC_LAPTOP_FILE "/input0"; @@ -1213,6 +1215,14 @@ static void eeepc_input_exit(struct eeepc_laptop *eeepc)  /*   * 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); @@ -1221,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); @@ -1249,12 +1258,11 @@ static void eeepc_acpi_notify(struct acpi_device *device, u32 event)  				* event will be desired value (or else ignored)  				*/  			} -			sparse_keymap_report_event(eeepc->inputdev, event, -						   1, true); +			eeepc_input_notify(eeepc, event);  		}  	} else {  		/* Everything else is a bona-fide keypress event */ -		sparse_keymap_report_event(eeepc->inputdev, event, 1, true); +		eeepc_input_notify(eeepc, event);  	}  } @@ -1310,7 +1318,7 @@ 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)) { @@ -1328,7 +1336,7 @@ static void cmsg_quirks(struct eeepc_laptop *eeepc)  	cmsg_quirk(eeepc, CM_ASL_TPD, "TPD");  } -static int __devinit eeepc_acpi_init(struct eeepc_laptop *eeepc) +static int eeepc_acpi_init(struct eeepc_laptop *eeepc)  {  	unsigned int init_flags;  	int result; @@ -1360,7 +1368,7 @@ static int __devinit 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 @@ -1372,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; @@ -1441,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: @@ -1454,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); diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c index 462ceab93f8..6112933f627 100644 --- a/drivers/platform/x86/eeepc-wmi.c +++ b/drivers/platform/x86/eeepc-wmi.c @@ -2,6 +2,7 @@   * 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> @@ -28,441 +29,249 @@  #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/dmi.h>  #include <linux/fb.h> -#include <linux/backlight.h> -#include <linux/platform_device.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/acpi.h> + +#include "asus-wmi.h"  #define	EEEPC_WMI_FILE	"eeepc-wmi" -MODULE_AUTHOR("Yong Wang <yong.y.wang@intel.com>"); +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" -#define EEEPC_WMI_MGMT_GUID	"97845ED0-4E6D-11DE-8A39-0800200C9A66"  MODULE_ALIAS("wmi:"EEEPC_WMI_EVENT_GUID); -MODULE_ALIAS("wmi:"EEEPC_WMI_MGMT_GUID); -#define NOTIFY_BRNUP_MIN	0x11 -#define NOTIFY_BRNUP_MAX	0x1f -#define NOTIFY_BRNDOWN_MIN	0x20 -#define NOTIFY_BRNDOWN_MAX	0x2e +static bool hotplug_wireless; -#define EEEPC_WMI_METHODID_DEVS	0x53564544 -#define EEEPC_WMI_METHODID_DSTS	0x53544344 -#define EEEPC_WMI_METHODID_CFVS	0x53564643 +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."); -#define EEEPC_WMI_DEVID_BACKLIGHT	0x00050012 +/* 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, 0x5d, { KEY_WLAN } }, -	{ KE_KEY, 0x32, { KEY_MUTE } }, -	{ KE_KEY, 0x31, { KEY_VOLUMEDOWN } },  	{ KE_KEY, 0x30, { KEY_VOLUMEUP } }, -	{ KE_IGNORE, NOTIFY_BRNDOWN_MIN, { KEY_BRIGHTNESSDOWN } }, -	{ KE_IGNORE, NOTIFY_BRNUP_MIN, { KEY_BRIGHTNESSUP } }, +	{ 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, 0x6b, { KEY_F13 } }, /* Disable Touchpad */ -	{ KE_KEY, 0xe1, { KEY_F14 } }, -	{ KE_KEY, 0xe9, { KEY_DISPLAY_OFF } }, -	{ KE_KEY, 0xe0, { KEY_PROG1 } }, -	{ KE_KEY, 0x5c, { KEY_F15 } }, +	{ 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},  }; -struct bios_args { -	u32	dev_id; -	u32	ctrl_param; +static struct quirk_entry quirk_asus_unknown = {  }; -struct eeepc_wmi { -	struct input_dev *inputdev; -	struct backlight_device *backlight_device; +static struct quirk_entry quirk_asus_1000h = { +	.hotplug_wireless = true,  }; -static struct platform_device *platform_device; - -static int eeepc_wmi_input_init(struct eeepc_wmi *eeepc) -{ -	int err; - -	eeepc->inputdev = input_allocate_device(); -	if (!eeepc->inputdev) -		return -ENOMEM; - -	eeepc->inputdev->name = "Eee PC WMI hotkeys"; -	eeepc->inputdev->phys = EEEPC_WMI_FILE "/input0"; -	eeepc->inputdev->id.bustype = BUS_HOST; -	eeepc->inputdev->dev.parent = &platform_device->dev; - -	err = sparse_keymap_setup(eeepc->inputdev, eeepc_wmi_keymap, NULL); -	if (err) -		goto err_free_dev; +static struct quirk_entry quirk_asus_et2012_type1 = { +	.store_backlight_power = true, +}; -	err = input_register_device(eeepc->inputdev); -	if (err) -		goto err_free_keymap; +static struct quirk_entry quirk_asus_et2012_type3 = { +	.scalar_panel_brightness = true, +	.store_backlight_power = true, +}; -	return 0; +static struct quirk_entry quirk_asus_x101ch = { +	/* We need this when ACPI function doesn't do this well */ +	.wmi_backlight_power = true, +}; -err_free_keymap: -	sparse_keymap_free(eeepc->inputdev); -err_free_dev: -	input_free_device(eeepc->inputdev); -	return err; -} +static struct quirk_entry *quirks; -static void eeepc_wmi_input_exit(struct eeepc_wmi *eeepc) +static void et2012_quirks(void)  { -	if (eeepc->inputdev) { -		sparse_keymap_free(eeepc->inputdev); -		input_unregister_device(eeepc->inputdev); +	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; +		}  	} - -	eeepc->inputdev = NULL; -} - -static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *ctrl_param) -{ -	struct acpi_buffer input = { (acpi_size)sizeof(u32), &dev_id }; -	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; -	union acpi_object *obj; -	acpi_status status; -	u32 tmp; - -	status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, -			1, EEEPC_WMI_METHODID_DSTS, &input, &output); - -	if (ACPI_FAILURE(status)) -		return status; - -	obj = (union acpi_object *)output.pointer; -	if (obj && obj->type == ACPI_TYPE_INTEGER) -		tmp = (u32)obj->integer.value; -	else -		tmp = 0; - -	if (ctrl_param) -		*ctrl_param = tmp; - -	kfree(obj); - -	return status; - -} - -static acpi_status eeepc_wmi_set_devstate(u32 dev_id, u32 ctrl_param) -{ -	struct bios_args args = { -		.dev_id = dev_id, -		.ctrl_param = ctrl_param, -	}; -	struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; -	acpi_status status; - -	status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, -			1, EEEPC_WMI_METHODID_DEVS, &input, NULL); - -	return status; -} - -static int read_brightness(struct backlight_device *bd) -{ -	static u32 ctrl_param; -	acpi_status status; - -	status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_BACKLIGHT, &ctrl_param); - -	if (ACPI_FAILURE(status)) -		return -1; -	else -		return ctrl_param & 0xFF;  } -static int update_bl_status(struct backlight_device *bd) +static int dmi_matched(const struct dmi_system_id *dmi)  { +	char *model; -	static u32 ctrl_param; -	acpi_status status; - -	ctrl_param = bd->props.brightness; +	quirks = dmi->driver_data; -	status = eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_BACKLIGHT, ctrl_param); +	model = (char *)dmi->matches[1].substr; +	if (unlikely(strncmp(model, "ET2012", 6) == 0)) +		et2012_quirks(); -	if (ACPI_FAILURE(status)) -		return -1; -	else -		return 0; +	return 1;  } -static const struct backlight_ops eeepc_wmi_bl_ops = { -	.get_brightness = read_brightness, -	.update_status = update_bl_status, +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 int eeepc_wmi_backlight_notify(struct eeepc_wmi *eeepc, int code) -{ -	struct backlight_device *bd = eeepc->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 eeepc_wmi_backlight_init(struct eeepc_wmi *eeepc) +static void eeepc_wmi_key_filter(struct asus_wmi_driver *asus_wmi, int *code, +				 unsigned int *value, bool *autorelease)  { -	struct backlight_device *bd; -	struct backlight_properties props; - -	memset(&props, 0, sizeof(struct backlight_properties)); -	props.max_brightness = 15; -	bd = backlight_device_register(EEEPC_WMI_FILE, -				       &platform_device->dev, eeepc, -				       &eeepc_wmi_bl_ops, &props); -	if (IS_ERR(bd)) { -		pr_err("Could not register backlight device\n"); -		return PTR_ERR(bd); +	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;  	} - -	eeepc->backlight_device = bd; - -	bd->props.brightness = read_brightness(bd); -	bd->props.power = FB_BLANK_UNBLANK; -	backlight_update_status(bd); - -	return 0; -} - -static void eeepc_wmi_backlight_exit(struct eeepc_wmi *eeepc) -{ -	if (eeepc->backlight_device) -		backlight_device_unregister(eeepc->backlight_device); - -	eeepc->backlight_device = NULL;  } -static void eeepc_wmi_notify(u32 value, void *context) +static acpi_status eeepc_wmi_parse_device(acpi_handle handle, u32 level, +						 void *context, void **retval)  { -	struct eeepc_wmi *eeepc = context; -	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; -	union acpi_object *obj; -	acpi_status status; -	int code; -	int orig_code; - -	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) { -		code = obj->integer.value; -		orig_code = code; - -		if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) -			code = NOTIFY_BRNUP_MIN; -		else if (code >= NOTIFY_BRNDOWN_MIN && -			 code <= NOTIFY_BRNDOWN_MAX) -			code = NOTIFY_BRNDOWN_MIN; - -		if (code == NOTIFY_BRNUP_MIN || code == NOTIFY_BRNDOWN_MIN) { -			if (!acpi_video_backlight_support()) -				eeepc_wmi_backlight_notify(eeepc, orig_code); -		} - -		if (!sparse_keymap_report_event(eeepc->inputdev, -						code, 1, true)) -			pr_info("Unknown key %x pressed\n", code); -	} - -	kfree(obj); +	pr_warn("Found legacy ATKD device (%s)\n", EEEPC_ACPI_HID); +	*(bool *)context = true; +	return AE_CTRL_TERMINATE;  } -static int store_cpufv(struct device *dev, struct device_attribute *attr, -		       const char *buf, size_t count) +static int eeepc_wmi_check_atkd(void)  { -	int value; -	struct acpi_buffer input = { (acpi_size)sizeof(value), &value };  	acpi_status status; +	bool found = false; -	if (!count || sscanf(buf, "%i", &value) != 1) -		return -EINVAL; -	if (value < 0 || value > 2) -		return -EINVAL; - -	status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, -				     1, EEEPC_WMI_METHODID_CFVS, &input, NULL); - -	if (ACPI_FAILURE(status)) -		return -EIO; -	else -		return count; -} - -static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv); - -static void eeepc_wmi_sysfs_exit(struct platform_device *device) -{ -	device_remove_file(&device->dev, &dev_attr_cpufv); -} - -static int eeepc_wmi_sysfs_init(struct platform_device *device) -{ -	int retval = -ENOMEM; +	status = acpi_get_devices(EEEPC_ACPI_HID, eeepc_wmi_parse_device, +				  &found, NULL); -	retval = device_create_file(&device->dev, &dev_attr_cpufv); -	if (retval) -		goto error_sysfs; - -	return 0; - -error_sysfs: -	eeepc_wmi_sysfs_exit(platform_device); -	return retval; +	if (ACPI_FAILURE(status) || !found) +		return 0; +	return -1;  } -static int __devinit eeepc_wmi_platform_probe(struct platform_device *device) +static int eeepc_wmi_probe(struct platform_device *pdev)  { -	struct eeepc_wmi *eeepc; -	int err; -	acpi_status status; - -	eeepc = platform_get_drvdata(device); - -	err = eeepc_wmi_input_init(eeepc); -	if (err) -		goto error_input; - -	if (!acpi_video_backlight_support()) { -		err = eeepc_wmi_backlight_init(eeepc); -		if (err) -			goto error_backlight; -	} else -		pr_info("Backlight controlled by ACPI video driver\n"); - -	status = wmi_install_notify_handler(EEEPC_WMI_EVENT_GUID, -					eeepc_wmi_notify, eeepc); -	if (ACPI_FAILURE(status)) { -		pr_err("Unable to register notify handler - %d\n", -			status); -		err = -ENODEV; -		goto error_wmi; +	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; - -error_wmi: -	eeepc_wmi_backlight_exit(eeepc); -error_backlight: -	eeepc_wmi_input_exit(eeepc); -error_input: -	return err;  } -static int __devexit eeepc_wmi_platform_remove(struct platform_device *device) +static void eeepc_wmi_quirks(struct asus_wmi_driver *driver)  { -	struct eeepc_wmi *eeepc; +	quirks = &quirk_asus_unknown; +	quirks->hotplug_wireless = hotplug_wireless; -	eeepc = platform_get_drvdata(device); -	wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID); -	eeepc_wmi_backlight_exit(eeepc); -	eeepc_wmi_input_exit(eeepc); +	dmi_check_system(asus_quirks); -	return 0; +	driver->quirks = quirks; +	driver->quirks->wapf = -1; +	driver->panel_power = FB_BLANK_UNBLANK;  } -static struct platform_driver platform_driver = { -	.driver = { -		.name = EEEPC_WMI_FILE, -		.owner = THIS_MODULE, -	}, -	.probe = eeepc_wmi_platform_probe, -	.remove = __devexit_p(eeepc_wmi_platform_remove), +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)  { -	struct eeepc_wmi *eeepc; -	int err; - -	if (!wmi_has_guid(EEEPC_WMI_EVENT_GUID) || -	    !wmi_has_guid(EEEPC_WMI_MGMT_GUID)) { -		pr_warning("No known WMI GUID found\n"); -		return -ENODEV; -	} - -	eeepc = kzalloc(sizeof(struct eeepc_wmi), GFP_KERNEL); -	if (!eeepc) -		return -ENOMEM; - -	platform_device = platform_device_alloc(EEEPC_WMI_FILE, -1); -	if (!platform_device) { -		pr_warning("Unable to allocate platform device\n"); -		err = -ENOMEM; -		goto fail_platform; -	} - -	err = platform_device_add(platform_device); -	if (err) { -		pr_warning("Unable to add platform device\n"); -		goto put_dev; -	} - -	platform_set_drvdata(platform_device, eeepc); - -	err = platform_driver_register(&platform_driver); -	if (err) { -		pr_warning("Unable to register platform driver\n"); -		goto del_dev; -	} - -	err = eeepc_wmi_sysfs_init(platform_device); -	if (err) -		goto del_sysfs; - -	return 0; - -del_sysfs: -	eeepc_wmi_sysfs_exit(platform_device); -del_dev: -	platform_device_del(platform_device); -put_dev: -	platform_device_put(platform_device); -fail_platform: -	kfree(eeepc); - -	return err; +	return asus_wmi_register_driver(&asus_wmi_driver);  }  static void __exit eeepc_wmi_exit(void)  { -	struct eeepc_wmi *eeepc; - -	eeepc_wmi_sysfs_exit(platform_device); -	eeepc = platform_get_drvdata(platform_device); -	platform_driver_unregister(&platform_driver); -	platform_device_unregister(platform_device); -	kfree(eeepc); +	asus_wmi_unregister_driver(&asus_wmi_driver);  }  module_init(eeepc_wmi_init); diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index f44cd2620ff..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,7 +66,6 @@  #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) @@ -217,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); @@ -233,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", @@ -244,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) @@ -315,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", @@ -331,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; @@ -343,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", @@ -359,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; @@ -437,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,  }; @@ -584,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; @@ -652,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; @@ -689,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) */ @@ -721,7 +698,7 @@ 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); @@ -729,10 +706,10 @@ err_unregister_input_dev:  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; @@ -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 @@ -935,10 +905,10 @@ err_free_input_dev:  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; @@ -1128,6 +1098,7 @@ static int __init fujitsu_init(void)  		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, @@ -1168,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; @@ -1215,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); @@ -1240,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 index 067bf36d32f..777c7e3dda5 100644 --- a/drivers/platform/x86/hdaps.c +++ b/drivers/platform/x86/hdaps.c @@ -2,7 +2,7 @@   * hdaps.c - driver for IBM's Hard Drive Active Protection System   *   * Copyright (C) 2005 Robert Love <rml@novell.com> - * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.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 @@ -26,6 +26,8 @@   * 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> @@ -238,7 +240,7 @@ static int hdaps_device_init(void)  		     __check_latch(0x1611, 0x01))  		goto out; -	printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n", +	printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",  	       __get_latch(0x1611));  	outb(0x17, 0x1610); @@ -299,21 +301,25 @@ static int hdaps_probe(struct platform_device *dev)  	if (ret)  		return ret; -	printk(KERN_INFO "hdaps: device successfully initialized.\n"); +	pr_info("device successfully initialized\n");  	return 0;  } -static int hdaps_resume(struct platform_device *dev) +#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, -	.resume = hdaps_resume,  	.driver	= {  		.name = "hdaps",  		.owner = THIS_MODULE, +		.pm = &hdaps_pm,  	},  }; @@ -373,11 +379,11 @@ static ssize_t hdaps_variance_show(struct device *dev,  static ssize_t hdaps_temp1_show(struct device *dev,  				struct device_attribute *attr, char *buf)  { -	u8 temp; +	u8 uninitialized_var(temp);  	int ret;  	ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp); -	if (ret < 0) +	if (ret)  		return ret;  	return sprintf(buf, "%u\n", temp); @@ -386,11 +392,11 @@ static ssize_t hdaps_temp1_show(struct device *dev,  static ssize_t hdaps_temp2_show(struct device *dev,  				struct device_attribute *attr, char *buf)  { -	u8 temp; +	u8 uninitialized_var(temp);  	int ret;  	ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp); -	if (ret < 0) +	if (ret)  		return ret;  	return sprintf(buf, "%u\n", temp); @@ -480,7 +486,7 @@ static struct attribute_group hdaps_attribute_group = {  /* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */  static int __init hdaps_dmi_match(const struct dmi_system_id *id)  { -	printk(KERN_INFO "hdaps: %s detected.\n", id->ident); +	pr_info("%s detected\n", id->ident);  	return 1;  } @@ -488,8 +494,7 @@ static int __init hdaps_dmi_match(const struct dmi_system_id *id)  static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)  {  	hdaps_invert = (unsigned long)id->driver_data; -	printk(KERN_INFO "hdaps: inverting axis (%u) readings.\n", -	       hdaps_invert); +	pr_info("inverting axis (%u) readings\n", hdaps_invert);  	return hdaps_dmi_match(id);  } @@ -543,7 +548,7 @@ static int __init hdaps_init(void)  	int ret;  	if (!dmi_check_system(hdaps_whitelist)) { -		printk(KERN_WARNING "hdaps: supported laptop not found!\n"); +		pr_warn("supported laptop not found!\n");  		ret = -ENODEV;  		goto out;  	} @@ -595,7 +600,7 @@ static int __init hdaps_init(void)  	if (ret)  		goto out_idev; -	printk(KERN_INFO "hdaps: driver successfully loaded.\n"); +	pr_info("driver successfully loaded\n");  	return 0;  out_idev: @@ -609,7 +614,7 @@ out_driver:  out_region:  	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);  out: -	printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret); +	pr_warn("driver init failed (ret=%d)!\n", ret);  	return ret;  } @@ -622,7 +627,7 @@ static void __exit hdaps_exit(void)  	platform_driver_unregister(&hdaps_driver);  	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS); -	printk(KERN_INFO "hdaps: driver unloaded.\n"); +	pr_info("driver unloaded\n");  }  module_init(hdaps_init); 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 1dac659b5e0..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,6 +24,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> @@ -50,15 +53,17 @@ 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 PREFIX "HP WMI: " -#define UNIMP "Unimplemented " +#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,  };  enum hp_wmi_event_ids { @@ -69,12 +74,16 @@ enum hp_wmi_event_ids {  	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,  }; -static int __devinit 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); -  struct bios_args {  	u32 signature;  	u32 command; @@ -86,7 +95,46 @@ struct bios_args {  struct bios_return {  	u32 sigpass;  	u32 return_code; -	u32 value; +}; + +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 hp_wireless2_bits { +	HPWMI_POWER_STATE	= 0x01, +	HPWMI_POWER_SOFT	= 0x02, +	HPWMI_POWER_BIOS	= 0x04, +	HPWMI_POWER_HARD	= 0x08, +}; + +#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[] = { @@ -97,6 +145,7 @@ static const struct key_entry hp_wmi_keymap[] = {  	{ 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 }  }; @@ -107,21 +156,16 @@ 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];  /*   * hp_wmi_perform_query @@ -129,7 +173,8 @@ static struct platform_driver hp_wmi_driver = {   * 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 - * buffersize:	Size of buffer + * insize:	Size of input buffer + * outsize:	Size of output buffer   *   * returns zero on success   *         an HP WMI query specific error code (which is positive) @@ -140,25 +185,30 @@ static struct platform_driver hp_wmi_driver = {   *       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, 128) + *       ret = hp_wmi_perform_query(0x7, 0, buffer, 1, 128)   */ -static int hp_wmi_perform_query(int query, int write, u32 *buffer, -				int buffersize) +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 = buffersize, -		.data = *buffer, +		.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; @@ -169,9 +219,26 @@ static int hp_wmi_perform_query(int query, int write, u32 *buffer,  		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; +	} -	memcpy(buffer, &bios_return.value, sizeof(bios_return.value)); +	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);  	return 0;  } @@ -179,7 +246,7 @@ static int hp_wmi_display_state(void)  {  	int state = 0;  	int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state, -				       sizeof(state)); +				       sizeof(state), sizeof(state));  	if (ret)  		return -EINVAL;  	return state; @@ -189,7 +256,7 @@ static int hp_wmi_hddtemp_state(void)  {  	int state = 0;  	int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, &state, -				       sizeof(state)); +				       sizeof(state), sizeof(state));  	if (ret)  		return -EINVAL;  	return state; @@ -199,7 +266,7 @@ static int hp_wmi_als_state(void)  {  	int state = 0;  	int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, &state, -				       sizeof(state)); +				       sizeof(state), sizeof(state));  	if (ret)  		return -EINVAL;  	return state; @@ -209,7 +276,7 @@ static int hp_wmi_dock_state(void)  {  	int state = 0;  	int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, -				       sizeof(state)); +				       sizeof(state), sizeof(state));  	if (ret)  		return -EINVAL; @@ -221,13 +288,37 @@ static int hp_wmi_tablet_state(void)  {  	int state = 0;  	int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, -				       sizeof(state)); +				       sizeof(state), sizeof(state));  	if (ret)  		return ret;  	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 (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; @@ -235,7 +326,7 @@ static int hp_wmi_set_block(void *data, bool blocked)  	int ret;  	ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, -				   &query, sizeof(query)); +				   &query, sizeof(query), 0);  	if (ret)  		return -EINVAL;  	return 0; @@ -250,7 +341,8 @@ static bool hp_wmi_get_sw_state(enum hp_wmi_radio r)  	int wireless = 0;  	int mask;  	hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, -			     &wireless, sizeof(wireless)); +			     &wireless, sizeof(wireless), +			     sizeof(wireless));  	/* TBD: Pass error */  	mask = 0x200 << (r * 8); @@ -266,7 +358,8 @@ static bool hp_wmi_get_hw_state(enum hp_wmi_radio r)  	int wireless = 0;  	int mask;  	hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, -			     &wireless, sizeof(wireless)); +			     &wireless, sizeof(wireless), +			     sizeof(wireless));  	/* TBD: Pass error */  	mask = 0x800 << (r * 8); @@ -277,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)  { @@ -322,12 +469,43 @@ static ssize_t show_tablet(struct device *dev, struct device_attribute *attr,  	return sprintf(buf, "%d\n", value);  } +static ssize_t show_postcode(struct device *dev, struct device_attribute *attr, +			 char *buf) +{ +	/* 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 ssize_t set_als(struct device *dev, struct device_attribute *attr,  		       const char *buf, size_t count)  {  	u32 tmp = simple_strtoul(buf, NULL, 10);  	int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, &tmp, -				       sizeof(tmp)); +				       sizeof(tmp), sizeof(tmp)); +	if (ret) +		return -EINVAL; + +	return count; +} + +static ssize_t set_postcode(struct device *dev, struct device_attribute *attr, +		       const char *buf, size_t count) +{ +	int ret; +	u32 tmp; +	long unsigned int tmp2; + +	ret = kstrtoul(buf, 10, &tmp2); +	if (ret || tmp2 != 1) +		return -EINVAL; + +	/* 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; @@ -339,6 +517,7 @@ 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)  { @@ -351,7 +530,7 @@ static void hp_wmi_notify(u32 value, void *context)  	status = wmi_get_event_data(value, &response);  	if (status != AE_OK) { -		printk(KERN_INFO PREFIX "bad event status 0x%x\n", status); +		pr_info("bad event status 0x%x\n", status);  		return;  	} @@ -360,8 +539,7 @@ static void hp_wmi_notify(u32 value, void *context)  	if (!obj)  		return;  	if (obj->type != ACPI_TYPE_BUFFER) { -		printk(KERN_INFO "hp-wmi: Unknown response received %d\n", -		       obj->type); +		pr_info("Unknown response received %d\n", obj->type);  		kfree(obj);  		return;  	} @@ -378,8 +556,7 @@ static void hp_wmi_notify(u32 value, void *context)  		event_id = *location;  		event_data = *(location + 2);  	} else { -		printk(KERN_INFO "hp-wmi: Unknown buffer length %d\n", -		       obj->buffer.length); +		pr_info("Unknown buffer length %d\n", obj->buffer.length);  		kfree(obj);  		return;  	} @@ -400,16 +577,21 @@ static void hp_wmi_notify(u32 value, void *context)  	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)) -			printk(KERN_INFO PREFIX "Unknown key code - 0x%x\n", -			       key_code); +			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), @@ -422,16 +604,34 @@ 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)); +		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: -		printk(KERN_INFO PREFIX UNIMP "CPU throttle because of 3 Cell" -		       " battery event detected\n"); +		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: -		printk(KERN_INFO PREFIX "Unknown event_id - %d - 0x%x\n", -		       event_id, event_data); +		pr_info("Unknown event_id - %d - 0x%x\n", event_id, event_data);  		break;  	}  } @@ -463,6 +663,9 @@ static int __init hp_wmi_input_setup(void)  			    hp_wmi_tablet_state());  	input_sync(hp_wmi_input_dev); +	if (hp_wmi_bios_2009_later() == 4) +		hp_wmi_enable_hotkeys(); +  	status = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL);  	if (ACPI_FAILURE(status)) {  		err = -EIO; @@ -498,39 +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 __devinit hp_wmi_bios_setup(struct platform_device *device) +static int hp_wmi_rfkill_setup(struct platform_device *device)  {  	int err;  	int wireless = 0;  	err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless, -				   sizeof(wireless)); +				   sizeof(wireless), sizeof(wireless));  	if (err)  		return err; -	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; -  	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, @@ -545,6 +735,10 @@ static int __devinit 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, @@ -559,26 +753,177 @@ static int __devinit 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; @@ -586,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); @@ -600,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;  } @@ -620,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), @@ -632,16 +990,37 @@ 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 (!bios_capable && !event_capable) +		return -ENODEV; +  	if (event_capable) {  		err = hp_wmi_input_setup();  		if (err) @@ -649,34 +1028,29 @@ static int __init hp_wmi_init(void)  	}  	if (bios_capable) { -		err = platform_driver_register(&hp_wmi_driver); -		if (err) -			goto err_driver_reg; -		hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1); -		if (!hp_wmi_platform_dev) { -			err = -ENOMEM; -			goto err_device_alloc; +		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;  		} -		err = platform_device_add(hp_wmi_platform_dev); + +		err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);  		if (err) -			goto err_device_add; +			goto err_unregister_device;  	} -	if (!bios_capable && !event_capable) -		return -ENODEV; -  	return 0; -err_device_add: -	platform_device_put(hp_wmi_platform_dev); -err_device_alloc: -	platform_driver_unregister(&hp_wmi_driver); -err_driver_reg: +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)  { @@ -688,6 +1062,4 @@ static void __exit hp_wmi_exit(void)  		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 index 3c2c6b91ecb..97c2be195ef 100644 --- a/drivers/platform/x86/ibm_rtl.c +++ b/drivers/platform/x86/ibm_rtl.c @@ -22,15 +22,19 @@   *   */ +#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/sysdev.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"); @@ -68,9 +72,10 @@ struct ibm_rtl_table {  #define RTL_SIGNATURE 0x0000005f4c54525fULL  #define RTL_MASK      0x000000ffffffffffULL -#define RTL_DEBUG(A, ...) do { \ -	if (debug) \ -		pr_info("ibm-rtl: " A, ##__VA_ARGS__ ); \ +#define RTL_DEBUG(fmt, ...)				\ +do {							\ +	if (debug)					\ +		pr_info(fmt, ##__VA_ARGS__);		\  } while (0)  static DEFINE_MUTEX(rtl_lock); @@ -100,7 +105,7 @@ static int ibm_rtl_write(u8 value)  	int ret = 0, count = 0;  	static u32 cmd_port_val; -	RTL_DEBUG("%s(%d)\n", __FUNCTION__, value); +	RTL_DEBUG("%s(%d)\n", __func__, value);  	value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM; @@ -130,8 +135,8 @@ static int ibm_rtl_write(u8 value)  		while (ioread8(&rtl_table->command)) {  			msleep(10);  			if (count++ > 500) { -				pr_err("ibm-rtl: Hardware not responding to " -					"mode switch request\n"); +				pr_err("Hardware not responding to " +				       "mode switch request\n");  				ret = -EIO;  				break;  			} @@ -148,22 +153,22 @@ static int ibm_rtl_write(u8 value)  	return ret;  } -static ssize_t rtl_show_version(struct sysdev_class * dev, -                                struct sysdev_class_attribute *attr, +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 sysdev_class *dev, -                              struct sysdev_class_attribute *attr, +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 sysdev_class *dev, -                             struct sysdev_class_attribute *attr, +static ssize_t rtl_set_state(struct device *dev, +                             struct device_attribute *attr,                               const char *buf,                               size_t count)  { @@ -188,27 +193,28 @@ static ssize_t rtl_set_state(struct sysdev_class *dev,  	return ret;  } -static struct sysdev_class class_rtl = { +static struct bus_type rtl_subsys = {  	.name = "ibm_rtl", +	.dev_name = "ibm_rtl",  }; -static SYSDEV_CLASS_ATTR(version, S_IRUGO, rtl_show_version, NULL); -static SYSDEV_CLASS_ATTR(state, 0600, rtl_show_state, rtl_set_state); +static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL); +static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state); -static struct sysdev_class_attribute *rtl_attributes[] = { -	&attr_version, -	&attr_state, +static struct device_attribute *rtl_attributes[] = { +	&dev_attr_version, +	&dev_attr_state,  	NULL  };  static int rtl_setup_sysfs(void) {  	int ret, i; -	ret = sysdev_class_register(&class_rtl); +	ret = subsys_system_register(&rtl_subsys, NULL);  	if (!ret) {  		for (i = 0; rtl_attributes[i]; i ++) -			sysdev_class_create_file(&class_rtl, rtl_attributes[i]); +			device_create_file(rtl_subsys.dev_root, rtl_attributes[i]);  	}  	return ret;  } @@ -216,36 +222,17 @@ static int rtl_setup_sysfs(void) {  static void rtl_teardown_sysfs(void) {  	int i;  	for (i = 0; rtl_attributes[i]; i ++) -		sysdev_class_remove_file(&class_rtl, rtl_attributes[i]); -	sysdev_class_unregister(&class_rtl); -} - -static int dmi_check_cb(const struct dmi_system_id *id) -{ -	RTL_DEBUG("found IBM server '%s'\n", id->ident); -	return 0; +		device_remove_file(rtl_subsys.dev_root, rtl_attributes[i]); +	bus_unregister(&rtl_subsys);  } -#define ibm_dmi_entry(NAME, TYPE)                  \ -{                                                  \ -	.ident = NAME,                             \ -	.matches = {                               \ -		DMI_MATCH(DMI_SYS_VENDOR, "IBM"),  \ -		DMI_MATCH(DMI_PRODUCT_NAME, TYPE), \ -	},                                         \ -	.callback = dmi_check_cb                   \ -}  static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = { -	ibm_dmi_entry("BladeCenter LS21", "7971"), -	ibm_dmi_entry("BladeCenter LS22", "7901"), -	ibm_dmi_entry("BladeCenter HS21 XM", "7995"), -	ibm_dmi_entry("BladeCenter HS22", "7870"), -	ibm_dmi_entry("BladeCenter HS22V", "7871"), -	ibm_dmi_entry("System x3550 M2", "7946"), -	ibm_dmi_entry("System x3650 M2", "7947"), -	ibm_dmi_entry("System x3550 M3", "7944"), -	ibm_dmi_entry("System x3650 M3", "7945"), +	{                                                  \ +		.matches = {                               \ +			DMI_MATCH(DMI_SYS_VENDOR, "IBM"),  \ +		},                                         \ +	},  	{ }  }; @@ -255,9 +242,9 @@ static int __init ibm_rtl_init(void) {  	int ret = -ENODEV, i;  	if (force) -		pr_warning("ibm-rtl: module loaded by force\n"); +		pr_warn("module loaded by force\n");  	/* first ensure that we are running on IBM HW */ -	else if (!dmi_check_system(ibm_rtl_dmi_table)) +	else if (efi_enabled(EFI_BOOT) || !dmi_check_system(ibm_rtl_dmi_table))  		return -ENODEV;  	/* Get the address for the Extended BIOS Data Area */ @@ -293,19 +280,19 @@ static int __init ibm_rtl_init(void) {  		if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {  			phys_addr_t addr;  			unsigned int plen; -			RTL_DEBUG("found RTL_SIGNATURE at %#llx\n", (u64)tmp); +			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); +				  rtl_cmd_width, rtl_cmd_type);  			addr = ioread32(&rtl_table->cmd_port_address); -			RTL_DEBUG("addr = %#llx\n", addr); +			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 = %#llx\n", (u64)rtl_cmd_addr); +			RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr);  			if (!rtl_cmd_addr) {  				ret = -ENOMEM;  				break; diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 5ff12205aa6..b4c495a62ee 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1,5 +1,5 @@  /* - *  ideapad_acpi.c - Lenovo IdeaPad ACPI Extras + *  ideapad-laptop.c - Lenovo IdeaPad ACPI Extras   *   *  Copyright © 2010 Intel Corporation   *  Copyright © 2010 David Woodhouse <dwmw2@infradead.org> @@ -20,36 +20,73 @@   *  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 <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.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, +}; -#define IDEAPAD_DEV_CAMERA	0 -#define IDEAPAD_DEV_WLAN	1 -#define IDEAPAD_DEV_BLUETOOTH	2 -#define IDEAPAD_DEV_3G		3 -#define IDEAPAD_DEV_KILLSW	4 +struct ideapad_rfk_priv { +	int dev; +	struct ideapad_private *priv; +};  struct ideapad_private { -	acpi_handle handle; -	struct rfkill *rfk[5]; -} *ideapad_priv; - -static struct { -	char *name; -	int cfgbit; -	int opcode; -	int type; -} ideapad_rfk_data[] = { -	{ "ideapad_camera",	19, 0x1E, NUM_RFKILL_TYPES }, -	{ "ideapad_wlan",	18, 0x15, RFKILL_TYPE_WLAN }, -	{ "ideapad_bluetooth",	16, 0x17, RFKILL_TYPE_BLUETOOTH }, -	{ "ideapad_3g",		17, 0x20, RFKILL_TYPE_WWAN }, -	{ "ideapad_killsw",	0,  0,    RFKILL_TYPE_WLAN } +	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; @@ -163,17 +200,163 @@ static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)  	pr_err("timeout in write_ec_cmd\n");  	return -1;  } -/* the above is ACPI helpers */ +/* + * 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)  { -	struct ideapad_private *priv = dev_get_drvdata(dev); -	acpi_handle handle = priv->handle;  	unsigned long result; +	struct ideapad_private *priv = dev_get_drvdata(dev); -	if (read_ec_data(handle, 0x1D, &result)) +	if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result))  		return sprintf(buf, "-1\n");  	return sprintf(buf, "%lu\n", result);  } @@ -182,75 +365,150 @@ static ssize_t store_ideapad_cam(struct device *dev,  				 struct device_attribute *attr,  				 const char *buf, size_t count)  { -	struct ideapad_private *priv = dev_get_drvdata(dev); -	acpi_handle handle = priv->handle;  	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(handle, 0x1E, state); +	ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state);  	if (ret < 0) -		return ret; +		return -EIO;  	return count;  }  static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); -static int ideapad_rfk_set(void *data, bool blocked) +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 device = (unsigned long)data; +	int ret, state; +	struct ideapad_private *priv = dev_get_drvdata(dev); -	if (device == IDEAPAD_DEV_KILLSW) +	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; +} -	return write_ec_cmd(ideapad_priv->handle, -			    ideapad_rfk_data[device].opcode, -			    !blocked); +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 acpi_device *adevice) +static void ideapad_sync_rfk_state(struct ideapad_private *priv)  { -	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); -	acpi_handle handle = priv->handle;  	unsigned long hw_blocked;  	int i; -	if (read_ec_data(handle, 0x23, &hw_blocked)) +	if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked))  		return;  	hw_blocked = !hw_blocked; -	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) +	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 acpi_device *adevice, int dev) +static int ideapad_register_rfkill(struct ideapad_private *priv, int dev)  { -	struct ideapad_private *priv = dev_get_drvdata(&adevice->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(ideapad_priv->handle, +		write_ec_cmd(priv->adev->handle,  			     ideapad_rfk_data[dev].opcode, 1);  		return 0;  	} - -	priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev, -				      ideapad_rfk_data[dev].type, &ideapad_rfk_ops, -				      (void *)(long)dev); +	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(ideapad_priv->handle, ideapad_rfk_data[dev].opcode-1, +	if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1,  			 &sw_blocked)) {  		rfkill_init_sw_state(priv->rfk[dev], 0);  	} else { @@ -266,10 +524,8 @@ static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)  	return 0;  } -static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) +static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev)  { -	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); -  	if (!priv->rfk[dev])  		return; @@ -277,118 +533,427 @@ static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)  	rfkill_destroy(priv->rfk[dev]);  } -static const struct acpi_device_id ideapad_device_ids[] = { -	{ "VPC2004", 0}, -	{ "", 0}, +/* + * 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 },  }; -MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); -static int ideapad_acpi_add(struct acpi_device *adevice) +static int ideapad_input_init(struct ideapad_private *priv)  { -	int i, cfg; -	int devs_present[5]; -	struct ideapad_private *priv; +	struct input_dev *inputdev; +	int error; -	if (read_method_int(adevice->handle, "_CFG", &cfg)) -		return -ENODEV; +	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; -	for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) { -		if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg)) -			devs_present[i] = 1; -		else -			devs_present[i] = 0; +	error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); +	if (error) { +		pr_err("Unable to setup input device keymap\n"); +		goto err_free_dev;  	} -	/* The hardware switch is always present */ -	devs_present[IDEAPAD_DEV_KILLSW] = 1; +	error = input_register_device(inputdev); +	if (error) { +		pr_err("Unable to register input device\n"); +		goto err_free_keymap; +	} -	priv = kzalloc(sizeof(*priv), GFP_KERNEL); -	if (!priv) -		return -ENOMEM; +	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 (devs_present[IDEAPAD_DEV_CAMERA]) { -		int ret = device_create_file(&adevice->dev, &dev_attr_camera_power); -		if (ret) { -			kfree(priv); -			return ret; +	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; +} -	priv->handle = adevice->handle; -	dev_set_drvdata(&adevice->dev, priv); -	ideapad_priv = priv; -	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) { -		if (!devs_present[i]) -			continue; +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, +}; -		ideapad_register_rfkill(adevice, i); +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);  	} -	ideapad_sync_rfk_state(adevice); + +	priv->blightdev = blightdev; +	blightdev->props.brightness = now; +	blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; +	backlight_update_status(blightdev); +  	return 0;  } -static int ideapad_acpi_remove(struct acpi_device *adevice, int type) +static void ideapad_backlight_exit(struct ideapad_private *priv)  { -	struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); -	int i; +	if (priv->blightdev) +		backlight_device_unregister(priv->blightdev); +	priv->blightdev = NULL; +} -	device_remove_file(&adevice->dev, &dev_attr_camera_power); +static void ideapad_backlight_notify_power(struct ideapad_private *priv) +{ +	unsigned long power; +	struct backlight_device *blightdev = priv->blightdev; -	for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) -		ideapad_unregister_rfkill(adevice, i); +	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; +} -	dev_set_drvdata(&adevice->dev, NULL); -	kfree(priv); -	return 0; +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(struct acpi_device *adevice, u32 event) +static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)  { -	acpi_handle handle = adevice->handle; +	struct ideapad_private *priv = data;  	unsigned long vpc1, vpc2, vpc_bit; -	if (read_ec_data(handle, 0x10, &vpc1)) +	if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))  		return; -	if (read_ec_data(handle, 0x1A, &vpc2)) +	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)) { -			if (vpc_bit == 9) -				ideapad_sync_rfk_state(adevice); +			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); +			}  		}  	}  } -static struct acpi_driver ideapad_acpi_driver = { -	.name = "ideapad_acpi", -	.class = "IdeaPad", -	.ids = ideapad_device_ids, -	.ops.add = ideapad_acpi_add, -	.ops.remove = ideapad_acpi_remove, -	.ops.notify = ideapad_acpi_notify, -	.owner = THIS_MODULE, +/* 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 __init ideapad_acpi_module_init(void) +static int ideapad_acpi_add(struct platform_device *pdev)  { -	acpi_bus_register_driver(&ideapad_acpi_driver); +	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; +} -static void __exit ideapad_acpi_module_exit(void) +#ifdef CONFIG_PM_SLEEP +static int ideapad_acpi_resume(struct device *device)  { -	acpi_bus_unregister_driver(&ideapad_acpi_driver); +	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"); - -module_init(ideapad_acpi_module_init); -module_exit(ideapad_acpi_module_exit); 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 index c44a5e8b8b8..18dcb58ba96 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -72,9 +72,13 @@  #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 @@ -245,6 +249,7 @@  #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 */ @@ -339,6 +344,9 @@ struct ips_driver {  	u64 orig_turbo_ratios;  }; +static bool +ips_gpu_turbo_enabled(struct ips_driver *ips); +  /**   * ips_cpu_busy - is CPU busy?   * @ips: IPS driver struct @@ -385,7 +393,7 @@ static void ips_cpu_raise(struct ips_driver *ips)  	thm_writew(THM_MPCPC, (new_tdp_limit * 10) / 8); -	turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDC_OVR_EN; +	turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;  	wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);  	turbo_override &= ~TURBO_TDP_MASK; @@ -420,7 +428,7 @@ static void ips_cpu_lower(struct ips_driver *ips)  	thm_writew(THM_MPCPC, (new_limit * 10) / 8); -	turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDC_OVR_EN; +	turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;  	wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);  	turbo_override &= ~TURBO_TDP_MASK; @@ -517,7 +525,7 @@ static void ips_disable_cpu_turbo(struct ips_driver *ips)   */  static bool ips_gpu_busy(struct ips_driver *ips)  { -	if (!ips->gpu_turbo_enabled) +	if (!ips_gpu_turbo_enabled(ips))  		return false;  	return ips->gpu_busy(); @@ -532,7 +540,7 @@ static bool ips_gpu_busy(struct ips_driver *ips)   */  static void ips_gpu_raise(struct ips_driver *ips)  { -	if (!ips->gpu_turbo_enabled) +	if (!ips_gpu_turbo_enabled(ips))  		return;  	if (!ips->gpu_raise()) @@ -549,7 +557,7 @@ static void ips_gpu_raise(struct ips_driver *ips)   */  static void ips_gpu_lower(struct ips_driver *ips)  { -	if (!ips->gpu_turbo_enabled) +	if (!ips_gpu_turbo_enabled(ips))  		return;  	if (!ips->gpu_lower()) @@ -602,25 +610,16 @@ static bool mcp_exceeded(struct ips_driver *ips)  	bool ret = false;  	u32 temp_limit;  	u32 avg_power; -	const char *msg = "MCP limit exceeded: ";  	spin_lock_irqsave(&ips->turbo_status_lock, flags);  	temp_limit = ips->mcp_temp_limit * 100; -	if (ips->mcp_avg_temp > temp_limit) { -		dev_info(&ips->dev->dev, -			"%sAvg temp %u, limit %u\n", msg, ips->mcp_avg_temp, -			temp_limit); +	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) { -		dev_info(&ips->dev->dev, -			"%sAvg power %u, limit %u\n", msg, avg_power, -			ips->mcp_power_limit); +	if (avg_power > ips->mcp_power_limit)  		ret = true; -	}  	spin_unlock_irqrestore(&ips->turbo_status_lock, flags); @@ -1106,7 +1105,7 @@ static int ips_monitor(void *data)  		last_msecs = jiffies_to_msecs(jiffies);  		expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD); -		__set_current_state(TASK_UNINTERRUPTIBLE); +		__set_current_state(TASK_INTERRUPTIBLE);  		mod_timer(&timer, expire);  		schedule(); @@ -1454,6 +1453,31 @@ 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), }, @@ -1462,6 +1486,24 @@ static DEFINE_PCI_DEVICE_TABLE(ips_id_table) = {  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; @@ -1471,6 +1513,9 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)  	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; @@ -1542,7 +1587,7 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)  		ips->poll_turbo_status = true;  	if (!ips_get_i915_syms(ips)) { -		dev_err(&dev->dev, "failed to get i915 symbols, graphics turbo disabled\n"); +		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"); @@ -1674,21 +1719,6 @@ static void ips_remove(struct pci_dev *dev)  	dev_dbg(&dev->dev, "IPS driver removed\n");  } -#ifdef CONFIG_PM -static int ips_suspend(struct pci_dev *dev, pm_message_t state) -{ -	return 0; -} - -static int ips_resume(struct pci_dev *dev) -{ -	return 0; -} -#else -#define ips_suspend NULL -#define ips_resume NULL -#endif /* CONFIG_PM */ -  static void ips_shutdown(struct pci_dev *dev)  {  } @@ -1698,23 +1728,10 @@ static struct pci_driver ips_pci_driver = {  	.id_table = ips_id_table,  	.probe = ips_probe,  	.remove = ips_remove, -	.suspend = ips_suspend, -	.resume = ips_resume,  	.shutdown = ips_shutdown,  }; -static int __init ips_init(void) -{ -	return pci_register_driver(&ips_pci_driver); -} -module_init(ips_init); - -static void ips_exit(void) -{ -	pci_unregister_driver(&ips_pci_driver); -	return; -} -module_exit(ips_exit); +module_pci_driver(ips_pci_driver);  MODULE_LICENSE("GPL");  MODULE_AUTHOR("Jesse Barnes <jbarnes@virtuousgeek.org>"); 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 eacd5da7dd2..e8b46d2c468 100644 --- a/drivers/platform/x86/intel_menlow.c +++ b/drivers/platform/x86/intel_menlow.c @@ -27,6 +27,8 @@   *  to get/set bandwidth.   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/kernel.h>  #include <linux/module.h>  #include <linux/init.h> @@ -34,10 +36,8 @@  #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"); @@ -135,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; @@ -155,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, @@ -199,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); @@ -388,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)  { @@ -476,6 +471,8 @@ static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl,  		return AE_ERROR;  	} +	return AE_OK; +   aux1_not_found:  	if (status == AE_NOT_FOUND)  		return AE_OK; 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 index e61db9dfebe..40929e4f7ad 100644 --- a/drivers/platform/x86/intel_pmic_gpio.c +++ b/drivers/platform/x86/intel_pmic_gpio.c @@ -19,6 +19,8 @@   * Moorestown platform PMIC chip   */ +#define pr_fmt(fmt) "%s: " fmt, __func__ +  #include <linux/module.h>  #include <linux/kernel.h>  #include <linux/interrupt.h> @@ -60,23 +62,18 @@ enum pmic_gpio_register {  #define GPOSW_DOU 0x08  #define GPOSW_RDRV 0x30 +#define GPIO_UPDATE_TYPE	0x80000000  #define NUM_GPIO 24 -struct pmic_gpio_irq { -	spinlock_t lock; -	u32 trigger[NUM_GPIO]; -	u32 dirty; -	struct work_struct work; -}; - -  struct pmic_gpio { +	struct mutex		buslock;  	struct gpio_chip	chip; -	struct pmic_gpio_irq	irqtypes;  	void			*gpiointr;  	int			irq;  	unsigned		irq_base; +	unsigned int		update_type; +	u32			trigger_type;  };  static void pmic_program_irqtype(int gpio, int type) @@ -92,42 +89,10 @@ static void pmic_program_irqtype(int gpio, int type)  		intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x10);  }; -static void pmic_irqtype_work(struct work_struct *work) -{ -	struct pmic_gpio_irq *t = -		container_of(work, struct pmic_gpio_irq, work); -	unsigned long flags; -	int i; -	u16 type; - -	spin_lock_irqsave(&t->lock, flags); -	/* As we drop the lock, we may need multiple scans if we race the -	   pmic_irq_type function */ -	while (t->dirty) { -		/* -		 *	For each pin that has the dirty bit set send an IPC -		 *	message to configure the hardware via the PMIC -		 */ -		for (i = 0; i < NUM_GPIO; i++) { -			if (!(t->dirty & (1 << i))) -				continue; -			t->dirty &= ~(1 << i); -			/* We can't trust the array entry or dirty -			   once the lock is dropped */ -			type = t->trigger[i]; -			spin_unlock_irqrestore(&t->lock, flags); -			pmic_program_irqtype(i, type); -			spin_lock_irqsave(&t->lock, flags); -		} -	} -	spin_unlock_irqrestore(&t->lock, flags); -} -  static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned offset)  { -	if (offset > 8) { -		printk(KERN_ERR -			"%s: only pin 0-7 support input\n", __func__); +	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, @@ -152,8 +117,7 @@ static int pmic_gpio_direction_output(struct gpio_chip *chip,  				value ? 1 << (offset - 16) : 0,  				1 << (offset - 16));  	else { -		printk(KERN_ERR -			"%s: invalid PMIC GPIO pin %d!\n", __func__, offset); +		pr_err("invalid PMIC GPIO pin %d!\n", offset);  		WARN_ON(1);  	} @@ -166,7 +130,7 @@ static int pmic_gpio_get(struct gpio_chip *chip, unsigned offset)  	int ret;  	/* we only have 8 GPIO pins we can use as input */ -	if (offset > 8) +	if (offset >= 8)  		return -EOPNOTSUPP;  	ret = intel_scu_ipc_ioread8(GPIO0 + offset, &r);  	if (ret < 0) @@ -190,25 +154,24 @@ static void pmic_gpio_set(struct gpio_chip *chip, unsigned offset, int value)  			1 << (offset - 16));  } -static int pmic_irq_type(unsigned irq, unsigned type) +/* + * 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 = get_irq_chip_data(irq); -	u32 gpio = irq - pg->irq_base; -	unsigned long flags; +	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; -	spin_lock_irqsave(&pg->irqtypes.lock, flags); -	pg->irqtypes.trigger[gpio] = type; -	pg->irqtypes.dirty |=  (1 << gpio); -	spin_unlock_irqrestore(&pg->irqtypes.lock, flags); -	schedule_work(&pg->irqtypes.work); +	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); @@ -216,38 +179,58 @@ static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned offset)  	return pg->irq_base + offset;  } -/* the gpiointr register is read-clear, so just do nothing. */ -static void pmic_irq_unmask(unsigned irq) +static void pmic_bus_lock(struct irq_data *data)  { -}; +	struct pmic_gpio *pg = irq_data_get_irq_chip_data(data); -static void pmic_irq_mask(unsigned irq) +	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", -	.mask		= pmic_irq_mask, -	.unmask		= pmic_irq_unmask, -	.set_type	= pmic_irq_type, +	.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 void pmic_irq_handler(unsigned irq, struct irq_desc *desc) +static irqreturn_t pmic_irq_handler(int irq, void *data)  { -	struct pmic_gpio *pg = (struct pmic_gpio *)get_irq_data(irq); +	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;  		}  	} -	desc->chip->eoi(irq); +	return ret;  } -static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev) +static int platform_pmic_gpio_probe(struct platform_device *pdev)  {  	struct device *dev = &pdev->dev;  	int irq = platform_get_irq(pdev, 0); @@ -277,7 +260,7 @@ static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev)  	/* setting up SRAM mapping for GPIOINT register */  	pg->gpiointr = ioremap_nocache(pdata->gpiointr, 8);  	if (!pg->gpiointr) { -		printk(KERN_ERR "%s: Can not map GPIOINT.\n", __func__); +		pr_err("Can not map GPIOINT\n");  		retval = -EINVAL;  		goto err2;  	} @@ -293,23 +276,33 @@ static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev)  	pg->chip.can_sleep = 1;  	pg->chip.dev = dev; -	INIT_WORK(&pg->irqtypes.work, pmic_irqtype_work); -	spin_lock_init(&pg->irqtypes.lock); +	mutex_init(&pg->buslock);  	pg->chip.dev = dev;  	retval = gpiochip_add(&pg->chip);  	if (retval) { -		printk(KERN_ERR "%s: Can not add pmic gpio chip.\n", __func__); +		pr_err("Can not add pmic gpio chip\n");  		goto err;  	} -	set_irq_data(pg->irq, pg); -	set_irq_chained_handler(pg->irq, pmic_irq_handler); + +	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++) { -		set_irq_chip_and_handler_name(i + pg->irq_base, &pmic_irqchip, -					handle_simple_irq, "demux"); -		set_irq_chip_data(i + pg->irq_base, pg); +		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: diff --git a/drivers/platform/x86/intel_rar_register.c b/drivers/platform/x86/intel_rar_register.c deleted file mode 100644 index 2b11a33325e..00000000000 --- a/drivers/platform/x86/intel_rar_register.c +++ /dev/null @@ -1,671 +0,0 @@ -/* - *  rar_register.c - An Intel Restricted Access Region register driver - * - *  Copyright(c) 2009 Intel Corporation. All rights reserved. - * - *  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. - * - * ------------------------------------------------------------------- - *  20091204 Mark Allyn <mark.a.allyn@intel.com> - *	     Ossama Othman <ossama.othman@intel.com> - *	Cleanup per feedback from Alan Cox and Arjan Van De Ven - * - *  20090806 Ossama Othman <ossama.othman@intel.com> - *      Return zero high address if upper 22 bits is zero. - *      Cleaned up checkpatch errors. - *      Clarified that driver is dealing with bus addresses. - * - *  20090702 Ossama Othman <ossama.othman@intel.com> - *      Removed unnecessary include directives - *      Cleaned up spinlocks. - *      Cleaned up logging. - *      Improved invalid parameter checks. - *      Fixed and simplified RAR address retrieval and RAR locking - *      code. - * - *  20090626 Mark Allyn <mark.a.allyn@intel.com> - *      Initial publish - */ - -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/spinlock.h> -#include <linux/device.h> -#include <linux/kernel.h> -#include <linux/rar_register.h> - -/* === Lincroft Message Bus Interface === */ -#define LNC_MCR_OFFSET		0xD0	/* Message Control Register */ -#define LNC_MDR_OFFSET		0xD4	/* Message Data Register */ - -/* Message Opcodes */ -#define LNC_MESSAGE_READ_OPCODE	0xD0 -#define LNC_MESSAGE_WRITE_OPCODE 0xE0 - -/* Message Write Byte Enables */ -#define LNC_MESSAGE_BYTE_WRITE_ENABLES	0xF - -/* B-unit Port */ -#define LNC_BUNIT_PORT	0x3 - -/* === Lincroft B-Unit Registers - Programmed by IA32 firmware === */ -#define LNC_BRAR0L	0x10 -#define LNC_BRAR0H	0x11 -#define LNC_BRAR1L	0x12 -#define LNC_BRAR1H	0x13 -/* Reserved for SeP */ -#define LNC_BRAR2L	0x14 -#define LNC_BRAR2H	0x15 - -/* Moorestown supports three restricted access regions. */ -#define MRST_NUM_RAR 3 - -/* RAR Bus Address Range */ -struct rar_addr { -	dma_addr_t low; -	dma_addr_t high; -}; - -/* - *	We create one of these for each RAR - */ -struct client { -	int (*callback)(unsigned long data); -	unsigned long driver_priv; -	bool busy; -}; - -static DEFINE_MUTEX(rar_mutex); -static DEFINE_MUTEX(lnc_reg_mutex); - -/* - *	One per RAR device (currently only one device) - */ -struct rar_device { -	struct rar_addr rar_addr[MRST_NUM_RAR]; -	struct pci_dev *rar_dev; -	bool registered; -	bool allocated; -	struct client client[MRST_NUM_RAR]; -}; - -/* Current platforms have only one rar_device for 3 rar regions */ -static struct rar_device my_rar_device; - -/* - *	Abstract out multiple device support. Current platforms only - *	have a single RAR device. - */ - -/** - *	alloc_rar_device	-	return a new RAR structure - * - *	Return a new (but not yet ready) RAR device object - */ -static struct rar_device *alloc_rar_device(void) -{ -	if (my_rar_device.allocated) -		return NULL; -	my_rar_device.allocated = 1; -	return &my_rar_device; -} - -/** - *	free_rar_device		-	free a RAR object - *	@rar: the RAR device being freed - * - *	Release a RAR object and any attached resources - */ -static void free_rar_device(struct rar_device *rar) -{ -	pci_dev_put(rar->rar_dev); -	rar->allocated = 0; -} - -/** - *	_rar_to_device		-	return the device handling this RAR - *	@rar: RAR number - *	@off: returned offset - * - *	Internal helper for looking up RAR devices. This and alloc are the - *	two functions that need touching to go to multiple RAR devices. - */ -static struct rar_device *_rar_to_device(int rar, int *off) -{ -	if (rar >= 0 && rar < MRST_NUM_RAR) { -		*off = rar; -		return &my_rar_device; -	} -	return NULL; -} - -/** - *	rar_to_device		-	return the device handling this RAR - *	@rar: RAR number - *	@off: returned offset - * - *	Return the device this RAR maps to if one is present, otherwise - *	returns NULL. Reports the offset relative to the base of this - *	RAR device in off. - */ -static struct rar_device *rar_to_device(int rar, int *off) -{ -	struct rar_device *rar_dev = _rar_to_device(rar, off); -	if (rar_dev == NULL || !rar_dev->registered) -		return NULL; -	return rar_dev; -} - -/** - *	rar_to_client		-	return the client handling this RAR - *	@rar: RAR number - * - *	Return the client this RAR maps to if a mapping is known, otherwise - *	returns NULL. - */ -static struct client *rar_to_client(int rar) -{ -	int idx; -	struct rar_device *r = _rar_to_device(rar, &idx); -	if (r != NULL) -		return &r->client[idx]; -	return NULL; -} - -/** - *	rar_read_addr		-	retrieve a RAR mapping - *	@pdev: PCI device for the RAR - *	@offset: offset for message - *	@addr: returned address - * - *	Reads the address of a given RAR register. Returns 0 on success - *	or an error code on failure. - */ -static int rar_read_addr(struct pci_dev *pdev, int offset, dma_addr_t *addr) -{ -	/* -	 * ======== The Lincroft Message Bus Interface ======== -	 * Lincroft registers may be obtained via PCI from -	 * the host bridge using the Lincroft Message Bus -	 * Interface.  That message bus interface is generally -	 * comprised of two registers: a control register (MCR, 0xDO) -	 * and a data register (MDR, 0xD4). -	 * -	 * The MCR (message control register) format is the following: -	 *   1.  [31:24]: Opcode -	 *   2.  [23:16]: Port -	 *   3.  [15:8]: Register Offset -	 *   4.  [7:4]: Byte Enables (use 0xF to set all of these bits -	 *              to 1) -	 *   5.  [3:0]: reserved -	 * -	 *  Read (0xD0) and write (0xE0) opcodes are written to the -	 *  control register when reading and writing to Lincroft -	 *  registers, respectively. -	 * -	 *  We're interested in registers found in the Lincroft -	 *  B-unit.  The B-unit port is 0x3. -	 * -	 *  The six B-unit RAR register offsets we use are listed -	 *  earlier in this file. -	 * -	 *  Lastly writing to the MCR register requires the "Byte -	 *  enables" bits to be set to 1.  This may be achieved by -	 *  writing 0xF at bit 4. -	 * -	 * The MDR (message data register) format is the following: -	 *   1. [31:0]: Read/Write Data -	 * -	 *  Data being read from this register is only available after -	 *  writing the appropriate control message to the MCR -	 *  register. -	 * -	 *  Data being written to this register must be written before -	 *  writing the appropriate control message to the MCR -	 *  register. -	*/ - -	int result; -	u32 addr32; - -	/* Construct control message */ -	u32 const message = -		 (LNC_MESSAGE_READ_OPCODE << 24) -		 | (LNC_BUNIT_PORT << 16) -		 | (offset << 8) -		 | (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4); - -	dev_dbg(&pdev->dev, "Offset for 'get' LNC MSG is %x\n", offset); - -	/* -	* We synchronize access to the Lincroft MCR and MDR registers -	* until BOTH the command is issued through the MCR register -	* and the corresponding data is read from the MDR register. -	* Otherwise a race condition would exist between accesses to -	* both registers. -	*/ - -	mutex_lock(&lnc_reg_mutex); - -	/* Send the control message */ -	result = pci_write_config_dword(pdev, LNC_MCR_OFFSET, message); -	if (!result) { -		/* Read back the address as a 32bit value */ -		result = pci_read_config_dword(pdev, LNC_MDR_OFFSET, &addr32); -		*addr = (dma_addr_t)addr32; -	} -	mutex_unlock(&lnc_reg_mutex); -	return result; -} - -/** - *	rar_set_addr		-	Set a RAR mapping - *	@pdev: PCI device for the RAR - *	@offset: offset for message - *	@addr: address to set - * - *	Sets the address of a given RAR register. Returns 0 on success - *	or an error code on failure. - */ -static int rar_set_addr(struct pci_dev *pdev, -	int offset, -	dma_addr_t addr) -{ -	/* -	* Data being written to this register must be written before -	* writing the appropriate control message to the MCR -	* register. -	* See rar_get_addrs() for a description of the -	* message bus interface being used here. -	*/ - -	int result; - -	/* Construct control message */ -	u32 const message = (LNC_MESSAGE_WRITE_OPCODE << 24) -		| (LNC_BUNIT_PORT << 16) -		| (offset << 8) -		| (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4); - -	/* -	* We synchronize access to the Lincroft MCR and MDR registers -	* until BOTH the command is issued through the MCR register -	* and the corresponding data is read from the MDR register. -	* Otherwise a race condition would exist between accesses to -	* both registers. -	*/ - -	mutex_lock(&lnc_reg_mutex); - -	/* Send the control message */ -	result = pci_write_config_dword(pdev, LNC_MDR_OFFSET, addr); -	if (!result) -		/* And address */ -		result = pci_write_config_dword(pdev, LNC_MCR_OFFSET, message); - -	mutex_unlock(&lnc_reg_mutex); -	return result; -} - -/* - *	rar_init_params		-	Initialize RAR parameters - *	@rar: RAR device to initialise - * - *	Initialize RAR parameters, such as bus addresses, etc. Returns 0 - *	on success, or an error code on failure. - */ -static int init_rar_params(struct rar_device *rar) -{ -	struct pci_dev *pdev = rar->rar_dev; -	unsigned int i; -	int result = 0; -	int offset = 0x10;	/* RAR 0 to 2 in order low/high/low/high/... */ - -	/* Retrieve RAR start and end bus addresses. -	* Access the RAR registers through the Lincroft Message Bus -	* Interface on PCI device: 00:00.0 Host bridge. -	*/ - -	for (i = 0; i < MRST_NUM_RAR; ++i) { -		struct rar_addr *addr = &rar->rar_addr[i]; - -		result = rar_read_addr(pdev, offset++, &addr->low); -		if (result != 0) -			return result; - -		result = rar_read_addr(pdev, offset++, &addr->high); -		if (result != 0) -			return result; - - -		/* -		* Only the upper 22 bits of the RAR addresses are -		* stored in their corresponding RAR registers so we -		* must set the lower 10 bits accordingly. - -		* The low address has its lower 10 bits cleared, and -		* the high address has all its lower 10 bits set, -		* e.g.: -		* low = 0x2ffffc00 -		*/ - -		addr->low &= (dma_addr_t)0xfffffc00u; - -		/* -		* Set bits 9:0 on uppser address if bits 31:10 are non -		* zero; otherwize clear all bits -		*/ - -		if ((addr->high & 0xfffffc00u) == 0) -			addr->high = 0; -		else -			addr->high |= 0x3ffu; -	} -	/* Done accessing the device. */ - -	if (result == 0) { -		for (i = 0; i != MRST_NUM_RAR; ++i) { -			/* -			* "BRAR" refers to the RAR registers in the -			* Lincroft B-unit. -			*/ -			dev_info(&pdev->dev, "BRAR[%u] bus address range = " -			  "[%lx, %lx]\n", i, -			  (unsigned long)rar->rar_addr[i].low, -			  (unsigned long)rar->rar_addr[i].high); -		} -	} -	return result; -} - -/** - *	rar_get_address		-	get the bus address in a RAR - *	@start: return value of start address of block - *	@end: return value of end address of block - * - *	The rar_get_address function is used by other device drivers - *	to obtain RAR address information on a RAR. It takes three - *	parameters: - * - *	The function returns a 0 upon success or an error if there is no RAR - *	facility on this system. - */ -int rar_get_address(int rar_index, dma_addr_t *start, dma_addr_t *end) -{ -	int idx; -	struct rar_device *rar = rar_to_device(rar_index, &idx); - -	if (rar == NULL) { -		WARN_ON(1); -		return -ENODEV; -	} - -	*start = rar->rar_addr[idx].low; -	*end = rar->rar_addr[idx].high; -	return 0; -} -EXPORT_SYMBOL(rar_get_address); - -/** - *	rar_lock	-	lock a RAR register - *	@rar_index: RAR to lock (0-2) - * - *	The rar_lock function is ued by other device drivers to lock an RAR. - *	once a RAR is locked, it stays locked until the next system reboot. - * - *	The function returns a 0 upon success or an error if there is no RAR - *	facility on this system, or the locking fails - */ -int rar_lock(int rar_index) -{ -	struct rar_device *rar; -	int result; -	int idx; -	dma_addr_t low, high; - -	rar = rar_to_device(rar_index, &idx); - -	if (rar == NULL) { -		WARN_ON(1); -		return -EINVAL; -	} - -	low = rar->rar_addr[idx].low & 0xfffffc00u; -	high = rar->rar_addr[idx].high & 0xfffffc00u; - -	/* -	* Only allow I/O from the graphics and Langwell; -	* not from the x86 processor -	*/ - -	if (rar_index == RAR_TYPE_VIDEO) { -		low |= 0x00000009; -		high |= 0x00000015; -	} else if (rar_index == RAR_TYPE_AUDIO) { -		/* Only allow I/O from Langwell; nothing from x86 */ -		low |= 0x00000008; -		high |= 0x00000018; -	} else -		/* Read-only from all agents */ -		high |= 0x00000018; - -	/* -	* Now program the register using the Lincroft message -	* bus interface. -	*/ -	result = rar_set_addr(rar->rar_dev, -				2 * idx, low); - -	if (result == 0) -		result = rar_set_addr(rar->rar_dev, -				2 * idx + 1, high); - -	return result; -} -EXPORT_SYMBOL(rar_lock); - -/** - *	register_rar		-	register a RAR handler - *	@num: RAR we wish to register for - *	@callback: function to call when RAR support is available - *	@data: data to pass to this function - * - *	The register_rar function is to used by other device drivers - *	to ensure that this driver is ready. As we cannot be sure of - *	the compile/execute order of drivers in ther kernel, it is - *	best to give this driver a callback function to call when - *	it is ready to give out addresses. The callback function - *	would have those steps that continue the initialization of - *	a driver that do require a valid RAR address. One of those - *	steps would be to call rar_get_address() - * - *	This function return 0 on success or an error code on failure. - */ -int register_rar(int num, int (*callback)(unsigned long data), -							unsigned long data) -{ -	/* For now we hardcode a single RAR device */ -	struct rar_device *rar; -	struct client *c; -	int idx; -	int retval = 0; - -	mutex_lock(&rar_mutex); - -	/* Do we have a client mapping for this RAR number ? */ -	c = rar_to_client(num); -	if (c == NULL) { -		retval = -ERANGE; -		goto done; -	} -	/* Is it claimed ? */ -	if (c->busy) { -		retval = -EBUSY; -		goto done; -	} -	c->busy = 1; - -	/* See if we have a handler for this RAR yet, if we do then fire it */ -	rar = rar_to_device(num, &idx); - -	if (rar) { -		/* -		* if the driver already registered, then we can simply -		* call the callback right now -		*/ -		(*callback)(data); -		goto done; -	} - -	/* Arrange to be called back when the hardware is found */ -	c->callback = callback; -	c->driver_priv = data; -done: -	mutex_unlock(&rar_mutex); -	return retval; -} -EXPORT_SYMBOL(register_rar); - -/** - *	unregister_rar	-	release a RAR allocation - *	@num: RAR number - * - *	Releases a RAR allocation, or pending allocation. If a callback is - *	pending then this function will either complete before the unregister - *	returns or not at all. - */ - -void unregister_rar(int num) -{ -	struct client *c; - -	mutex_lock(&rar_mutex); -	c = rar_to_client(num); -	if (c == NULL || !c->busy) -		WARN_ON(1); -	else -		c->busy = 0; -	mutex_unlock(&rar_mutex); -} -EXPORT_SYMBOL(unregister_rar); - -/** - *	rar_callback		-	Process callbacks - *	@rar: new RAR device - * - *	Process the callbacks for a newly found RAR device. - */ - -static void rar_callback(struct rar_device *rar) -{ -	struct client *c = &rar->client[0]; -	int i; - -	mutex_lock(&rar_mutex); - -	rar->registered = 1;	/* Ensure no more callbacks queue */ - -	for (i = 0; i < MRST_NUM_RAR; i++) { -		if (c->callback && c->busy) { -			c->callback(c->driver_priv); -			c->callback = NULL; -		} -		c++; -	} -	mutex_unlock(&rar_mutex); -} - -/** - *	rar_probe		-	PCI probe callback - *	@dev: PCI device - *	@id: matching entry in the match table - * - *	A RAR device has been discovered. Initialise it and if successful - *	process any pending callbacks that can now be completed. - */ -static int rar_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ -	int error; -	struct rar_device *rar; - -	dev_dbg(&dev->dev, "PCI probe starting\n"); - -	rar = alloc_rar_device(); -	if (rar == NULL) -		return -EBUSY; - -	/* Enable the device */ -	error = pci_enable_device(dev); -	if (error) { -		dev_err(&dev->dev, -			"Error enabling RAR register PCI device\n"); -		goto end_function; -	} - -	/* Fill in the rar_device structure */ -	rar->rar_dev = pci_dev_get(dev); -	pci_set_drvdata(dev, rar); - -	/* -	 * Initialize the RAR parameters, which have to be retrieved -	 * via the message bus interface. -	 */ -	error = init_rar_params(rar); -	if (error) { -		pci_disable_device(dev); -		dev_err(&dev->dev, "Error retrieving RAR addresses\n"); -		goto end_function; -	} -	/* now call anyone who has registered (using callbacks) */ -	rar_callback(rar); -	return 0; -end_function: -	free_rar_device(rar); -	return error; -} - -const struct pci_device_id rar_pci_id_tbl[] = { -	{ PCI_VDEVICE(INTEL, 0x4110) }, -	{ 0 } -}; - -MODULE_DEVICE_TABLE(pci, rar_pci_id_tbl); - -const struct pci_device_id *my_id_table = rar_pci_id_tbl; - -/* field for registering driver to PCI device */ -static struct pci_driver rar_pci_driver = { -	.name = "rar_register_driver", -	.id_table = rar_pci_id_tbl, -	.probe = rar_probe, -	/* Cannot be unplugged - no remove */ -}; - -static int __init rar_init_handler(void) -{ -	return pci_register_driver(&rar_pci_driver); -} - -static void __exit rar_exit_handler(void) -{ -	pci_unregister_driver(&rar_pci_driver); -} - -module_init(rar_init_handler); -module_exit(rar_exit_handler); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Intel Restricted Access Region Register Driver"); diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index 41a9e34899a..76ca094ed01 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -9,7 +9,7 @@   * as published by the Free Software Foundation; version 2   * of the License.   * - * SCU runing in ARC processor communicates with other entity running in IA + * 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 @@ -19,12 +19,13 @@  #include <linux/delay.h>  #include <linux/errno.h>  #include <linux/init.h> -#include <linux/sysdev.h> +#include <linux/device.h>  #include <linux/pm.h>  #include <linux/pci.h>  #include <linux/interrupt.h>  #include <linux/sfi.h> -#include <asm/mrst.h> +#include <linux/module.h> +#include <asm/intel-mid.h>  #include <asm/intel_scu_ipc.h>  /* IPC defines the following message types */ @@ -57,12 +58,48 @@   *    message handler is called within firmware.   */ -#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register address */ -#define IPC_MAX_ADDR      0x100		/* Maximum IPC regisers */  #define IPC_WWBUF_SIZE    20		/* IPC Write buffer Size */  #define IPC_RWBUF_SIZE    20		/* IPC Read buffer Size */ -#define IPC_I2C_BASE      0xFF12B000	/* I2C control register base address */ -#define IPC_I2C_MAX_ADDR  0x10		/* Maximum I2C regisers */ +#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); @@ -71,6 +108,8 @@ 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 */ @@ -97,6 +136,10 @@ static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */   */  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);  } @@ -155,12 +198,36 @@ static inline int busy_loop(void) /* Wait till scu status is busy */  	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 i, nc, bytes, d; +	int nc;  	u32 offset = 0; -	u32 err = 0; +	int err;  	u8 cbuf[IPC_WWBUF_SIZE] = { };  	u32 *wbuf = (u32 *)&cbuf; @@ -173,55 +240,34 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)  		return -ENODEV;  	} -	if (platform != MRST_CPU_CHIP_PENWELL) { -		bytes = 0; -		d = 0; -		for (i = 0; i < count; i++) { -			cbuf[bytes++] = addr[i]; -			cbuf[bytes++] = addr[i] >> 8; -			if (id != IPC_CMD_PCNTRL_R) -				cbuf[bytes++] = data[d++]; -			if (id == IPC_CMD_PCNTRL_M) -				cbuf[bytes++] = data[d++]; -		} -		for (i = 0; i < bytes; i += 4) -			ipc_data_writel(wbuf[i/4], i); -		ipc_command(bytes << 16 |  id << 12 | 0 << 8 | op); -	} else { -		for (nc = 0; nc < count; nc++, offset += 2) { -			cbuf[offset] = addr[nc]; -			cbuf[offset + 1] = addr[nc] >> 8; -		} +	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); -		} +	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 = busy_loop(); -	if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */ +	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); -		if (platform != MRST_CPU_CHIP_PENWELL) { -			for (nc = 0, offset = 2; nc < count; nc++, offset += 3) -				data[nc] = ipc_data_readb(offset); -		} else { -			for (nc = 0; nc < count; nc++) -				data[nc] = ipc_data_readb(nc); -		} +		for (nc = 0; nc < count; nc++) +			data[nc] = ipc_data_readb(nc);  	}  	mutex_unlock(&ipclock);  	return err; @@ -403,7 +449,7 @@ EXPORT_SYMBOL(intel_scu_ipc_update_register);   */  int intel_scu_ipc_simple_command(int cmd, int sub)  { -	u32 err = 0; +	int err;  	mutex_lock(&ipclock);  	if (ipcdev.pdev == NULL) { @@ -411,7 +457,7 @@ int intel_scu_ipc_simple_command(int cmd, int sub)  		return -ENODEV;  	}  	ipc_command(sub << 12 | cmd); -	err = busy_loop(); +	err = intel_scu_ipc_check_status();  	mutex_unlock(&ipclock);  	return err;  } @@ -433,8 +479,7 @@ EXPORT_SYMBOL(intel_scu_ipc_simple_command);  int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,  							u32 *out, int outlen)  { -	u32 err = 0; -	int i = 0; +	int i, err;  	mutex_lock(&ipclock);  	if (ipcdev.pdev == NULL) { @@ -446,10 +491,12 @@ int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,  		ipc_data_writel(*in++, 4 * i);  	ipc_command((inlen << 16) | (sub << 12) | cmd); -	err = busy_loop(); +	err = intel_scu_ipc_check_status(); -	for (i = 0; i < outlen; i++) -		*out++ = ipc_data_readl(4 * i); +	if (!err) { +		for (i = 0; i < outlen; i++) +			*out++ = ipc_data_readl(4 * i); +	}  	mutex_unlock(&ipclock);  	return err; @@ -496,155 +543,13 @@ int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)  			"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);  		mutex_unlock(&ipclock); -		return -1; +		return -EIO;  	}  	mutex_unlock(&ipclock);  	return 0;  }  EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl); -#define IPC_FW_LOAD_ADDR 0xFFFC0000 /* Storage location for FW image */ -#define IPC_FW_UPDATE_MBOX_ADDR 0xFFFFDFF4 /* Mailbox between ipc and scu */ -#define IPC_MAX_FW_SIZE 262144 /* 256K storage size for loading the FW image */ -#define IPC_FW_MIP_HEADER_SIZE 2048 /* Firmware MIP header size */ -/* IPC inform SCU to get ready for update process */ -#define IPC_CMD_FW_UPDATE_READY  0x10FE -/* IPC inform SCU to go for update process */ -#define IPC_CMD_FW_UPDATE_GO     0x20FE -/* Status code for fw update */ -#define IPC_FW_UPDATE_SUCCESS	0x444f4e45 /* Status code 'DONE' */ -#define IPC_FW_UPDATE_BADN	0x4241444E /* Status code 'BADN' */ -#define IPC_FW_TXHIGH		0x54784849 /* Status code 'IPC_FW_TXHIGH' */ -#define IPC_FW_TXLOW		0x54784c4f /* Status code 'IPC_FW_TXLOW' */ - -struct fw_update_mailbox { -	u32    status; -	u32    scu_flag; -	u32    driver_flag; -}; - - -/** - *	intel_scu_ipc_fw_update	-	 Firmware update utility - *	@buffer: firmware buffer - *	@length: size of firmware buffer - * - *	This function provides an interface to load the firmware into - *	the SCU. Returns 0 on success or -1 on failure - */ -int intel_scu_ipc_fw_update(u8 *buffer, u32 length) -{ -	void __iomem *fw_update_base; -	struct fw_update_mailbox __iomem *mailbox = NULL; -	int retry_cnt = 0; -	u32 status; - -	mutex_lock(&ipclock); -	fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024)); -	if (fw_update_base == NULL) { -		mutex_unlock(&ipclock); -		return -ENOMEM; -	} -	mailbox = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR, -					sizeof(struct fw_update_mailbox)); -	if (mailbox == NULL) { -		iounmap(fw_update_base); -		mutex_unlock(&ipclock); -		return -ENOMEM; -	} - -	ipc_command(IPC_CMD_FW_UPDATE_READY); - -	/* Intitialize mailbox */ -	writel(0, &mailbox->status); -	writel(0, &mailbox->scu_flag); -	writel(0, &mailbox->driver_flag); - -	/* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/ -	memcpy_toio(fw_update_base, buffer, 0x800); - -	/* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02). -	* Upon receiving this command, SCU will write the 2K MIP header -	* from 0xFFFC0000 into NAND. -	* SCU will write a status code into the Mailbox, and then set scu_flag. -	*/ - -	ipc_command(IPC_CMD_FW_UPDATE_GO); - -	/*Driver stalls until scu_flag is set */ -	while (readl(&mailbox->scu_flag) != 1) { -		rmb(); -		mdelay(1); -	} - -	/* Driver checks Mailbox status. -	 * If the status is 'BADN', then abort (bad NAND). -	 * If the status is 'IPC_FW_TXLOW', then continue. -	 */ -	while (readl(&mailbox->status) != IPC_FW_TXLOW) { -		rmb(); -		mdelay(10); -	} -	mdelay(10); - -update_retry: -	if (retry_cnt > 5) -		goto update_end; - -	if (readl(&mailbox->status) != IPC_FW_TXLOW) -		goto update_end; -	buffer = buffer + 0x800; -	memcpy_toio(fw_update_base, buffer, 0x20000); -	writel(1, &mailbox->driver_flag); -	while (readl(&mailbox->scu_flag) == 1) { -		rmb(); -		mdelay(1); -	} - -	/* check for 'BADN' */ -	if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN) -		goto update_end; - -	while (readl(&mailbox->status) != IPC_FW_TXHIGH) { -		rmb(); -		mdelay(10); -	} -	mdelay(10); - -	if (readl(&mailbox->status) != IPC_FW_TXHIGH) -		goto update_end; - -	buffer = buffer + 0x20000; -	memcpy_toio(fw_update_base, buffer, 0x20000); -	writel(0, &mailbox->driver_flag); - -	while (mailbox->scu_flag == 0) { -		rmb(); -		mdelay(1); -	} - -	/* check for 'BADN' */ -	if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN) -		goto update_end; - -	if (readl(&mailbox->status) == IPC_FW_TXLOW) { -		++retry_cnt; -		goto update_retry; -	} - -update_end: -	status = readl(&mailbox->status); - -	iounmap(fw_update_base); -	iounmap(mailbox); -	mutex_unlock(&ipclock); - -	if (status == IPC_FW_UPDATE_SUCCESS) -		return 0; -	return -1; -} -EXPORT_SYMBOL(intel_scu_ipc_fw_update); -  /*   * 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 @@ -654,6 +559,9 @@ EXPORT_SYMBOL(intel_scu_ipc_fw_update);   */  static irqreturn_t ioc(int irq, void *dev_id)  { +	if (ipcdev.irq_mode) +		complete(&ipcdev.cmd_complete); +  	return IRQ_HANDLED;  } @@ -668,12 +576,16 @@ static irqreturn_t ioc(int irq, void *dev_id)  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) @@ -687,18 +599,23 @@ static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id)  	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(IPC_BASE_ADDR, IPC_MAX_ADDR); +	ipcdev.ipc_base = ioremap_nocache(pdata->ipc_base, pdata->ipc_len);  	if (!ipcdev.ipc_base)  		return -ENOMEM; -	ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR); +	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;  } @@ -720,12 +637,25 @@ static void ipc_remove(struct pci_dev *pdev)  	iounmap(ipcdev.ipc_base);  	iounmap(ipcdev.i2c_base);  	ipcdev.pdev = NULL; +	intel_scu_devices_destroy();  } -static const struct pci_device_id pci_ids[] = { -	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)}, -	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x082a)}, -	{ 0,} +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); @@ -739,7 +669,7 @@ static struct pci_driver ipc_driver = {  static int __init intel_scu_ipc_init(void)  { -	platform = mrst_identify_cpu(); +	platform = intel_mid_identify_cpu();  	if (platform == 0)  		return -ENODEV;  	return  pci_register_driver(&ipc_driver); 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 7e9bb6df9d3..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> @@ -60,6 +62,8 @@  #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" @@ -78,11 +82,28 @@  #define MSI_STANDARD_EC_SCM_LOAD_ADDRESS	0x2d  #define MSI_STANDARD_EC_SCM_LOAD_MASK		(1 << 0) -static int msi_laptop_resume(struct platform_device *device); +#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 int force; +static bool force;  module_param(force, bool, 0);  MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); @@ -90,23 +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 bool old_ec_model; +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; - -/* 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 - */ -static bool load_scm_model;  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) @@ -120,7 +164,7 @@ static int set_lcd_level(int level)  	buf[1] = (u8) (level*31);  	return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), -			      NULL, 0, 1); +			      NULL, 0);  }  static int get_lcd_level(void) @@ -129,7 +173,7 @@ static int get_lcd_level(void)  	int result;  	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, -				&rdata, 1, 1); +				&rdata, 1);  	if (result < 0)  		return result; @@ -142,7 +186,7 @@ static int get_auto_brightness(void)  	int result;  	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, -				&rdata, 1, 1); +				&rdata, 1);  	if (result < 0)  		return result; @@ -157,7 +201,7 @@ static int set_auto_brightness(int enable)  	wdata[0] = 4;  	result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, -				&rdata, 1, 1); +				&rdata, 1);  	if (result < 0)  		return result; @@ -165,7 +209,7 @@ static int set_auto_brightness(int enable)  	wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);  	return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, -			      NULL, 0, 1); +			      NULL, 0);  }  static ssize_t set_device_state(const char *buf, size_t count, u8 mask) @@ -177,10 +221,13 @@ static ssize_t set_device_state(const char *buf, size_t count, u8 mask)  	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 -EINVAL; +		return result;  	if (!!(rdata & mask) != status) {  		/* reverse device bit */ @@ -191,7 +238,7 @@ static ssize_t set_device_state(const char *buf, size_t count, u8 mask)  		result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata);  		if (result < 0) -			return -EINVAL; +			return result;  	}  	return count; @@ -202,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); @@ -222,7 +269,7 @@ static int get_wireless_state_ec_standard(void)  	result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);  	if (result < 0) -		return -1; +		return result;  	wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK); @@ -240,7 +287,7 @@ static int get_threeg_exists(void)  	result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata);  	if (result < 0) -		return -1; +		return result;  	threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK); @@ -273,9 +320,9 @@ static ssize_t show_wlan(struct device *dev,  	struct device_attribute *attr, char *buf)  { -	int ret, enabled; +	int ret, enabled = 0; -	if (old_ec_model) { +	if (quirks->old_ec_model) {  		ret = get_wireless_state(&enabled, NULL);  	} else {  		ret = get_wireless_state_ec_standard(); @@ -297,9 +344,9 @@ static ssize_t show_bluetooth(struct device *dev,  	struct device_attribute *attr, char *buf)  { -	int ret, enabled; +	int ret, enabled = 0; -	if (old_ec_model) { +	if (quirks->old_ec_model) {  		ret = get_wireless_state(NULL, &enabled);  	} else {  		ret = get_wireless_state_ec_standard(); @@ -324,8 +371,8 @@ static ssize_t show_threeg(struct device *dev,  	int ret;  	/* old msi ec not support 3G */ -	if (old_ec_model) -		return -1; +	if (quirks->old_ec_model) +		return -ENODEV;  	ret = get_wireless_state_ec_standard();  	if (ret < 0) @@ -399,18 +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(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  }; @@ -418,22 +566,42 @@ 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,  	}, -	.resume = msi_laptop_resume,  };  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(KERN_INFO "msi-laptop: Identified laptop model '%s'.\n", -	       id->ident); +	pr_info("Identified laptop model '%s'\n", dmi->ident); + +	quirks = dmi->driver_data; +  	return 1;  } @@ -447,6 +615,7 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {  			DMI_MATCH(DMI_CHASSIS_VENDOR,  				  "MICRO-STAR INT'L CO.,LTD")  		}, +		.driver_data = &quirk_old_ec_model,  		.callback = dmi_check_cb  	},  	{ @@ -457,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  	},  	{ @@ -467,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  	},  	{ @@ -478,12 +649,9 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {  			DMI_MATCH(DMI_CHASSIS_VENDOR,  				  "MICRO-STAR INT'L CO.,LTD")  		}, +		.driver_data = &quirk_old_ec_model,  		.callback = dmi_check_cb  	}, -	{ } -}; - -static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {  	{  		.ident = "MSI N034",  		.matches = { @@ -493,6 +661,7 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {  			DMI_MATCH(DMI_CHASSIS_VENDOR,  			"MICRO-STAR INTERNATIONAL CO., LTD")  		}, +		.driver_data = &quirk_load_scm_model,  		.callback = dmi_check_cb  	},  	{ @@ -504,6 +673,7 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {  			DMI_MATCH(DMI_CHASSIS_VENDOR,  			"MICRO-STAR INTERNATIONAL CO., LTD")  		}, +		.driver_data = &quirk_load_scm_model,  		.callback = dmi_check_cb  	},  	{ @@ -513,6 +683,7 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {  				"MICRO-STAR INTERNATIONAL CO., LTD"),  			DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"),  		}, +		.driver_data = &quirk_load_scm_model,  		.callback = dmi_check_cb  	},  	{ @@ -522,6 +693,27 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {  				"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  	},  	{ } @@ -534,32 +726,26 @@ static int rfkill_bluetooth_set(void *data, bool blocked)  	 * blocked == false is on  	 * blocked == true is off  	 */ -	if (blocked) -		set_device_state("0", 0, MSI_STANDARD_EC_BLUETOOTH_MASK); -	else -		set_device_state("1", 0, MSI_STANDARD_EC_BLUETOOTH_MASK); +	int result = set_device_state(blocked ? "0" : "1", 0, +			MSI_STANDARD_EC_BLUETOOTH_MASK); -	return 0; +	return min(result, 0);  }  static int rfkill_wlan_set(void *data, bool blocked)  { -	if (blocked) -		set_device_state("0", 0, MSI_STANDARD_EC_WLAN_MASK); -	else -		set_device_state("1", 0, MSI_STANDARD_EC_WLAN_MASK); +	int result = set_device_state(blocked ? "0" : "1", 0, +			MSI_STANDARD_EC_WLAN_MASK); -	return 0; +	return min(result, 0);  }  static int rfkill_threeg_set(void *data, bool blocked)  { -	if (blocked) -		set_device_state("0", 0, MSI_STANDARD_EC_3G_MASK); -	else -		set_device_state("1", 0, MSI_STANDARD_EC_3G_MASK); +	int result = set_device_state(blocked ? "0" : "1", 0, +			MSI_STANDARD_EC_3G_MASK); -	return 0; +	return min(result, 0);  }  static const struct rfkill_ops rfkill_bluetooth_ops = { @@ -592,18 +778,43 @@ static void rfkill_cleanup(void)  	}  } +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) -		rfkill_set_sw_state(rfk_wlan, !wlan_s); +		msi_rfkill_set_state(rfk_wlan, !wlan_s);  	if (rfk_bluetooth) -		rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s); +		msi_rfkill_set_state(rfk_bluetooth, !bluetooth_s);  	if (rfk_threeg) -		rfkill_set_sw_state(rfk_threeg, !threeg_s); +		msi_rfkill_set_state(rfk_threeg, !threeg_s);  } -static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill); +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) @@ -613,20 +824,30 @@ static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,  	if (str & 0x20)  		return false; -	/* 0x54 wwan, 0x62 bluetooth, 0x76 wlan*/ +	/* 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: -			schedule_delayed_work(&msi_rfkill_work, -				round_jiffies_relative(0.5 * HZ)); +			if (quirks->ec_delay) { +				schedule_delayed_work(&msi_rfkill_dwork, +					round_jiffies_relative(0.5 * HZ)); +			} else +				schedule_work(&msi_rfkill_work);  			break;  		} -		extended = false;  	}  	return false; @@ -691,8 +912,11 @@ static int rfkill_init(struct platform_device *sdev)  	}  	/* schedule to run rfkill state initial */ -	schedule_delayed_work(&msi_rfkill_init, -				round_jiffies_relative(1 * HZ)); +	if (quirks->ec_delay) { +		schedule_delayed_work(&msi_rfkill_init, +			round_jiffies_relative(1 * HZ)); +	} else +		schedule_work(&msi_rfkill_work);  	return 0; @@ -710,12 +934,13 @@ err_bluetooth:  	return retval;  } -static int msi_laptop_resume(struct platform_device *device) +#ifdef CONFIG_PM_SLEEP +static int msi_laptop_resume(struct device *device)  {  	u8 data;  	int result; -	if (!load_scm_model) +	if (!quirks->load_scm_model)  		return 0;  	/* set load SCM to disable hardware control by fn key */ @@ -730,19 +955,58 @@ static int msi_laptop_resume(struct platform_device *device)  	return 0;  } +#endif -static int load_scm_model_init(struct platform_device *sdev) +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; -	/* 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; +	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); @@ -759,16 +1023,23 @@ static int load_scm_model_init(struct platform_device *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) { -		printk(KERN_ERR -			"msi-laptop: Unable to install key filter\n"); +		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: @@ -784,26 +1055,27 @@ static int __init msi_init(void)  	if (acpi_disabled)  		return -ENODEV; -	if (force || dmi_check_system(msi_dmi_table)) -		old_ec_model = 1; +	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 (!old_ec_model) +	if (!quirks->old_ec_model)  		get_threeg_exists(); -	if (!old_ec_model && dmi_check_system(msi_load_scm_models_dmi_table)) -		load_scm_model = 1; -  	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, @@ -826,56 +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 (load_scm_model && (load_scm_model_init(msipf_device) < 0)) { +	if (quirks->load_scm_model && (load_scm_model_init(msipf_device) < 0)) {  		ret = -EINVAL; -		goto fail_platform_device1; +		goto fail_scm_model_init;  	}  	ret = sysfs_create_group(&msipf_device->dev.kobj,  				 &msipf_attribute_group);  	if (ret) -		goto fail_platform_device2; +		goto fail_create_group; -	if (!old_ec_model) { +	if (!quirks->old_ec_model) {  		if (threeg_exists)  			ret = device_create_file(&msipf_device->dev,  						&dev_attr_threeg);  		if (ret) -			goto fail_platform_device2; -	} +			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: - -	if (load_scm_model) { +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_work); +		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; @@ -883,24 +1157,28 @@ fail_backlight:  static void __exit msi_cleanup(void)  { -	if (load_scm_model) { +	if (quirks->load_scm_model) {  		i8042_remove_filter(msi_laptop_i8042_filter); -		cancel_delayed_work_sync(&msi_rfkill_work); +		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 (!old_ec_model && threeg_exists) +	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); @@ -919,3 +1197,5 @@ 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 42a5469a245..70222f265f6 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -20,6 +20,7 @@   *  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> @@ -27,35 +28,71 @@  #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];  static struct backlight_device *backlight; @@ -76,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); @@ -95,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); @@ -110,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; @@ -149,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;  	} @@ -162,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; @@ -220,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; @@ -234,60 +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; - -	if (!acpi_video_backlight_support()) { -		struct backlight_properties props; -		memset(&props, 0, sizeof(struct backlight_properties)); -		props.max_brightness = ARRAY_SIZE(backlight_map) - 1; -		backlight = backlight_device_register(DRV_NAME, NULL, NULL, -						      &msi_backlight_ops, -						      &props); -		if (IS_ERR(backlight)) { -			err = PTR_ERR(backlight); +		err = msi_wmi_input_setup(); +		if (err) { +			pr_err("Unable to setup input device\n"); +			return err; +		} + +		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;  		} -		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 cc1e0ba104d..3f870972247 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -125,12 +125,10 @@  #include <linux/seq_file.h>  #include <linux/uaccess.h>  #include <linux/slab.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/acpi.h>  #include <linux/input.h>  #include <linux/input/sparse-keymap.h> -  #ifndef ACPI_HOTKEY_COMPONENT  #define ACPI_HOTKEY_COMPONENT	0x10000000  #endif @@ -176,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[] = { @@ -189,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, @@ -196,9 +198,9 @@ 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,  };  static const struct key_entry panasonic_keymap[] = { @@ -447,6 +449,7 @@ static struct attribute_group pcc_attr_group = {  /* hotkey input device driver */ +static int sleep_keydown_seen;  static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)  {  	struct input_dev *hotk_input_dev = pcc->input_dev; @@ -461,7 +464,14 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)  		return;  	} -	acpi_bus_generate_proc_event(pcc->device, HKEY_NOTIFY, result); +	/* 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); +	}  	if (!sparse_keymap_report_event(hotk_input_dev,  					result & 0xf, result & 0x80, false)) @@ -489,11 +499,8 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc)  	int error;  	input_dev = input_allocate_device(); -	if (!input_dev) { -		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, -				  "Couldn't allocate input device for hotkey")); +	if (!input_dev)  		return -ENOMEM; -	}  	input_dev->name = ACPI_PCC_DRIVER_NAME;  	input_dev->phys = ACPI_PCC_INPUT_PHYS; @@ -538,11 +545,16 @@ static void acpi_pcc_destroy_input(struct pcc_acpi *pcc)  /* 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); +	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", @@ -550,6 +562,7 @@ static int acpi_pcc_hotkey_resume(struct acpi_device *device)  	return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);  } +#endif  static int acpi_pcc_hotkey_add(struct acpi_device *device)  { @@ -562,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;  	} @@ -602,6 +615,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)  	}  	/* 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, &props); @@ -635,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); @@ -671,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 f200677851b..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> @@ -59,9 +61,6 @@  #include <linux/workqueue.h>  #include <linux/acpi.h>  #include <linux/slab.h> -#include <acpi/acpi_drivers.h> -#include <acpi/acpi_bus.h> -#include <asm/uaccess.h>  #include <linux/sonypi.h>  #include <linux/sony-laptop.h>  #include <linux/rfkill.h> @@ -69,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" @@ -88,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); @@ -124,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, @@ -135,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 ***********/ @@ -235,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[] = { @@ -255,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 */ @@ -297,6 +358,7 @@ 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 */ @@ -327,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) { @@ -360,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;  		} @@ -369,9 +432,11 @@ 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);  		/* schedule key release */ @@ -400,7 +465,7 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device)  	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;  	} @@ -446,7 +511,7 @@ 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;  	input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE);  	input_set_capability(jog_dev, EV_REL, REL_WHEEL); @@ -589,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...) \ @@ -668,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;  		} -		return 0;  	} -	printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n"); +	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 -1; +	return 0; +} + +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;  }  /* @@ -790,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) @@ -811,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); @@ -820,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); @@ -828,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; @@ -839,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 @@ -888,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 },  }; @@ -919,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; +		} -	dprintk("sony_nc_notify, event: 0x%.2x\n", ev); -	acpi_bus_generate_proc_event(sony_nc_acpi_device, 1, ev); +		/* clear the event (and the event reason when present) */ +		arg = 1 << offset; +		sony_nc_int_call(sony_nc_acpi_handle, "SN05", &arg, &result); + +	} 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, @@ -986,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); @@ -998,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); +} + +#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 acpi_device *device) +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)  { @@ -1073,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);  } @@ -1090,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: @@ -1118,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); @@ -1131,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; @@ -1155,96 +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); +	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 +	 */ +	for (i = 0; i < ARRAY_SIZE(buffer); i++) { + +		if (buffer[i] == 0xff) +			break; + +		dprintk("Radio devices, found 0x%.2x\n", buffer[i]); + +		if (buffer[i] == 0 && !sony_rfkill_devices[SONY_WIFI]) +			sony_nc_setup_rfkill(device, SONY_WIFI); + +		if (buffer[i] == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH]) +			sony_nc_setup_rfkill(device, SONY_BLUETOOTH); + +		if (((0xf0 & buffer[i]) == 0x20 || +					(0xf0 & buffer[i]) == 0x50) && +				!sony_rfkill_devices[SONY_WWAN]) +			sony_nc_setup_rfkill(device, SONY_WWAN); + +		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; +}; -	/* need to read the whole buffer returned by the acpi call to SN06 -	 * here otherwise we may miss some features +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  	 */ -	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; +	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;  	} -	device_enum = (union acpi_object *) buffer.pointer; -	if (!device_enum) { -		pr_err("Invalid SN06 return object\n"); -		goto out_no_enum; +	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;  	} -	if (device_enum->type != ACPI_TYPE_BUFFER) { -		pr_err("Invalid SN06 return object type 0x%.2x\n", -		       device_enum->type); -		goto out_no_enum; + +	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 buffer is filled with magic numbers describing the devices -	 * available, 0xff terminates the enumeration +	/* 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"  	 */ -	for (i = 0; i < device_enum->buffer.length; i++) { +	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; +} -		dev_code = *(device_enum->buffer.pointer + i); -		if (dev_code == 0xff) +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"); + +	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; -		dprintk("Radio devices, looking at 0x%.2x\n", dev_code); +	if (sony_nc_thermal_mode_set(cmd)) +		return -EIO; -		if (dev_code == 0 && !sony_rfkill_devices[SONY_WIFI]) -			sony_nc_setup_rfkill(device, SONY_WIFI); +	return count; +} -		if (dev_code == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH]) -			sony_nc_setup_rfkill(device, SONY_BLUETOOTH); +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 ((0xf0 & dev_code) == 0x20 && -				!sony_rfkill_devices[SONY_WWAN]) -			sony_nc_setup_rfkill(device, SONY_WWAN); +	if (mode < 0) +		return mode; -		if (dev_code == 0x30 && !sony_rfkill_devices[SONY_WIMAX]) -			sony_nc_setup_rfkill(device, SONY_WIMAX); +	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;  	} -out_no_enum: -	kfree(buffer.pointer); -	return; +	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"); @@ -1259,65 +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))) { -							struct backlight_properties props; -		memset(&props, 0, sizeof(struct backlight_properties)); -		props.max_brightness = SONY_MAX_BRIGHTNESS - 1; -		sony_backlight_device = backlight_device_register("sony", NULL, -								  NULL, -								  &sony_backlight_ops, -								  &props); - -		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); -		} - +		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) { @@ -1326,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; @@ -1338,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; @@ -1357,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; @@ -1389,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; @@ -1417,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 ***********/ @@ -1427,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 @@ -1573,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 }  }; @@ -1831,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 */ @@ -1879,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;  	} @@ -1899,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;  	} @@ -1922,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;  	} @@ -1978,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); @@ -2006,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); @@ -2043,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); @@ -2082,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; @@ -2198,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);  	} @@ -2236,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;  		} @@ -2249,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;  		} @@ -2257,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)) { @@ -2385,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;  	} @@ -2395,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; @@ -2459,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), @@ -2499,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; @@ -2527,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;  	} @@ -2543,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: @@ -2631,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 { @@ -2650,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 */ @@ -2658,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;  	} @@ -2714,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;  			}  		} @@ -2731,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;  } @@ -2742,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;  	} @@ -2788,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); @@ -2799,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); @@ -2826,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); @@ -2849,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;  	} @@ -2857,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], @@ -2869,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;  	} @@ -2877,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;  	} @@ -2891,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: @@ -2930,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}, @@ -2956,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[] = { @@ -2986,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; @@ -2995,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 1fe0f1feff7..6a6ea28a7e5 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -25,14 +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" @@ -40,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"); @@ -162,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); @@ -188,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); @@ -242,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) @@ -264,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 2d61186ad5a..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  /* @@ -59,7 +61,6 @@  #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> @@ -72,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 @@ -128,7 +125,8 @@ enum {  };  /* 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 */ @@ -181,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 */ @@ -191,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 */ @@ -198,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 */  }; @@ -223,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 @@ -273,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; @@ -293,7 +291,7 @@ struct ibm_init_struct {  	char param[32];  	int (*init) (struct ibm_init_struct *); -	mode_t base_procfs_mode; +	umode_t base_procfs_mode;  	struct ibm_struct *data;  }; @@ -366,7 +364,7 @@ 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 */ @@ -374,13 +372,13 @@ 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 @@ -388,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 @@ -516,7 +516,7 @@ static acpi_handle ec_handle;  #define TPACPI_HANDLE(object, parent, paths...)			\  	static acpi_handle  object##_handle;			\ -	static const acpi_handle *object##_parent __initdata =	\ +	static const acpi_handle * const object##_parent __initconst =	\  						&parent##_handle; \  	static char *object##_paths[] __initdata = { paths } @@ -534,21 +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.VGA",	/* X100e and a few others */ -	   "\\_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; @@ -562,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;  	} @@ -587,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;  		}  	} @@ -608,20 +600,20 @@ static int acpi_evalf(acpi_handle handle,  		success = (status == AE_OK &&  			   out_obj.type == ACPI_TYPE_INTEGER);  		if (success && res) -			*(int *)res = out_obj.integer.value; +			*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: %s\n", +		pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",  		       method, fmt0, acpi_format_exception(status));  	return success; @@ -703,6 +695,14 @@ static void __init drv_acpi_handle_init(const char *name,  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; @@ -715,10 +715,10 @@ static void __init tpacpi_acpi_handle_locate(const char *name,  	acpi_status status;  	acpi_handle device_found; -	BUG_ON(!name || !hid || !handle); +	BUG_ON(!name || !handle);  	vdbg_printk(TPACPI_DBG_INIT,  			"trying to locate ACPI handle for %s, using HID %s\n", -			name, hid); +			name, hid ? hid : "NULL");  	memset(&device_found, 0, sizeof(device_found));  	status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback, @@ -765,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;  	} @@ -779,12 +778,10 @@ 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: %s\n", +			pr_err("acpi_install_notify_handler(%s) failed: %s\n",  			       ibm->name, acpi_format_exception(status));  		}  		return -ENODEV; @@ -809,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;  	} @@ -821,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; @@ -851,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; @@ -928,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; @@ -937,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; @@ -956,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)  { @@ -973,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,  }; @@ -1079,15 +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"); +		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);  } @@ -1262,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;  	} @@ -1273,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) { @@ -1289,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; @@ -1299,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;  } @@ -1823,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");  	}  } @@ -1975,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. @@ -2036,8 +2025,6 @@ 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; @@ -2115,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);  }  /* @@ -2155,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) @@ -2182,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. @@ -2274,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); @@ -2305,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) @@ -2347,53 +2321,55 @@ static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m)  	}  } -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) +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) +do { \ +	if (event_mask & (1 << __scancode)) \ +		tpacpi_hotkey_send_key(__scancode); \ +} while (0) -	void issue_volchange(const unsigned int oldvol, -			     const unsigned int newvol) -	{ -		unsigned int i = oldvol; +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++; -		} +	while (i > newvol) { +		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); +		i--;  	} +	while (i < newvol) { +		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); +		i++; +	} +} -	void issue_brightnesschange(const unsigned int oldbrt, -				    const unsigned int newbrt) -	{ -		unsigned int i = oldbrt; +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++; -		} +	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) +{  	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);  	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle); @@ -2410,7 +2386,7 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,  	 * 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 minumum volume +	 *   even when already at maximum or minimum volume  	 * - The act of unmuting issues volume up/down notification,  	 *   depending which key was used to unmute  	 * @@ -2428,7 +2404,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,  		    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); +			issue_volchange(oldn->volume_level, newn->volume_level, +				event_mask);  			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);  		}  	} else { @@ -2438,7 +2415,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,  			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);  		}  		if (oldn->volume_level != newn->volume_level) { -			issue_volchange(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) @@ -2451,7 +2429,7 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,  	/* handle brightness */  	if (oldn->brightness_level != newn->brightness_level) {  		issue_brightnesschange(oldn->brightness_level, -				       newn->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) @@ -2478,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; @@ -2510,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; @@ -2542,7 +2519,6 @@ static int hotkey_kthread(void *data)  	}  exit: -	mutex_unlock(&hotkey_thread_mutex);  	return 0;  } @@ -2550,15 +2526,8 @@ 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);  	}  } @@ -2576,8 +2545,7 @@ static void hotkey_poll_setup(const 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");  			}  		} @@ -2585,11 +2553,10 @@ static void hotkey_poll_setup(const 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);  		}  	}  } @@ -2813,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); @@ -2916,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, @@ -2969,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, @@ -2993,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. @@ -3041,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 @@ -3050,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");  } @@ -3211,8 +3162,19 @@ 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,  		},  	}; @@ -3253,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 @@ -3290,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, @@ -3306,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 */ @@ -3345,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) @@ -3365,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, @@ -3384,8 +3340,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)  	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;  	} @@ -3427,14 +3382,12 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)  	/* Do not issue duplicate brightness change events to  	 * userspace. tpacpi_detect_brightness_capabilities() must have  	 * been called before this point  */ -	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"); +	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 @@ -3476,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; @@ -3495,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) @@ -3514,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;  } @@ -3541,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 */ @@ -3553,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) @@ -3595,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;  	} @@ -3654,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, @@ -3666,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;  		} @@ -3694,8 +3777,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)  			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;  				break; @@ -3708,16 +3790,9 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)  			}  			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 */ @@ -3725,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: @@ -3743,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 */ @@ -3767,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(); @@ -3789,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 -------------------------------------------------------------- */ @@ -3826,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) @@ -3882,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},  }; @@ -4012,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) @@ -4052,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 && @@ -4204,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"); @@ -4242,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 && @@ -4383,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 && @@ -4445,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) @@ -4488,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");  } @@ -4561,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; @@ -4631,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;  	} @@ -4885,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) @@ -5225,6 +5318,7 @@ static void led_exit(void)  			led_classdev_unregister(&tpacpi_leds[i].led_classdev);  	} +	flush_workqueue(tpacpi_wq);  	kfree(tpacpi_leds);  } @@ -5340,6 +5434,16 @@ static int __init led_init(struct ibm_init_struct *iibm)  	led_supported = led_init_detect_mode(); +	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); @@ -5349,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); @@ -5368,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;  } @@ -5415,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")) { @@ -5640,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 -------------------------------------------------- */ @@ -5770,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;  			} @@ -6109,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,  }; @@ -6125,19 +6228,28 @@ 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_SUCCESS(acpi_evaluate_object(handle, "_BCL", NULL, &buffer))) { +	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)) { -			printk(TPACPI_ERR "Unknown _BCL data, " -			       "please report this to %s\n", TPACPI_MAIL); +			pr_err("Unknown _BCL data, please report this to %s\n", +				TPACPI_MAIL);  			rc = 0;  		} else {  			rc = obj->package.count;  		} -	} else { -		return 0; +		break;  	}  	kfree(buffer.pointer); @@ -6153,7 +6265,7 @@ static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)  	acpi_handle video_device;  	int bcl_levels = 0; -	tpacpi_acpi_handle_locate("video", ACPI_VIDEO_HID, &video_device); +	tpacpi_acpi_handle_locate("video", NULL, &video_device);  	if (video_device)  		bcl_levels = tpacpi_query_bcl_levels(video_device); @@ -6215,18 +6327,15 @@ static void __init tpacpi_detect_brightness_capabilities(void)  	switch (b) {  	case 16:  		bright_maxlvl = 15; -		printk(TPACPI_INFO -		       "detected a 16-level brightness capable ThinkPad\n"); +		pr_info("detected a 16-level brightness capable ThinkPad\n");  		break;  	case 8:  	case 0:  		bright_maxlvl = 7; -		printk(TPACPI_INFO -		       "detected a 8-level brightness capable ThinkPad\n"); +		pr_info("detected a 8-level brightness capable ThinkPad\n");  		break;  	default: -		printk(TPACPI_ERR -		       "Unsupported brightness interface, " +		pr_err("Unsupported brightness interface, "  		       "please contact %s\n", TPACPI_MAIL);  		tp_features.bright_unkfw = 1;  		bright_maxlvl = b - 1; @@ -6261,22 +6370,19 @@ static int __init brightness_init(struct ibm_init_struct *iibm)  	if (acpi_video_backlight_support()) {  		if (brightness_enable > 1) { -			printk(TPACPI_INFO -			       "Standard ACPI backlight interface " -			       "available, not loading native one.\n"); +			pr_info("Standard ACPI backlight interface " +				"available, not loading native one\n");  			return 1;  		} else if (brightness_enable == 1) { -			printk(TPACPI_WARN -				"Cannot enable backlight brightness support, " +			pr_warn("Cannot enable backlight brightness support, "  				"ACPI is already handling it.  Refer to the " -				"acpi_backlight kernel parameter\n"); +				"acpi_backlight kernel parameter.\n");  			return 1;  		}  	} else if (tp_features.bright_acpimode && brightness_enable > 1) { -		printk(TPACPI_NOTICE -			"Standard ACPI backlight interface not " -			"available, thinkpad_acpi native " -			"brightness control enabled\n"); +		pr_notice("Standard ACPI backlight interface not " +			  "available, thinkpad_acpi native " +			  "brightness control enabled\n");  	}  	/* @@ -6310,6 +6416,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm)  		return 1;  	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, @@ -6319,19 +6426,17 @@ static int __init brightness_init(struct ibm_init_struct *iibm)  	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);  	}  	/* Added by mistake in early 2007.  Probably useless, but it could @@ -6345,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();  } @@ -6462,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; @@ -6507,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 @@ -6750,7 +6860,7 @@ static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,  	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, @@ -6759,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, @@ -6768,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();  } @@ -6801,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;  	} @@ -6839,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; @@ -6850,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;  	} @@ -6915,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;  	} @@ -6981,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)"); @@ -7049,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;  	} @@ -7129,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;  } @@ -7193,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: @@ -7294,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; @@ -7337,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;  	}  } @@ -7414,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))) @@ -7712,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) @@ -7741,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();  	} @@ -8049,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;  		} @@ -8139,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; @@ -8150,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 */ @@ -8207,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);  	}  } @@ -8305,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); @@ -8321,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"); @@ -8336,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"); @@ -8356,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); @@ -8416,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, +}; +  /****************************************************************************   ****************************************************************************   * @@ -8472,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) @@ -8497,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) { @@ -8561,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;  			} @@ -8575,7 +8757,7 @@ static int __init ibm_init(struct ibm_init_struct *iibm)  		"%s installed\n", ibm->name);  	if (ibm->read) { -		mode_t mode = iibm->base_procfs_mode; +		umode_t mode = iibm->base_procfs_mode;  		if (!mode)  			mode = S_IRUGO; @@ -8584,8 +8766,7 @@ static int __init ibm_init(struct ibm_init_struct *iibm)  		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;  		} @@ -8619,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. @@ -8653,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] @@ -8685,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); @@ -8735,8 +8922,7 @@ static int __init probe_for_thinkpad(void)  	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;  	} @@ -8748,10 +8934,10 @@ static int __init probe_for_thinkpad(void)  static void __init thinkpad_acpi_init_banner(void)  { -	printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION); -	printk(TPACPI_INFO "%s\n", TPACPI_URL); +	pr_info("%s v%s\n", TPACPI_DESC, TPACPI_VERSION); +	pr_info("%s\n", TPACPI_URL); -	printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n", +	pr_info("ThinkPad BIOS %s, EC %s\n",  		(thinkpad_id.bios_version_str) ?  			thinkpad_id.bios_version_str : "unknown",  		(thinkpad_id.ec_version_str) ? @@ -8760,7 +8946,7 @@ static void __init thinkpad_acpi_init_banner(void)  	BUG_ON(!thinkpad_id.vendor);  	if (thinkpad_id.model_str) -		printk(TPACPI_INFO "%s %s, model %s\n", +		pr_info("%s %s, model %s\n",  			(thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?  				"IBM" : ((thinkpad_id.vendor ==  						PCI_VENDOR_ID_LENOVO) ? @@ -8831,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) @@ -8885,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, @@ -8976,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) @@ -9009,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);  } @@ -9018,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;  	} @@ -9053,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;  	} @@ -9070,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;  	} @@ -9084,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;  	} @@ -9098,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;  	} @@ -9108,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;  	} @@ -9126,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 { @@ -9165,7 +9340,7 @@ static int __init thinkpad_acpi_module_init(void)  	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 { diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index 1d07d6d09f2..e597de05e6c 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -41,6 +41,7 @@ static const struct key_entry topstar_keymap[] = {  	{ KE_KEY, 0x8c, { KEY_MEDIA } },  	/* 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, }, @@ -79,13 +80,9 @@ static void acpi_topstar_notify(struct acpi_device *device, u32 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; @@ -100,10 +97,8 @@ static int acpi_topstar_init_hkey(struct topstar_hkey *hkey)  	int error;  	input = input_allocate_device(); -	if (!input) { -		pr_err("Unable to allocate input device\n"); +	if (!input)  		return -ENOMEM; -	}  	input->name = "Topstar Laptop extra buttons";  	input->phys = "topstar/input0"; @@ -156,7 +151,7 @@ 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); @@ -185,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 06f304f46e0..76441dcbe5f 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -5,6 +5,7 @@   *  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 @@ -35,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> @@ -45,32 +48,28 @@  #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 TOSH_INTERFACE_1	"\\_SB_.VALD" -#define TOSH_INTERFACE_2	"\\_SB_.VALZ"  #define METHOD_VIDEO_OUT	"\\_SB_.VALX.DSSX" -#define GHCI_METHOD		".GHCI"  /* Toshiba HCI interface definitions   * @@ -80,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 @@ -87,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 @@ -113,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}, @@ -122,10 +185,13 @@ static const struct acpi_device_id toshiba_device_ids[] = {  };  MODULE_DEVICE_TABLE(acpi, toshiba_device_ids); -static const struct key_entry toshiba_acpi_keymap[] __initconst = { +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 } }, @@ -134,7 +200,8 @@ static const struct key_entry toshiba_acpi_keymap[] __initconst = {  	{ KE_KEY, 0x140, { KEY_BRIGHTNESSDOWN } },  	{ KE_KEY, 0x141, { KEY_BRIGHTNESSUP } },  	{ KE_KEY, 0x142, { KEY_WLAN } }, -	{ KE_KEY, 0x143, { KEY_PROG1 } }, +	{ 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 } }, @@ -143,6 +210,31 @@ static const struct key_entry toshiba_acpi_keymap[] __initconst = {  	{ 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 },  }; @@ -157,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); +	status = acpi_execute_simple_method(NULL, (char *)methodName, val); +	return (status == AE_OK) ? 0 : -EIO;  } -#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); -} -#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]; @@ -222,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) { @@ -239,189 +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; -	struct input_dev *hotkey_dev; -	int illumination_installed; -	acpi_handle handle; +/* common sci tasks + */ + +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; -	const char *bt_name; +	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; +	} -	struct mutex mutex; -}; +	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(void) +static int toshiba_illumination_available(struct toshiba_acpi_dev *dev)  { -	u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; +	u32 in[HCI_WORDS] = { SCI_GET, SCI_ILLUMINATION, 0, 0, 0, 0 };  	u32 out[HCI_WORDS];  	acpi_status status; -	in[0] = 0xf100; -	status = hci_raw(in, out); -	if (ACPI_FAILURE(status)) { -		printk(MY_INFO "Illumination device not available\n"); +	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;  	} -	in[0] = 0xf400; -	status = hci_raw(in, out); +  	return 1;  }  static void toshiba_illumination_set(struct led_classdev *cdev,  				     enum led_brightness brightness)  { -	u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; -	u32 out[HCI_WORDS]; +	struct toshiba_acpi_dev *dev = container_of(cdev, +			struct toshiba_acpi_dev, led_dev); +	u32 state, result;  	acpi_status status;  	/* First request : initialize communication. */ -	in[0] = 0xf100; -	status = hci_raw(in, out); +	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)) { -		printk(MY_INFO "Illumination device not available\n"); +		pr_err("ACPI call for illumination failed\n"); +		return; +	} else if (result == HCI_NOT_SUPPORTED) { +		pr_info("Illumination not supported\n");  		return;  	} +} -	if (brightness) { -		/* Switch the illumination on */ -		in[0] = 0xf400; -		in[1] = 0x14e; -		in[2] = 1; -		status = hci_raw(in, out); -		if (ACPI_FAILURE(status)) { -			printk(MY_INFO "ACPI call for illumination failed.\n"); -			return; -		} -	} else { -		/* Switch the illumination off */ -		in[0] = 0xf400; -		in[1] = 0x14e; -		in[2] = 0; -		status = hci_raw(in, out); -		if (ACPI_FAILURE(status)) { -			printk(MY_INFO "ACPI call for illumination failed.\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;  	} -	/* Last request : close communication. */ -	in[0] = 0xf200; -	in[1] = 0; -	in[2] = 0; -	hci_raw(in, out); +	return state ? LED_FULL : LED_OFF;  } -static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) +/* KBD Illumination */ +static int toshiba_kbd_illum_status_set(struct toshiba_acpi_dev *dev, u32 time)  { -	u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; -	u32 out[HCI_WORDS]; +	u32 result;  	acpi_status status; -	enum led_brightness result; -	/* First request : initialize communication. */ -	in[0] = 0xf100; -	status = hci_raw(in, out); -	if (ACPI_FAILURE(status)) { -		printk(MY_INFO "Illumination device not available\n"); +	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;  	} -	/* Check the illumination */ -	in[0] = 0xf300; -	in[1] = 0x14e; -	status = hci_raw(in, out); +	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)) { -		printk(MY_INFO "ACPI call for illumination failed.\n"); +		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;  	} -	result = out[2] ? LED_FULL : LED_OFF; +	return out[2] ? LED_FULL : LED_OFF; +} -	/* Last request : close communication. */ -	in[0] = 0xf200; -	in[1] = 0; -	in[2] = 0; -	hci_raw(in, out); +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; -	return result; +	/* 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; +	}  } -static struct led_classdev toshiba_led = { -	.name           = "toshiba::illumination", -	.max_brightness = 1, -	.brightness_set = toshiba_illumination_set, -	.brightness_get = toshiba_illumination_get, -}; +/* 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; -static struct toshiba_acpi_dev toshiba_acpi = { -	.bt_name = "Toshiba Bluetooth", -}; +	/* 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; @@ -438,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;  	} @@ -448,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: @@ -469,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); @@ -489,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)) @@ -558,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 { @@ -577,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); @@ -646,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); @@ -658,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 = { @@ -675,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; @@ -711,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;  	} @@ -734,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; @@ -780,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;  	} @@ -806,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 = { @@ -822,247 +1189,711 @@ static const struct file_operations version_proc_fops = {  #define PROC_TOSHIBA		"toshiba" -static void __init create_toshiba_proc_entries(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); +	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 void remove_toshiba_proc_entries(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);  } -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_notify(acpi_handle handle, u32 event, void *context) +/* + * Sysfs files + */ + +static ssize_t toshiba_kbd_bl_mode_store(struct device *dev, +					 struct device_attribute *attr, +					 const char *buf, size_t count)  { -	u32 hci_result, value; +	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); +	int mode = -1; +	int time = -1; -	if (event != 0x80) +	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; +	} + +	return count; +} + +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_kbd_illum_status_get(toshiba, &time) < 0) +		return -EIO; + +	return sprintf(buf, "%i\n", time & 0x07); +} + +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 ssize_t toshiba_kbd_bl_timeout_show(struct device *dev, +					   struct device_attribute *attr, +					   char *buf) +{ +	struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev); +	u32 time; + +	if (toshiba_kbd_illum_status_get(toshiba, &time) < 0) +		return -EIO; + +	return sprintf(buf, "%i\n", time >> HCI_MISC_SHIFT); +} + +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); +} + +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; + +	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; +	} + +	return false; +} + +static void toshiba_acpi_hotkey_work(struct work_struct *work) +{ +	acpi_handle ec_handle = ec_get_handle(); +	acpi_status status; + +	if (!ec_handle)  		return; -	do { -		hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); -		if (hci_result == HCI_SUCCESS) { -			if (value == 0x100) -				continue; -			/* act on key press; ignore key release */ -			if (value & 0x80) -				continue; - -			if (!sparse_keymap_report_event(toshiba_acpi.hotkey_dev, -							value, 1, true)) { -				printk(MY_INFO "Unknown key %x\n", -				       value); -			} -		} 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"); -		} -	} while (hci_result != HCI_EMPTY); + +	status = acpi_evaluate_object(ec_handle, "NTFY", NULL, NULL); +	if (ACPI_FAILURE(status)) +		pr_err("ACPI NTFY method execution failed\n");  } -static int __init toshiba_acpi_setup_keyboard(char *device) +/* + * 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; -	int error; -	status = acpi_get_handle(NULL, device, &toshiba_acpi.handle); +	status = acpi_evaluate_integer(dev->acpi_dev->handle, "INFO", +				      NULL, &value);  	if (ACPI_FAILURE(status)) { -		printk(MY_INFO "Unable to get notification device\n"); -		return -ENODEV; +		pr_err("ACPI INFO method execution failed\n"); +		return -EIO;  	} -	toshiba_acpi.hotkey_dev = input_allocate_device(); -	if (!toshiba_acpi.hotkey_dev) { -		printk(MY_INFO "Unable to register input device\n"); +	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; -	} -	toshiba_acpi.hotkey_dev->name = "Toshiba input device"; -	toshiba_acpi.hotkey_dev->phys = device; -	toshiba_acpi.hotkey_dev->id.bustype = BUS_HOST; +	dev->hotkey_dev->name = "Toshiba input device"; +	dev->hotkey_dev->phys = "toshiba_acpi/input0"; +	dev->hotkey_dev->id.bustype = BUS_HOST; -	error = sparse_keymap_setup(toshiba_acpi.hotkey_dev, -				    toshiba_acpi_keymap, NULL); +	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; -	status = acpi_install_notify_handler(toshiba_acpi.handle, -				ACPI_DEVICE_NOTIFY, toshiba_acpi_notify, NULL); -	if (ACPI_FAILURE(status)) { -		printk(MY_INFO "Unable to install hotkey notification\n"); -		error = -ENODEV; -		goto err_free_keymap; +	/* +	 * 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(toshiba_acpi.handle, "ENAB", NULL, NULL); +	status = acpi_evaluate_object(dev->acpi_dev->handle, "ENAB", NULL, NULL);  	if (ACPI_FAILURE(status)) { -		printk(MY_INFO "Unable to enable hotkeys\n"); +		pr_info("Unable to enable hotkeys\n");  		error = -ENODEV; -		goto err_remove_notify; +		goto err_remove_filter;  	} -	error = input_register_device(toshiba_acpi.hotkey_dev); +	error = input_register_device(dev->hotkey_dev);  	if (error) { -		printk(MY_INFO "Unable to register input device\n"); -		goto err_remove_notify; +		pr_info("Unable to register input device\n"); +		goto err_remove_filter;  	} +	hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &hci_result);  	return 0; - err_remove_notify: -	acpi_remove_notify_handler(toshiba_acpi.handle, -				   ACPI_DEVICE_NOTIFY, toshiba_acpi_notify); + err_remove_filter: +	if (dev->ntfy_supported) +		i8042_remove_filter(toshiba_acpi_i8042_filter);   err_free_keymap: -	sparse_keymap_free(toshiba_acpi.hotkey_dev); +	sparse_keymap_free(dev->hotkey_dev);   err_free_dev: -	input_free_device(toshiba_acpi.hotkey_dev); -	toshiba_acpi.hotkey_dev = NULL; +	input_free_device(dev->hotkey_dev); +	dev->hotkey_dev = NULL;  	return error;  } -static void toshiba_acpi_exit(void) +static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev) +{ +	struct backlight_properties props; +	int brightness; +	int ret; +	bool enabled; + +	/* +	 * 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; +	} + +	dev->backlight_dev->props.brightness = brightness; +	return 0; +} + +static int toshiba_acpi_remove(struct acpi_device *acpi_dev)  { -	if (toshiba_acpi.hotkey_dev) { -		acpi_remove_notify_handler(toshiba_acpi.handle, -				ACPI_DEVICE_NOTIFY, toshiba_acpi_notify); -		sparse_keymap_free(toshiba_acpi.hotkey_dev); -		input_unregister_device(toshiba_acpi.hotkey_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 (toshiba_acpi.bt_rfk) { -		rfkill_unregister(toshiba_acpi.bt_rfk); -		rfkill_destroy(toshiba_acpi.bt_rfk); +	if (dev->hotkey_dev) { +		input_unregister_device(dev->hotkey_dev); +		sparse_keymap_free(dev->hotkey_dev);  	} -	if (toshiba_backlight_device) -		backlight_device_unregister(toshiba_backlight_device); +	if (dev->bt_rfk) { +		rfkill_unregister(dev->bt_rfk); +		rfkill_destroy(dev->bt_rfk); +	} -	remove_toshiba_proc_entries(); +	if (dev->backlight_dev) +		backlight_device_unregister(dev->backlight_dev); -	if (toshiba_proc_dir) -		remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); +	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; -	if (toshiba_acpi.illumination_installed) -		led_classdev_unregister(&toshiba_led); +	kfree(dev); -	platform_device_unregister(toshiba_acpi.p_dev); +	return 0; +} + +static const char *find_hci_method(acpi_handle handle) +{ +	if (acpi_has_method(handle, "GHCI")) +		return "GHCI"; -	return; +	if (acpi_has_method(handle, "SPFC")) +		return "SPFC"; + +	return NULL;  } -static int __init toshiba_acpi_init(void) +static int toshiba_acpi_add(struct acpi_device *acpi_dev)  { -	u32 hci_result; +	struct toshiba_acpi_dev *dev; +	const char *hci_method; +	u32 dummy;  	bool bt_present;  	int ret = 0; -	struct backlight_properties props; -	if (acpi_disabled) -		return -ENODEV; - -	/* simple device detection: look for HCI method */ -	if (is_valid_acpi_path(TOSH_INTERFACE_1 GHCI_METHOD)) { -		method_hci = TOSH_INTERFACE_1 GHCI_METHOD; -		if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_1)) -			printk(MY_INFO "Unable to activate hotkeys\n"); -	} else if (is_valid_acpi_path(TOSH_INTERFACE_2 GHCI_METHOD)) { -		method_hci = TOSH_INTERFACE_2 GHCI_METHOD; -		if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_2)) -			printk(MY_INFO "Unable to activate hotkeys\n"); -	} else -		return -ENODEV; +	if (toshiba_acpi) +		return -EBUSY; -	printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", +	pr_info("Toshiba Laptop ACPI Extras version %s\n",  	       TOSHIBA_ACPI_VERSION); -	printk(MY_INFO "    HCI method: %s\n", method_hci); - -	mutex_init(&toshiba_acpi.mutex); -	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(); -		return ret; +	hci_method = find_hci_method(acpi_dev->handle); +	if (!hci_method) { +		pr_err("HCI interface not found\n"); +		return -ENODEV;  	} -	force_fan = 0; -	key_event_valid = 0; +	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); -	/* enable event fifo */ -	hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); +	if (toshiba_acpi_setup_keyboard(dev)) +		pr_info("Unable to activate hotkeys\n"); -	toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); -	if (!toshiba_proc_dir) { -		toshiba_acpi_exit(); -		return -ENODEV; -	} else { -		create_toshiba_proc_entries(); -	} +	mutex_init(&dev->mutex); -	props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; -	toshiba_backlight_device = backlight_device_register("toshiba", -							     &toshiba_acpi.p_dev->dev, -							     NULL, -							     &toshiba_backlight_data, -							     &props); -        if (IS_ERR(toshiba_backlight_device)) { -		ret = PTR_ERR(toshiba_backlight_device); - -		printk(KERN_ERR "Could not register toshiba backlight device\n"); -		toshiba_backlight_device = NULL; -		toshiba_acpi_exit(); -		return ret; -	} +	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;  		}  	} -	toshiba_acpi.illumination_installed = 0; -	if (toshiba_illumination_available()) { -		if (!led_classdev_register(&(toshiba_acpi.p_dev->dev), -					   &toshiba_led)) -			toshiba_acpi.illumination_installed = 1; +	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 94406861191..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,9 +52,9 @@ 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,  }; @@ -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,10 +90,12 @@ 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)  { @@ -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 104b77c87ef..43d13295e63 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -36,8 +36,7 @@  #include <linux/list.h>  #include <linux/acpi.h>  #include <linux/slab.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/module.h>  ACPI_MODULE_NAME("wmi");  MODULE_AUTHOR("Carlos Corbacho"); @@ -81,17 +80,17 @@ struct wmi_block {  #define ACPI_WMI_STRING      0x4	/* GUID takes & returns a string */  #define ACPI_WMI_EVENT       0x8	/* GUID is an event */ -static int debug_event; +static bool debug_event;  module_param(debug_event, bool, 0444);  MODULE_PARM_DESC(debug_event,  		 "Log WMI Events [0/1]"); -static int debug_dump_wdg; +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, int type); +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); @@ -251,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; @@ -262,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; @@ -352,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"; @@ -385,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);  		/* @@ -397,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"); @@ -413,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; @@ -486,16 +471,16 @@ static void wmi_dump_wdg(const struct guid_block *g)  	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); +	pr_info("\tflags: %#x", g->flags);  	if (g->flags) {  		if (g->flags & ACPI_WMI_EXPENSIVE) -			pr_cont("ACPI_WMI_EXPENSIVE "); +			pr_cont(" ACPI_WMI_EXPENSIVE");  		if (g->flags & ACPI_WMI_METHOD) -			pr_cont("ACPI_WMI_METHOD "); +			pr_cont(" ACPI_WMI_METHOD");  		if (g->flags & ACPI_WMI_STRING) -			pr_cont("ACPI_WMI_STRING "); +			pr_cont(" ACPI_WMI_STRING");  		if (g->flags & ACPI_WMI_EVENT) -			pr_cont("ACPI_WMI_EVENT "); +			pr_cont(" ACPI_WMI_EVENT");  	}  	pr_cont("\n"); @@ -549,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); -	if (block->handler && block->handler != wmi_notify_debug) -		return AE_ALREADY_ACQUIRED; +	list_for_each(p, &wmi_block_list) { +		acpi_status wmi_status; +		block = list_entry(p, struct wmi_block, list); -	block->handler = handler; -	block->handler_data = data; +		if (memcmp(block->gblock.guid, guid_input, 16) == 0) { +			if (block->handler && +			    block->handler != wmi_notify_debug) +				return AE_ALREADY_ACQUIRED; -	status = wmi_method_enable(block, 1); +			block->handler = handler; +			block->handler_data = data; + +			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;  } @@ -577,24 +575,40 @@ EXPORT_SYMBOL_GPL(wmi_install_notify_handler);  acpi_status wmi_remove_notify_handler(const char *guid)  {  	struct wmi_block *block; -	acpi_status status = AE_OK; +	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; +	wmi_parse_guid(guid, tmp); +	wmi_swap_bytes(tmp, guid_input); -	if (!block->handler || block->handler == wmi_notify_debug) -		return AE_NULL_ENTRY; +	list_for_each(p, &wmi_block_list) { +		acpi_status wmi_status; +		block = list_entry(p, struct wmi_block, list); -	if (debug_event) { -		block->handler = wmi_notify_debug; -	} else { -		status = wmi_method_enable(block, 0); -		block->handler = NULL; -		block->handler_data = NULL; +		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;  }  EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); @@ -656,18 +670,22 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,  	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_RO(modalias); -static struct device_attribute wmi_dev_attrs[] = { -	__ATTR_RO(modalias), -	__ATTR_NULL +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)  { @@ -702,43 +720,22 @@ static struct class wmi_class = {  	.name = "wmi",  	.dev_release = wmi_dev_free,  	.dev_uevent = wmi_dev_uevent, -	.dev_attrs = wmi_dev_attrs, +	.dev_groups = wmi_groups,  }; -static struct wmi_block *wmi_create_device(const struct guid_block *gblock, -					   acpi_handle handle) +static int wmi_create_device(const struct guid_block *gblock, +			     struct wmi_block *wblock, acpi_handle handle)  { -	struct wmi_block *wblock; -	int error;  	char guid_string[37]; -	wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); -	if (!wblock) { -		error = -ENOMEM; -		goto err_out; -	} - -	wblock->handle = handle; -	wblock->gblock = *gblock; -  	wblock->dev.class = &wmi_class;  	wmi_gtoa(gblock->guid, guid_string); -	dev_set_name(&wblock->dev, guid_string); +	dev_set_name(&wblock->dev, "%s", guid_string);  	dev_set_drvdata(&wblock->dev, wblock); -	error = device_register(&wblock->dev); -	if (error) -		goto err_free; - -	list_add_tail(&wblock->list, &wmi_block_list); -	return wblock; - -err_free: -	kfree(wblock); -err_out: -	return ERR_PTR(error); +	return device_register(&wblock->dev);  }  static void wmi_free_devices(void) @@ -746,8 +743,13 @@ static void wmi_free_devices(void)  	struct wmi_block *wblock, *next;  	/* Delete devices for all the GUIDs */ -	list_for_each_entry_safe(wblock, next, &wmi_block_list, list) -		device_unregister(&wblock->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 bool guid_already_parsed(const char *guid_string) @@ -755,7 +757,7 @@ static bool guid_already_parsed(const char *guid_string)  	struct wmi_block *wblock;  	list_for_each_entry(wblock, &wmi_block_list, list) -		if (strncmp(wblock->gblock.guid, guid_string, 16) == 0) +		if (memcmp(wblock->gblock.guid, guid_string, 16) == 0)  			return true;  	return false; @@ -764,13 +766,12 @@ static bool guid_already_parsed(const char *guid_string)  /*   * Parse the _WDG method for the GUID data blocks   */ -static 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;  	const struct guid_block *gblock;  	struct wmi_block *wblock; -	char guid_string[37];  	acpi_status status;  	int retval;  	u32 i, total; @@ -792,28 +793,31 @@ static acpi_status parse_wdg(acpi_handle handle)  	total = obj->buffer.length / sizeof(struct guid_block);  	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; + +		wblock->handle = handle; +		wblock->gblock = gblock[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); -			pr_info("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; +			}  		} -		if (debug_dump_wdg) -			wmi_dump_wdg(&gblock[i]); - -		wblock = wmi_create_device(&gblock[i], handle); -		if (IS_ERR(wblock)) { -			retval = PTR_ERR(wblock); -			wmi_free_devices(); -			break; -		} +		list_add_tail(&wblock->list, &wmi_block_list);  		if (debug_event) {  			wblock->handler = wmi_notify_debug; @@ -901,7 +905,7 @@ 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); diff --git a/drivers/platform/x86/xo1-rfkill.c b/drivers/platform/x86/xo1-rfkill.c index e549eeeda12..4bd17248dfc 100644 --- a/drivers/platform/x86/xo1-rfkill.c +++ b/drivers/platform/x86/xo1-rfkill.c @@ -12,25 +12,35 @@  #include <linux/module.h>  #include <linux/platform_device.h>  #include <linux/rfkill.h> +#include <linux/olpc-ec.h> -#include <asm/olpc.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; -	return olpc_ec_cmd(cmd, NULL, 0, NULL, 0); +	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 __devinit xo1_rfkill_probe(struct platform_device *pdev) +static int xo1_rfkill_probe(struct platform_device *pdev)  {  	struct rfkill *rfk;  	int r; @@ -50,7 +60,7 @@ static int __devinit xo1_rfkill_probe(struct platform_device *pdev)  	return 0;  } -static int __devexit xo1_rfkill_remove(struct platform_device *pdev) +static int xo1_rfkill_remove(struct platform_device *pdev)  {  	struct rfkill *rfk = platform_get_drvdata(pdev);  	rfkill_unregister(rfk); @@ -64,22 +74,11 @@ static struct platform_driver xo1_rfkill_driver = {  		.owner = THIS_MODULE,  	},  	.probe		= xo1_rfkill_probe, -	.remove		= __devexit_p(xo1_rfkill_remove), +	.remove		= xo1_rfkill_remove,  }; -static int __init xo1_rfkill_init(void) -{ -	return platform_driver_register(&xo1_rfkill_driver); -} - -static void __exit xo1_rfkill_exit(void) -{ -	platform_driver_unregister(&xo1_rfkill_driver); -} +module_platform_driver(xo1_rfkill_driver);  MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>");  MODULE_LICENSE("GPL");  MODULE_ALIAS("platform:xo1-rfkill"); - -module_init(xo1_rfkill_init); -module_exit(xo1_rfkill_exit); 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);  | 
