summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2011-03-03 17:02:09 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2011-03-03 17:02:15 +1100
commitb882b52acd5e2b2f2c8d85b97a5d3e2e6e5069e0 (patch)
treeee8785ccc7906a013f60762085473a161819acc5
parent1cfb8e547f6f44b1809b0a1dcde7beae482d822a (diff)
parentc80297acbb55237adc128b3b2e0b74f0cf34764e (diff)
Merge remote-tracking branch 'drivers-x86/linux-next'
Conflicts: drivers/hwmon/lm85.c
-rw-r--r--Documentation/ABI/testing/sysfs-platform-eeepc-wmi21
-rw-r--r--Documentation/laptops/sony-laptop.txt37
-rw-r--r--drivers/platform/x86/Kconfig37
-rw-r--r--drivers/platform/x86/Makefile6
-rw-r--r--drivers/platform/x86/asus-laptop.c180
-rw-r--r--drivers/platform/x86/dell-wmi-aio.c171
-rw-r--r--drivers/platform/x86/eeepc-wmi.c780
-rw-r--r--drivers/platform/x86/hp-wmi.c312
-rw-r--r--drivers/platform/x86/intel_mid_powerbtn.c148
-rw-r--r--drivers/platform/x86/intel_mid_thermal.c575
-rw-r--r--drivers/platform/x86/sony-laptop.c492
-rw-r--r--drivers/platform/x86/xo15-ebook.c180
-rw-r--r--include/linux/input.h7
-rw-r--r--include/linux/sonypi.h1
14 files changed, 2508 insertions, 439 deletions
diff --git a/Documentation/ABI/testing/sysfs-platform-eeepc-wmi b/Documentation/ABI/testing/sysfs-platform-eeepc-wmi
index e4b5fef5fadd..26acb6842c44 100644
--- a/Documentation/ABI/testing/sysfs-platform-eeepc-wmi
+++ b/Documentation/ABI/testing/sysfs-platform-eeepc-wmi
@@ -8,3 +8,24 @@ Description:
* 0 -> Super Performance Mode
* 1 -> High Performance Mode
* 2 -> Power Saving Mode
+
+What: /sys/devices/platform/eeepc-wmi/camera
+Date: Jan 2010
+KernelVersion: 2.6.39
+Contact: "Corentin Chary" <corentincj@iksaif.net>
+Description:
+ Control the camera. 1 means on, 0 means off.
+
+What: /sys/devices/platform/eeepc-wmi/cardr
+Date: Jan 2010
+KernelVersion: 2.6.39
+Contact: "Corentin Chary" <corentincj@iksaif.net>
+Description:
+ Control the card reader. 1 means on, 0 means off.
+
+What: /sys/devices/platform/eeepc-wmi/touchpad
+Date: Jan 2010
+KernelVersion: 2.6.39
+Contact: "Corentin Chary" <corentincj@iksaif.net>
+Description:
+ Control the card touchpad. 1 means on, 0 means off.
diff --git a/Documentation/laptops/sony-laptop.txt b/Documentation/laptops/sony-laptop.txt
index 23ce7d350d1a..2bd4e82e5d9f 100644
--- a/Documentation/laptops/sony-laptop.txt
+++ b/Documentation/laptops/sony-laptop.txt
@@ -14,7 +14,8 @@ Some models report hotkeys through the SNC or SPIC devices, such events are
reported both through the ACPI subsystem as acpi events and through the INPUT
subsystem. See the logs of acpid or /proc/acpi/event and
/proc/bus/input/devices to find out what those events are and which input
-devices are created by the driver.
+devices are created by the driver. Additionally, loading the driver with the
+debug option will report all events in the kernel log.
Backlight control:
------------------
@@ -64,6 +65,16 @@ powers off the sound card,
# echo "1" > /sys/devices/platform/sony-laptop/audiopower
powers on the sound card.
+
+RFkill control:
+---------------
+More recent Vaio models expose a consistent set of ACPI methods to
+control radio frequency emitting devices. If you are a lucky owner of
+such a laptop you will find the necessary rfkill devices under
+/sys/class/rfkill. Check those starting with sony-* in
+ # grep . /sys/class/rfkill/*/{state,name}
+
+
Development:
------------
@@ -75,8 +86,21 @@ pass the option 'debug=1'.
REPEAT: DON'T DO THIS IF YOU DON'T LIKE RISKY BUSINESS.
In your kernel logs you will find the list of all ACPI methods
-the SNC device has on your laptop. You can see the GCDP/GCDP methods
-used to pwer on/off the CD drive, but there are others.
+the SNC device has on your laptop.
+
+* For new models you will see a long list of meaningless method names,
+reading the DSDT table source should reveal that:
+(1) the SNC device uses an internal capability lookup table
+(2) SN00 is used to find values in the lookup table
+(3) SN06 and SN07 are used to call into the real methods based on
+ offsets you can obtain iterating the table using SN00
+(4) SN02 used to enable events.
+Some values in the capability lookup table are more or less known, see
+the code for all sony_call_snc_handle calls, others are more obscure.
+
+* For old models you can see the GCDP/GCDP methods used to pwer on/off
+the CD drive, but there are others and they are usually different from
+model to model.
I HAVE NO IDEA WHAT THOSE METHODS DO.
@@ -108,9 +132,8 @@ Bugs/Limitations:
laptop, including permanent damage.
* The sony-laptop and sonypi drivers do not interact at all. In the
- future, sonypi could use sony-laptop to do (part of) its business.
+ future, sonypi will be removed and replaced by sony-laptop.
* spicctrl, which is the userspace tool used to communicate with the
- sonypi driver (through /dev/sonypi) does not try to use the
- sony-laptop driver. In the future, spicctrl could try sonypi first,
- and if it isn't present, try sony-laptop instead.
+ sonypi driver (through /dev/sonypi) is deprecated as well since all
+ its features are now available under the sysfs tree via sony-laptop.
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 222dfb737b11..b039d6a93f17 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -101,6 +101,19 @@ 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.
+
+
config FUJITSU_LAPTOP
tristate "Fujitsu Laptop Extras"
depends on ACPI
@@ -616,6 +629,21 @@ config GPIO_INTEL_PMIC
Say Y here to support GPIO via the SCU IPC interface
on Intel MID platforms.
+config INTEL_MID_POWER_BUTTON
+ tristate "power button driver for Intel MID platforms"
+ depends on INTEL_SCU_IPC && INPUT
+ help
+ This driver handles the power button on the Intel MID platforms.
+
+ If unsure, say N.
+
+config INTEL_MFLD_THERMAL
+ tristate "Thermal driver for Intel Medfield platform"
+ depends on INTEL_SCU_IPC && THERMAL
+ help
+ Say Y here to enable thermal driver support for the Intel Medfield
+ platform.
+
config RAR_REGISTER
bool "Restricted Access Region Register Driver"
depends on PCI && X86_MRST
@@ -672,4 +700,13 @@ config XO1_RFKILL
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 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.
+
endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 299aefb3e74c..a9877711b3f1 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -10,6 +10,7 @@ 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_ACER_WMI) += acer-wmi.o
obj-$(CONFIG_ACERHDF) += acerhdf.o
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
@@ -29,9 +30,12 @@ obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
-obj-$(CONFIG_INTEL_SCU_IPC_UTIL)+= intel_scu_ipcutil.o
+obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
+obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
obj-$(CONFIG_RAR_REGISTER) += intel_rar_register.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_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
index f3aa6a7fdab6..92e2a0b16328 100644
--- a/drivers/platform/x86/asus-laptop.c
+++ b/drivers/platform/x86/asus-laptop.c
@@ -50,6 +50,7 @@
#include <linux/input/sparse-keymap.h>
#include <linux/rfkill.h>
#include <linux/slab.h>
+#include <linux/dmi.h>
#include <acpi/acpi_drivers.h>
#include <acpi/acpi_bus.h>
@@ -157,46 +158,9 @@ MODULE_PARM_DESC(wwan_status, "Set the wireless status on boot "
#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 */
@@ -246,7 +210,6 @@ struct asus_laptop {
int wireless_status;
bool have_rsts;
- int lcd_state;
struct rfkill *gps_rfkill;
@@ -559,48 +522,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);
@@ -628,16 +549,9 @@ 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 const struct backlight_ops asusbl_ops = {
@@ -661,8 +575,7 @@ 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));
@@ -970,41 +883,6 @@ static void asus_set_display(struct asus_laptop *asus, int value)
return;
}
-static int read_display(struct asus_laptop *asus)
-{
- unsigned long long value = 0;
- acpi_status rv = AE_OK;
-
- /*
- * 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");
- }
-
- value &= 0x0F; /* needed for some models, shouldn't hurt others */
-
- return value;
-}
-
-/*
- * 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()
- */
-static ssize_t show_disp(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));
-}
-
/*
* Experimental support for display switching. As of now: 1 should activate
* the LCD output, 2 should do for CRT, 4 for TV-Out and 8 for DVI.
@@ -1246,15 +1124,6 @@ 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);
@@ -1281,7 +1150,7 @@ static DEVICE_ATTR(bluetooth, S_IRUGO | S_IWUSR,
show_bluetooth, store_bluetooth);
static DEVICE_ATTR(wimax, S_IRUGO | S_IWUSR, show_wimax, store_wimax);
static DEVICE_ATTR(wwan, S_IRUGO | S_IWUSR, show_wwan, store_wwan);
-static DEVICE_ATTR(display, S_IRUGO | S_IWUSR, show_disp, store_disp);
+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_level, S_IRUGO | S_IWUSR, show_lslvl, store_lslvl);
static DEVICE_ATTR(ls_switch, S_IRUGO | S_IWUSR, show_lssw, store_lssw);
@@ -1392,26 +1261,6 @@ static struct platform_driver platform_driver = {
}
};
-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
@@ -1497,10 +1346,6 @@ static int asus_laptop_get_info(struct asus_laptop *asus)
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;
@@ -1552,10 +1397,23 @@ static int __devinit asus_acpi_init(struct asus_laptop *asus)
asus_als_level(asus, asus->light_level);
}
- asus->lcd_state = 1; /* LCD should be on when the module load */
return result;
}
+static void __devinit 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)
@@ -1574,6 +1432,8 @@ 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;
diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c
new file mode 100644
index 000000000000..0ed84573ae1f
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-aio.c
@@ -0,0 +1,171 @@
+/*
+ * 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 <acpi/acpi_drivers.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"
+
+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_END, 0 }
+};
+
+static struct input_dev *dell_wmi_aio_input_dev;
+
+static void dell_wmi_aio_notify(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_info("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+ if (obj) {
+ unsigned int scancode;
+
+ 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:
+ /* Broken machines return the scancode in a buffer */
+ if (obj->buffer.pointer && obj->buffer.length > 0) {
+ scancode = obj->buffer.pointer[0];
+ 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_warning("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/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c
index 4d38f98aa976..d3997757e790 100644
--- a/drivers/platform/x86/eeepc-wmi.c
+++ b/drivers/platform/x86/eeepc-wmi.c
@@ -37,9 +37,12 @@
#include <linux/backlight.h>
#include <linux/leds.h>
#include <linux/rfkill.h>
+#include <linux/pci.h>
+#include <linux/pci_hotplug.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/platform_device.h>
+#include <linux/dmi.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
@@ -57,35 +60,72 @@ MODULE_LICENSE("GPL");
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
+#define NOTIFY_BRNUP_MIN 0x11
+#define NOTIFY_BRNUP_MAX 0x1f
+#define NOTIFY_BRNDOWN_MIN 0x20
+#define NOTIFY_BRNDOWN_MAX 0x2e
-#define EEEPC_WMI_METHODID_DEVS 0x53564544
-#define EEEPC_WMI_METHODID_DSTS 0x53544344
-#define EEEPC_WMI_METHODID_CFVS 0x53564643
+/* WMI Methods */
+#define EEEPC_WMI_METHODID_DSTS 0x53544344
+#define EEEPC_WMI_METHODID_DEVS 0x53564544
+#define EEEPC_WMI_METHODID_CFVS 0x53564643
-#define EEEPC_WMI_DEVID_BACKLIGHT 0x00050012
-#define EEEPC_WMI_DEVID_TPDLED 0x00100011
+/* Wireless */
#define EEEPC_WMI_DEVID_WLAN 0x00010011
#define EEEPC_WMI_DEVID_BLUETOOTH 0x00010013
+#define EEEPC_WMI_DEVID_WIMAX 0x00010017
#define EEEPC_WMI_DEVID_WWAN3G 0x00010019
+/* Backlight and Brightness */
+#define EEEPC_WMI_DEVID_BACKLIGHT 0x00050011
+#define EEEPC_WMI_DEVID_BRIGHTNESS 0x00050012
+
+/* Misc */
+#define EEEPC_WMI_DEVID_CAMERA 0x00060013
+
+/* Storage */
+#define EEEPC_WMI_DEVID_CARDREADER 0x00080013
+
+/* Input */
+#define EEEPC_WMI_DEVID_TOUCHPAD 0x00100011
+#define EEEPC_WMI_DEVID_TOUCHPAD_LED 0x00100012
+
+/* DSTS masks */
+#define EEEPC_WMI_DSTS_STATUS_BIT 0x00000001
+#define EEEPC_WMI_DSTS_PRESENCE_BIT 0x00010000
+#define EEEPC_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF
+#define EEEPC_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00
+
+static bool hotplug_wireless;
+
+module_param(hotplug_wireless, bool, 0444);
+MODULE_PARM_DESC(hotplug_wireless,
+ "Enable hotplug for wireless device. "
+ "If your laptop needs that, please report to "
+ "acpi4asus-user@lists.sourceforge.net.");
+
static const struct key_entry eeepc_wmi_keymap[] = {
/* 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, 0xcc, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0x30, { KEY_VOLUMEUP } },
+ { KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 0x32, { KEY_MUTE } },
+ { KE_KEY, 0x5c, { KEY_F15 } }, /* Power Gear key */
+ { KE_KEY, 0x5d, { KEY_WLAN } },
{ KE_KEY, 0x6b, { KEY_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, 0x82, { KEY_CAMERA } },
+ { KE_KEY, 0x83, { KEY_CAMERA_ZOOMIN } },
+ { KE_KEY, 0x88, { KEY_WLAN } },
+ { KE_KEY, 0xcc, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0xe0, { KEY_PROG1 } }, /* Task Manager */
+ { KE_KEY, 0xe1, { KEY_F14 } }, /* Change Resolution */
+ { KE_KEY, 0xe9, { KEY_BRIGHTNESS_ZERO } },
+ { 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_END, 0},
};
@@ -108,6 +148,8 @@ struct eeepc_wmi_debug {
};
struct eeepc_wmi {
+ bool hotplug_wireless;
+
struct input_dev *inputdev;
struct backlight_device *backlight_device;
struct platform_device *platform_device;
@@ -119,14 +161,18 @@ struct eeepc_wmi {
struct rfkill *wlan_rfkill;
struct rfkill *bluetooth_rfkill;
+ struct rfkill *wimax_rfkill;
struct rfkill *wwan3g_rfkill;
+ struct hotplug_slot *hotplug_slot;
+ struct mutex hotplug_lock;
+ struct mutex wmi_lock;
+ struct workqueue_struct *hotplug_workqueue;
+ struct work_struct hotplug_work;
+
struct eeepc_wmi_debug debug;
};
-/* Only used in eeepc_wmi_init() and eeepc_wmi_exit() */
-static struct platform_device *platform_device;
-
static int eeepc_wmi_input_init(struct eeepc_wmi *eeepc)
{
int err;
@@ -176,7 +222,8 @@ static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *retval)
u32 tmp;
status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID,
- 1, EEEPC_WMI_METHODID_DSTS, &input, &output);
+ 1, EEEPC_WMI_METHODID_DSTS,
+ &input, &output);
if (ACPI_FAILURE(status))
return status;
@@ -236,6 +283,28 @@ static acpi_status eeepc_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
return status;
}
+/* Helper for special devices with magic return codes */
+static int eeepc_wmi_get_devstate_bits(u32 dev_id, u32 mask)
+{
+ u32 retval = 0;
+ acpi_status status;
+
+ status = eeepc_wmi_get_devstate(dev_id, &retval);
+
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ if (!(retval & EEEPC_WMI_DSTS_PRESENCE_BIT))
+ return -ENODEV;
+
+ return retval & mask;
+}
+
+static int eeepc_wmi_get_devstate_simple(u32 dev_id)
+{
+ return eeepc_wmi_get_devstate_bits(dev_id, EEEPC_WMI_DSTS_STATUS_BIT);
+}
+
/*
* LEDs
*/
@@ -253,7 +322,7 @@ static void tpd_led_update(struct work_struct *work)
eeepc = container_of(work, struct eeepc_wmi, tpd_led_work);
ctrl_param = eeepc->tpd_led_wk;
- eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_TPDLED, ctrl_param, NULL);
+ eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_TOUCHPAD_LED, ctrl_param, NULL);
}
static void tpd_led_set(struct led_classdev *led_cdev,
@@ -267,25 +336,9 @@ static void tpd_led_set(struct led_classdev *led_cdev,
queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work);
}
-static int read_tpd_state(struct eeepc_wmi *eeepc)
+static int read_tpd_led_state(struct eeepc_wmi *eeepc)
{
- u32 retval;
- acpi_status status;
-
- status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_TPDLED, &retval);
-
- if (ACPI_FAILURE(status))
- return -1;
- else if (!retval || retval == 0x00060000)
- /*
- * if touchpad led is present, DSTS will set some bits,
- * usually 0x00020000.
- * 0x00060000 means that the device is not supported
- */
- return -ENODEV;
- else
- /* Status is stored in the first bit */
- return retval & 0x1;
+ return eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_TOUCHPAD_LED);
}
static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
@@ -294,14 +347,14 @@ static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
eeepc = container_of(led_cdev, struct eeepc_wmi, tpd_led);
- return read_tpd_state(eeepc);
+ return read_tpd_led_state(eeepc);
}
static int eeepc_wmi_led_init(struct eeepc_wmi *eeepc)
{
int rv;
- if (read_tpd_state(eeepc) < 0)
+ if (read_tpd_led_state(eeepc) < 0)
return 0;
eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue");
@@ -333,30 +386,280 @@ static void eeepc_wmi_led_exit(struct eeepc_wmi *eeepc)
}
/*
+ * PCI hotplug (for wlan rfkill)
+ */
+static bool eeepc_wlan_rfkill_blocked(struct eeepc_wmi *eeepc)
+{
+ int result = eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_WLAN);
+
+ if (result < 0)
+ return false;
+ return !result;
+}
+
+static void eeepc_rfkill_hotplug(struct eeepc_wmi *eeepc)
+{
+ struct pci_dev *dev;
+ struct pci_bus *bus;
+ bool blocked;
+ bool absent;
+ u32 l;
+
+ mutex_lock(&eeepc->wmi_lock);
+ blocked = eeepc_wlan_rfkill_blocked(eeepc);
+ mutex_unlock(&eeepc->wmi_lock);
+
+ mutex_lock(&eeepc->hotplug_lock);
+
+ if (eeepc->wlan_rfkill)
+ rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
+
+ if (eeepc->hotplug_slot) {
+ bus = pci_find_bus(0, 1);
+ if (!bus) {
+ pr_warning("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_warning("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;
+ }
+
+ 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);
+ if (pci_bus_add_device(dev))
+ pr_err("Unable to hotplug wifi\n");
+ }
+ } else {
+ dev = pci_get_slot(bus, 0);
+ if (dev) {
+ pci_remove_bus_device(dev);
+ pci_dev_put(dev);
+ }
+ }
+ }
+
+out_unlock:
+ mutex_unlock(&eeepc->hotplug_lock);
+}
+
+static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct eeepc_wmi *eeepc = data;
+
+ if (event != ACPI_NOTIFY_BUS_CHECK)
+ return;
+
+ /*
+ * We can't call directly eeepc_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 eeepc_rfkill_hotplug
+ * call later, in a safer context.
+ */
+ queue_work(eeepc->hotplug_workqueue, &eeepc->hotplug_work);
+}
+
+static int eeepc_register_rfkill_notifier(struct eeepc_wmi *eeepc,
+ 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,
+ eeepc_rfkill_notify,
+ eeepc);
+ if (ACPI_FAILURE(status))
+ pr_warning("Failed to register notify on %s\n", node);
+ } else
+ return -ENODEV;
+
+ return 0;
+}
+
+static void eeepc_unregister_rfkill_notifier(struct eeepc_wmi *eeepc,
+ 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,
+ eeepc_rfkill_notify);
+ if (ACPI_FAILURE(status))
+ pr_err("Error removing rfkill notify handler %s\n",
+ node);
+ }
+}
+
+static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
+ u8 *value)
+{
+ int result = eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_WLAN);
+
+ if (result < 0)
+ return result;
+
+ *value = !!result;
+ return 0;
+}
+
+static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
+{
+ kfree(hotplug_slot->info);
+ kfree(hotplug_slot);
+}
+
+static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
+ .owner = THIS_MODULE,
+ .get_adapter_status = eeepc_get_adapter_status,
+ .get_power_status = eeepc_get_adapter_status,
+};
+
+static void eeepc_hotplug_work(struct work_struct *work)
+{
+ struct eeepc_wmi *eeepc;
+
+ eeepc = container_of(work, struct eeepc_wmi, hotplug_work);
+ eeepc_rfkill_hotplug(eeepc);
+}
+
+static int eeepc_setup_pci_hotplug(struct eeepc_wmi *eeepc)
+{
+ 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;
+ }
+
+ eeepc->hotplug_workqueue =
+ create_singlethread_workqueue("hotplug_workqueue");
+ if (!eeepc->hotplug_workqueue)
+ goto error_workqueue;
+
+ INIT_WORK(&eeepc->hotplug_work, eeepc_hotplug_work);
+
+ eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
+ if (!eeepc->hotplug_slot)
+ goto error_slot;
+
+ eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
+ GFP_KERNEL);
+ if (!eeepc->hotplug_slot->info)
+ goto error_info;
+
+ eeepc->hotplug_slot->private = eeepc;
+ eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
+ eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
+ eeepc_get_adapter_status(eeepc->hotplug_slot,
+ &eeepc->hotplug_slot->info->adapter_status);
+
+ ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi");
+ if (ret) {
+ pr_err("Unable to register hotplug slot - %d\n", ret);
+ goto error_register;
+ }
+
+ return 0;
+
+error_register:
+ kfree(eeepc->hotplug_slot->info);
+error_info:
+ kfree(eeepc->hotplug_slot);
+ eeepc->hotplug_slot = NULL;
+error_slot:
+ destroy_workqueue(eeepc->hotplug_workqueue);
+error_workqueue:
+ return ret;
+}
+
+/*
* Rfkill devices
*/
static int eeepc_rfkill_set(void *data, bool blocked)
{
int dev_id = (unsigned long)data;
u32 ctrl_param = !blocked;
+ acpi_status status;
+
+ status = eeepc_wmi_set_devstate(dev_id, ctrl_param, NULL);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
- return eeepc_wmi_set_devstate(dev_id, ctrl_param, NULL);
+ return 0;
}
static void eeepc_rfkill_query(struct rfkill *rfkill, void *data)
{
int dev_id = (unsigned long)data;
- u32 retval;
- acpi_status status;
+ int result;
- status = eeepc_wmi_get_devstate(dev_id, &retval);
+ result = eeepc_wmi_get_devstate_simple(dev_id);
- if (ACPI_FAILURE(status))
+ if (result < 0)
return ;
- rfkill_set_sw_state(rfkill, !(retval & 0x1));
+ rfkill_set_sw_state(rfkill, !result);
+}
+
+static int eeepc_rfkill_wlan_set(void *data, bool blocked)
+{
+ struct eeepc_wmi *eeepc = data;
+ int ret;
+
+ /*
+ * This handler is enabled only if hotplug is enabled.
+ * In this case, the eeepc_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(&eeepc->wmi_lock);
+ ret = eeepc_rfkill_set((void *)(long)EEEPC_WMI_DEVID_WLAN, blocked);
+ mutex_unlock(&eeepc->wmi_lock);
+ return ret;
}
+static void eeepc_rfkill_wlan_query(struct rfkill *rfkill, void *data)
+{
+ eeepc_rfkill_query(rfkill, (void *)(long)EEEPC_WMI_DEVID_WLAN);
+}
+
+static const struct rfkill_ops eeepc_rfkill_wlan_ops = {
+ .set_block = eeepc_rfkill_wlan_set,
+ .query = eeepc_rfkill_wlan_query,
+};
+
static const struct rfkill_ops eeepc_rfkill_ops = {
.set_block = eeepc_rfkill_set,
.query = eeepc_rfkill_query,
@@ -367,31 +670,22 @@ static int eeepc_new_rfkill(struct eeepc_wmi *eeepc,
const char *name,
enum rfkill_type type, int dev_id)
{
- int result;
- u32 retval;
- acpi_status status;
-
- status = eeepc_wmi_get_devstate(dev_id, &retval);
+ int result = eeepc_wmi_get_devstate_simple(dev_id);
- if (ACPI_FAILURE(status))
- return -1;
-
- /* If the device is present, DSTS will always set some bits
- * 0x00070000 - 1110000000000000000 - device supported
- * 0x00060000 - 1100000000000000000 - not supported
- * 0x00020000 - 0100000000000000000 - device supported
- * 0x00010000 - 0010000000000000000 - not supported / special mode ?
- */
- if (!retval || retval == 0x00060000)
- return -ENODEV;
+ if (result < 0)
+ return result;
- *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
- &eeepc_rfkill_ops, (void *)(long)dev_id);
+ if (dev_id == EEEPC_WMI_DEVID_WLAN && eeepc->hotplug_wireless)
+ *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
+ &eeepc_rfkill_wlan_ops, eeepc);
+ else
+ *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
+ &eeepc_rfkill_ops, (void *)(long)dev_id);
if (!*rfkill)
return -EINVAL;
- rfkill_init_sw_state(*rfkill, !(retval & 0x1));
+ rfkill_init_sw_state(*rfkill, !result);
result = rfkill_register(*rfkill);
if (result) {
rfkill_destroy(*rfkill);
@@ -403,16 +697,34 @@ static int eeepc_new_rfkill(struct eeepc_wmi *eeepc,
static void eeepc_wmi_rfkill_exit(struct eeepc_wmi *eeepc)
{
+ eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
+ eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
+ eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
if (eeepc->wlan_rfkill) {
rfkill_unregister(eeepc->wlan_rfkill);
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);
+ if (eeepc->hotplug_workqueue)
+ destroy_workqueue(eeepc->hotplug_workqueue);
+
if (eeepc->bluetooth_rfkill) {
rfkill_unregister(eeepc->bluetooth_rfkill);
rfkill_destroy(eeepc->bluetooth_rfkill);
eeepc->bluetooth_rfkill = NULL;
}
+ if (eeepc->wimax_rfkill) {
+ rfkill_unregister(eeepc->wimax_rfkill);
+ rfkill_destroy(eeepc->wimax_rfkill);
+ eeepc->wimax_rfkill = NULL;
+ }
if (eeepc->wwan3g_rfkill) {
rfkill_unregister(eeepc->wwan3g_rfkill);
rfkill_destroy(eeepc->wwan3g_rfkill);
@@ -424,6 +736,9 @@ static int eeepc_wmi_rfkill_init(struct eeepc_wmi *eeepc)
{
int result = 0;
+ mutex_init(&eeepc->hotplug_lock);
+ mutex_init(&eeepc->wmi_lock);
+
result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
"eeepc-wlan", RFKILL_TYPE_WLAN,
EEEPC_WMI_DEVID_WLAN);
@@ -438,6 +753,13 @@ static int eeepc_wmi_rfkill_init(struct eeepc_wmi *eeepc)
if (result && result != -ENODEV)
goto exit;
+ result = eeepc_new_rfkill(eeepc, &eeepc->wimax_rfkill,
+ "eeepc-wimax", RFKILL_TYPE_WIMAX,
+ EEEPC_WMI_DEVID_WIMAX);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill,
"eeepc-wwan3g", RFKILL_TYPE_WWAN,
EEEPC_WMI_DEVID_WWAN3G);
@@ -445,6 +767,26 @@ static int eeepc_wmi_rfkill_init(struct eeepc_wmi *eeepc)
if (result && result != -ENODEV)
goto exit;
+ if (!eeepc->hotplug_wireless)
+ goto exit;
+
+ result = eeepc_setup_pci_hotplug(eeepc);
+ /*
+ * If we get -EBUSY then something else is handling the PCI hotplug -
+ * don't fail in this case
+ */
+ if (result == -EBUSY)
+ result = 0;
+
+ 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)
eeepc_wmi_rfkill_exit(eeepc);
@@ -458,34 +800,53 @@ exit:
/*
* Backlight
*/
+static int read_backlight_power(void)
+{
+ int ret = eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_BACKLIGHT);
+
+ if (ret < 0)
+ return ret;
+
+ return ret ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+}
+
static int read_brightness(struct backlight_device *bd)
{
u32 retval;
acpi_status status;
- status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_BACKLIGHT, &retval);
+ status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_BRIGHTNESS, &retval);
if (ACPI_FAILURE(status))
- return -1;
+ return -EIO;
else
- return retval & 0xFF;
+ return retval & EEEPC_WMI_DSTS_BRIGHTNESS_MASK;
}
static int update_bl_status(struct backlight_device *bd)
{
-
u32 ctrl_param;
acpi_status status;
+ int power;
ctrl_param = bd->props.brightness;
- status = eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_BACKLIGHT,
+ status = eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_BRIGHTNESS,
ctrl_param, NULL);
if (ACPI_FAILURE(status))
- return -1;
- else
- return 0;
+ return -EIO;
+
+ power = read_backlight_power();
+ if (power != -ENODEV && bd->props.power != power) {
+ ctrl_param = !!(bd->props.power == FB_BLANK_UNBLANK);
+ status = eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_BACKLIGHT,
+ ctrl_param, NULL);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+ }
+ return 0;
}
static const struct backlight_ops eeepc_wmi_bl_ops = {
@@ -515,9 +876,29 @@ static int eeepc_wmi_backlight_init(struct eeepc_wmi *eeepc)
{
struct backlight_device *bd;
struct backlight_properties props;
+ int max;
+ int power;
+
+ max = eeepc_wmi_get_devstate_bits(EEEPC_WMI_DEVID_BRIGHTNESS,
+ EEEPC_WMI_DSTS_MAX_BRIGTH_MASK);
+ power = read_backlight_power();
+
+ if (max < 0 && power < 0) {
+ /* Try to keep the original error */
+ if (max == -ENODEV && power == -ENODEV)
+ return -ENODEV;
+ if (max != -ENODEV)
+ return max;
+ else
+ return power;
+ }
+ if (max == -ENODEV)
+ max = 0;
+ if (power == -ENODEV)
+ power = FB_BLANK_UNBLANK;
memset(&props, 0, sizeof(struct backlight_properties));
- props.max_brightness = 15;
+ props.max_brightness = max;
bd = backlight_device_register(EEEPC_WMI_FILE,
&eeepc->platform_device->dev, eeepc,
&eeepc_wmi_bl_ops, &props);
@@ -529,7 +910,7 @@ static int eeepc_wmi_backlight_init(struct eeepc_wmi *eeepc)
eeepc->backlight_device = bd;
bd->props.brightness = read_brightness(bd);
- bd->props.power = FB_BLANK_UNBLANK;
+ bd->props.power = power;
backlight_update_status(bd);
return 0;
@@ -583,6 +964,71 @@ static void eeepc_wmi_notify(u32 value, void *context)
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(int devid, const char *buf, size_t count)
+{
+ acpi_status status;
+ u32 retval;
+ int rv, value;
+
+ value = eeepc_wmi_get_devstate_simple(devid);
+ if (value == -ENODEV) /* Check device presence */
+ return value;
+
+ rv = parse_arg(buf, count, &value);
+ status = eeepc_wmi_set_devstate(devid, value, &retval);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+ return rv;
+}
+
+static ssize_t show_sys_wmi(int devid, char *buf)
+{
+ int value = eeepc_wmi_get_devstate_simple(devid);
+
+ if (value < 0)
+ return value;
+
+ return sprintf(buf, "%d\n", value);
+}
+
+#define EEEPC_WMI_CREATE_DEVICE_ATTR(_name, _mode, _cm) \
+ static ssize_t show_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ return show_sys_wmi(_cm, buf); \
+ } \
+ static ssize_t store_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ return store_sys_wmi(_cm, buf, count); \
+ } \
+ static struct device_attribute dev_attr_##_name = { \
+ .attr = { \
+ .name = __stringify(_name), \
+ .mode = _mode }, \
+ .show = show_##_name, \
+ .store = store_##_name, \
+ }
+
+EEEPC_WMI_CREATE_DEVICE_ATTR(touchpad, 0644, EEEPC_WMI_DEVID_TOUCHPAD);
+EEEPC_WMI_CREATE_DEVICE_ATTR(camera, 0644, EEEPC_WMI_DEVID_CAMERA);
+EEEPC_WMI_CREATE_DEVICE_ATTR(cardr, 0644, EEEPC_WMI_DEVID_CARDREADER);
+
static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@@ -608,11 +1054,35 @@ 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,
NULL
};
+static mode_t eeepc_sysfs_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ bool supported = true;
+ int devid = -1;
+
+ if (attr == &dev_attr_camera.attr)
+ devid = EEEPC_WMI_DEVID_CAMERA;
+ else if (attr == &dev_attr_cardr.attr)
+ devid = EEEPC_WMI_DEVID_CARDREADER;
+ else if (attr == &dev_attr_touchpad.attr)
+ devid = EEEPC_WMI_DEVID_TOUCHPAD;
+
+ if (devid != -1)
+ supported = eeepc_wmi_get_devstate_simple(devid) != -ENODEV;
+
+ return supported ? attr->mode : 0;
+}
+
static struct attribute_group platform_attribute_group = {
- .attrs = platform_attributes
+ .is_visible = eeepc_sysfs_is_visible,
+ .attrs = platform_attributes
};
static void eeepc_wmi_sysfs_exit(struct platform_device *device)
@@ -630,33 +1100,12 @@ static int eeepc_wmi_sysfs_init(struct platform_device *device)
*/
static int __init eeepc_wmi_platform_init(struct eeepc_wmi *eeepc)
{
- int err;
-
- eeepc->platform_device = platform_device_alloc(EEEPC_WMI_FILE, -1);
- if (!eeepc->platform_device)
- return -ENOMEM;
- platform_set_drvdata(eeepc->platform_device, eeepc);
-
- err = platform_device_add(eeepc->platform_device);
- if (err)
- goto fail_platform_device;
-
- err = eeepc_wmi_sysfs_init(eeepc->platform_device);
- if (err)
- goto fail_sysfs;
- return 0;
-
-fail_sysfs:
- platform_device_del(eeepc->platform_device);
-fail_platform_device:
- platform_device_put(eeepc->platform_device);
- return err;
+ return eeepc_wmi_sysfs_init(eeepc->platform_device);
}
static void eeepc_wmi_platform_exit(struct eeepc_wmi *eeepc)
{
eeepc_wmi_sysfs_exit(eeepc->platform_device);
- platform_device_unregister(eeepc->platform_device);
}
/*
@@ -770,7 +1219,29 @@ error_debugfs:
/*
* WMI Driver
*/
-static struct platform_device * __init eeepc_wmi_add(void)
+static void eeepc_dmi_check(struct eeepc_wmi *eeepc)
+{
+ const char *model;
+
+ model = dmi_get_system_info(DMI_PRODUCT_NAME);
+ if (!model)
+ return;
+
+ /*
+ * Whitelist for wlan hotplug
+ *
+ * Eeepc 1000H needs the current hotplug code to handle
+ * Fn+F2 correctly. We may add other Eeepc here later, but
+ * it seems that most of the laptops supported by eeepc-wmi
+ * don't need to be on this list
+ */
+ if (strcmp(model, "1000H") == 0) {
+ eeepc->hotplug_wireless = true;
+ pr_info("wlan hotplug enabled\n");
+ }
+}
+
+static int __init eeepc_wmi_add(struct platform_device *pdev)
{
struct eeepc_wmi *eeepc;
acpi_status status;
@@ -778,12 +1249,14 @@ static struct platform_device * __init eeepc_wmi_add(void)
eeepc = kzalloc(sizeof(struct eeepc_wmi), GFP_KERNEL);
if (!eeepc)
- return ERR_PTR(-ENOMEM);
+ return -ENOMEM;
+
+ eeepc->platform_device = pdev;
+ platform_set_drvdata(eeepc->platform_device, eeepc);
+
+ eeepc->hotplug_wireless = hotplug_wireless;
+ eeepc_dmi_check(eeepc);
- /*
- * Register the platform device first. It is used as a parent for the
- * sub-devices below.
- */
err = eeepc_wmi_platform_init(eeepc);
if (err)
goto fail_platform;
@@ -802,7 +1275,7 @@ static struct platform_device * __init eeepc_wmi_add(void)
if (!acpi_video_backlight_support()) {
err = eeepc_wmi_backlight_init(eeepc);
- if (err)
+ if (err && err != -ENODEV)
goto fail_backlight;
} else
pr_info("Backlight controlled by ACPI video driver\n");
@@ -820,7 +1293,7 @@ static struct platform_device * __init eeepc_wmi_add(void)
if (err)
goto fail_debugfs;
- return eeepc->platform_device;
+ return 0;
fail_debugfs:
wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID);
@@ -836,10 +1309,10 @@ fail_input:
eeepc_wmi_platform_exit(eeepc);
fail_platform:
kfree(eeepc);
- return ERR_PTR(err);
+ return err;
}
-static int eeepc_wmi_remove(struct platform_device *device)
+static int __exit eeepc_wmi_remove(struct platform_device *device)
{
struct eeepc_wmi *eeepc;
@@ -856,10 +1329,64 @@ static int eeepc_wmi_remove(struct platform_device *device)
return 0;
}
+/*
+ * Platform driver - hibernate/resume callbacks
+ */
+static int eeepc_hotk_thaw(struct device *device)
+{
+ struct eeepc_wmi *eeepc = dev_get_drvdata(device);
+
+ if (eeepc->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 = eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_WLAN);
+ eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_WLAN, wlan, NULL);
+ }
+
+ return 0;
+}
+
+static int eeepc_hotk_restore(struct device *device)
+{
+ struct eeepc_wmi *eeepc = dev_get_drvdata(device);
+ int bl;
+
+ /* Refresh both wlan rfkill state and pci hotplug */
+ if (eeepc->wlan_rfkill)
+ eeepc_rfkill_hotplug(eeepc);
+
+ if (eeepc->bluetooth_rfkill) {
+ bl = !eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_BLUETOOTH);
+ rfkill_set_sw_state(eeepc->bluetooth_rfkill, bl);
+ }
+ if (eeepc->wimax_rfkill) {
+ bl = !eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_WIMAX);
+ rfkill_set_sw_state(eeepc->wimax_rfkill, bl);
+ }
+ if (eeepc->wwan3g_rfkill) {
+ bl = !eeepc_wmi_get_devstate_simple(EEEPC_WMI_DEVID_WWAN3G);
+ rfkill_set_sw_state(eeepc->wwan3g_rfkill, bl);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops eeepc_pm_ops = {
+ .thaw = eeepc_hotk_thaw,
+ .restore = eeepc_hotk_restore,
+};
+
static struct platform_driver platform_driver = {
+ .remove = __exit_p(eeepc_wmi_remove),
.driver = {
.name = EEEPC_WMI_FILE,
.owner = THIS_MODULE,
+ .pm = &eeepc_pm_ops,
},
};
@@ -884,10 +1411,8 @@ static int __init eeepc_wmi_check_atkd(void)
return -1;
}
-static int __init eeepc_wmi_init(void)
+static int __init eeepc_wmi_probe(struct platform_device *pdev)
{
- 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");
@@ -904,29 +1429,24 @@ static int __init eeepc_wmi_init(void)
return -ENODEV;
}
- platform_device = eeepc_wmi_add();
- if (IS_ERR(platform_device)) {
- err = PTR_ERR(platform_device);
- goto fail_eeepc_wmi;
- }
+ return eeepc_wmi_add(pdev);
+}
- err = platform_driver_register(&platform_driver);
- if (err) {
- pr_warning("Unable to register platform driver\n");
- goto fail_platform_driver;
- }
+static struct platform_device *platform_device;
+static int __init eeepc_wmi_init(void)
+{
+ platform_device = platform_create_bundle(&platform_driver,
+ eeepc_wmi_probe,
+ NULL, 0, NULL, 0);
+ if (IS_ERR(platform_device))
+ return PTR_ERR(platform_device);
return 0;
-
-fail_platform_driver:
- eeepc_wmi_remove(platform_device);
-fail_eeepc_wmi:
- return err;
}
static void __exit eeepc_wmi_exit(void)
{
- eeepc_wmi_remove(platform_device);
+ platform_device_unregister(platform_device);
platform_driver_unregister(&platform_driver);
}
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
index 9e05af9c41cb..1bc4a7539ba9 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>
@@ -51,6 +52,7 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
#define HPWMI_HARDWARE_QUERY 0x4
#define HPWMI_WIRELESS_QUERY 0x5
#define HPWMI_HOTKEY_QUERY 0xc
+#define HPWMI_WIRELESS2_QUERY 0x1b
#define PREFIX "HP WMI: "
#define UNIMP "Unimplemented "
@@ -86,7 +88,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[] = {
@@ -108,6 +149,15 @@ static struct rfkill *wifi_rfkill;
static struct rfkill *bluetooth_rfkill;
static struct rfkill *wwan_rfkill;
+struct rfkill2_device {
+ u8 id;
+ int num;
+ struct rfkill *rfkill;
+};
+
+static int rfkill2_count;
+static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES];
+
static const struct dev_pm_ops hp_wmi_pm_ops = {
.resume = hp_wmi_resume_handler,
.restore = hp_wmi_resume_handler,
@@ -129,7 +179,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 +191,29 @@ 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 };
- status = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
+ if (WARN_ON(insize > sizeof(args.data)))
+ return -EINVAL;
+ memcpy(&args.data, buffer, insize);
+
+ wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
obj = output.pointer;
@@ -169,10 +224,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;
- memcpy(buffer, &bios_return.value, sizeof(bios_return.value));
+ if (bios_return->return_code) {
+ if (bios_return->return_code != HPWMI_RET_UNKNOWN_CMDTYPE)
+ printk(KERN_WARNING PREFIX "query 0x%x returned "
+ "error 0x%x\n",
+ query, bios_return->return_code);
+ kfree(obj);
+ return bios_return->return_code;
+ }
+
+ if (!outsize) {
+ /* ignore output data */
+ kfree(obj);
+ return 0;
+ }
+ actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
+ memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
+ memset(buffer + actual_outsize, 0, outsize - actual_outsize);
kfree(obj);
return 0;
}
@@ -181,7 +252,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;
@@ -191,7 +262,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;
@@ -201,7 +272,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;
@@ -211,7 +282,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;
@@ -223,7 +294,7 @@ 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;
@@ -237,7 +308,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;
@@ -252,7 +323,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);
@@ -268,7 +340,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);
@@ -279,6 +352,51 @@ 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) {
+ printk(KERN_WARNING PREFIX "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 ssize_t show_display(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -329,7 +447,7 @@ static ssize_t set_als(struct device *dev, struct device_attribute *attr,
{
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;
@@ -402,6 +520,7 @@ 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;
@@ -412,6 +531,11 @@ static void hp_wmi_notify(u32 value, void *context)
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),
@@ -502,32 +626,16 @@ static void cleanup_sysfs(struct platform_device *device)
device_remove_file(&device->dev, &dev_attr_tablet);
}
-static int __devinit hp_wmi_bios_setup(struct platform_device *device)
+static int __devinit 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,
@@ -573,14 +681,131 @@ static int __devinit hp_wmi_bios_setup(struct platform_device *device)
return 0;
register_wwan_err:
rfkill_destroy(wwan_rfkill);
+ wwan_rfkill = NULL;
if (bluetooth_rfkill)
rfkill_unregister(bluetooth_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 __devinit 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) {
+ printk(KERN_WARNING PREFIX "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;
+ default:
+ printk(KERN_WARNING PREFIX "unknown device type 0x%x\n",
+ state.device[i].radio_type);
+ continue;
+ }
+
+ if (!state.device[i].vendor_id) {
+ printk(KERN_WARNING PREFIX "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))
+ printk(KERN_INFO PREFIX "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 __devinit hp_wmi_bios_setup(struct platform_device *device)
+{
+ int err;
+
+ /* clear detected rfkill devices */
+ wifi_rfkill = NULL;
+ bluetooth_rfkill = NULL;
+ wwan_rfkill = NULL;
+ rfkill2_count = 0;
+
+ if (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;
+ return 0;
+
add_sysfs_error:
cleanup_sysfs(device);
return err;
@@ -588,8 +813,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);
@@ -622,6 +853,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),
diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c
new file mode 100644
index 000000000000..213e79ba68d5
--- /dev/null
+++ b/drivers/platform/x86/intel_mid_powerbtn.c
@@ -0,0 +1,148 @@
+/*
+ * 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 <asm/intel_scu_ipc.h>
+
+#define DRIVER_NAME "msic_power_btn"
+
+#define MSIC_IRQ_STAT 0x02
+ #define MSIC_IRQ_PB (1 << 0)
+#define MSIC_PB_CONFIG 0x3e
+#define MSIC_PB_STATUS 0x3f
+ #define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */
+
+struct mfld_pb_priv {
+ struct input_dev *input;
+ unsigned int irq;
+};
+
+static irqreturn_t mfld_pb_isr(int irq, void *dev_id)
+{
+ struct mfld_pb_priv *priv = dev_id;
+ int ret;
+ u8 pbstat;
+
+ ret = intel_scu_ipc_ioread8(MSIC_PB_STATUS, &pbstat);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ input_event(priv->input, EV_KEY, KEY_POWER, !(pbstat & MSIC_PB_LEVEL));
+ input_sync(priv->input);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit mfld_pb_probe(struct platform_device *pdev)
+{
+ struct mfld_pb_priv *priv;
+ struct input_dev *input;
+ int irq;
+ int error;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return -EINVAL;
+
+ priv = kzalloc(sizeof(struct mfld_pb_priv), GFP_KERNEL);
+ input = input_allocate_device();
+ if (!priv || !input) {
+ error = -ENOMEM;
+ goto err_free_mem;
+ }
+
+ priv->input = input;
+ priv->irq = irq;
+
+ 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(priv->irq, NULL, mfld_pb_isr,
+ 0, DRIVER_NAME, priv);
+ if (error) {
+ dev_err(&pdev->dev,
+ "unable to request irq %d for mfld power button\n",
+ irq);
+ goto err_free_mem;
+ }
+
+ 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, priv);
+ return 0;
+
+err_free_irq:
+ free_irq(priv->irq, priv);
+err_free_mem:
+ input_free_device(input);
+ kfree(priv);
+ return error;
+}
+
+static int __devexit mfld_pb_remove(struct platform_device *pdev)
+{
+ struct mfld_pb_priv *priv = platform_get_drvdata(pdev);
+
+ free_irq(priv->irq, priv);
+ input_unregister_device(priv->input);
+ kfree(priv);
+
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver mfld_pb_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = mfld_pb_probe,
+ .remove = __devexit_p(mfld_pb_remove),
+};
+
+static int __init mfld_pb_init(void)
+{
+ return platform_driver_register(&mfld_pb_driver);
+}
+module_init(mfld_pb_init);
+
+static void __exit mfld_pb_exit(void)
+{
+ platform_driver_unregister(&mfld_pb_driver);
+}
+module_exit(mfld_pb_exit);
+
+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 000000000000..1215c3096868
--- /dev/null
+++ b/drivers/platform/x86/intel_mid_thermal.c
@@ -0,0 +1,575 @@
+/*
+ * 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 <asm/intel_scu_ipc.h>
+
+/* Number of thermal sensors */
+#define MSIC_THERMAL_SENSORS 4
+
+/* ADC1 - thermal registers */
+#define MSIC_THERM_ADC1CNTL1 0x1C0
+#define MSIC_ADC_ENBL 0x10
+#define MSIC_ADC_START 0x08
+
+#define MSIC_THERM_ADC1CNTL3 0x1C2
+#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
+#define ADC_CHANLS_MAX 15 /* Number of ADC channels */
+#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 0x1C5 /* increments by 1 */
+#define ADC_DATA_START_ADDR 0x1D4 /* 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_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCRRDATA_ENBL);
+ if (ret)
+ return ret;
+
+ /* Re-toggle the RRDATARD bit (temporary workaround) */
+ ret = intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCTHERM_ENBL);
+ if (ret)
+ return ret;
+
+ /* Read the higher bits of data */
+ ret = intel_scu_ipc_ioread8(addr, &data);
+ if (ret)
+ return ret;
+
+ /* Shift bits to accomodate the lower two data bits */
+ adc_val = (data << 2);
+ addr++;
+
+ ret = intel_scu_ipc_ioread8(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_scu_ipc_ioread8(MSIC_THERM_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_scu_ipc_iowrite8(MSIC_THERM_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_scu_ipc_iowrite8(base_addr, SKIN_SENSOR0_CODE);
+ if (ret)
+ return ret;
+
+ ret = intel_scu_ipc_iowrite8(base_addr + 1, SKIN_SENSOR1_CODE);
+ if (ret)
+ return ret;
+
+ ret = intel_scu_ipc_iowrite8(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_scu_ipc_iowrite8(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_scu_ipc_ioread8(addr, &data);
+ if (ret)
+ return ret;
+ /* Set the stop bit to zero */
+ return intel_scu_ipc_iowrite8(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_scu_ipc_ioread8(MSIC_THERM_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_scu_ipc_ioread8(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_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL3, &data);
+ if (ret)
+ return ret;
+
+ if (data & MSIC_ADCTHERM_MASK)
+ dev_warn(dev, "ADCTHERM already set");
+
+ /* 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
+ * @pdev: platform device structure
+ *
+ * mid thermal resume: re-initializes the adc. Can sleep.
+ */
+static int mid_thermal_resume(struct platform_device *pdev)
+{
+ return mid_initialize_adc(&pdev->dev);
+}
+
+/**
+ * mid_thermal_suspend - suspend routine
+ * @pdev: platform device structure
+ *
+ * mid thermal suspend implements the suspend functionality
+ * by stopping the ADC. Can sleep.
+ */
+static int mid_thermal_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ /*
+ * 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);
+}
+
+/**
+ * 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 = kzalloc(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");
+ kfree(pinfo);
+ return ret;
+ }
+
+ /* Register each sensor with the generic thermal framework*/
+ for (i = 0; i < MSIC_THERMAL_SENSORS; i++) {
+ pinfo->tzd[i] = thermal_zone_device_register(name[i],
+ 0, initialize_sensor(i),
+ &tzd_ops, 0, 0, 0, 0);
+ if (IS_ERR(pinfo->tzd[i]))
+ goto reg_fail;
+ }
+
+ pinfo->pdev = pdev;
+ platform_set_drvdata(pdev, pinfo);
+ return 0;
+
+reg_fail:
+ ret = PTR_ERR(pinfo->tzd[i]);
+ while (--i >= 0)
+ thermal_zone_device_unregister(pinfo->tzd[i]);
+ configure_adc(0);
+ kfree(pinfo);
+ 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++)
+ thermal_zone_device_unregister(pinfo->tzd[i]);
+
+ platform_set_drvdata(pdev, NULL);
+
+ /* Stop the ADC */
+ return configure_adc(0);
+}
+
+/*********************************************************************
+ * Driver initialisation and finalization
+ *********************************************************************/
+
+#define DRIVER_NAME "msic_sensor"
+
+static const struct platform_device_id therm_id_table[] = {
+ { DRIVER_NAME, 1 },
+};
+
+static struct platform_driver mid_thermal_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = mid_thermal_probe,
+ .suspend = mid_thermal_suspend,
+ .resume = mid_thermal_resume,
+ .remove = __devexit_p(mid_thermal_remove),
+ .id_table = therm_id_table,
+};
+
+static int __init mid_thermal_module_init(void)
+{
+ return platform_driver_register(&mid_thermal_driver);
+}
+
+static void __exit mid_thermal_module_exit(void)
+{
+ platform_driver_unregister(&mid_thermal_driver);
+}
+
+module_init(mid_thermal_module_init);
+module_exit(mid_thermal_module_exit);
+
+MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
index 5e83370b0812..a0ba0e795b67 100644
--- a/drivers/platform/x86/sony-laptop.c
+++ b/drivers/platform/x86/sony-laptop.c
@@ -71,8 +71,9 @@
#endif
#define DRV_PFX "sony-laptop: "
-#define dprintk(msg...) do { \
- if (debug) printk(KERN_WARNING DRV_PFX msg); \
+#define dprintk(msg...) do { \
+ if (debug) \
+ pr_warn(DRV_PFX msg); \
} while (0)
#define SONY_LAPTOP_DRIVER_VERSION "0.6"
@@ -124,6 +125,19 @@ 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 (default: 0)");
+
+static int kbd_backlight_timeout; /* = 0 */
+module_param(kbd_backlight_timeout, int, 0444);
+MODULE_PARM_DESC(kbd_backlight_timeout,
+ "set this to 0 to set the default 10 seconds timeout, "
+ "1 for 30 seconds, 2 for 60 seconds and 3 to disable timeout "
+ "(default: 0)");
+
enum sony_nc_rfkill {
SONY_WIFI,
SONY_BLUETOOTH,
@@ -402,7 +416,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(DRV_PFX "kfifo_alloc failed\n");
goto err_dec_users;
}
@@ -686,7 +700,7 @@ static int acpi_callgetfunc(acpi_handle handle, char *name, int *result)
return 0;
}
- printk(KERN_WARNING DRV_PFX "acpi_callreadfunc failed\n");
+ pr_warn(DRV_PFX "acpi_callreadfunc failed\n");
return -1;
}
@@ -712,7 +726,7 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
if (status == AE_OK) {
if (result != NULL) {
if (out_obj.type != ACPI_TYPE_INTEGER) {
- printk(KERN_WARNING DRV_PFX "acpi_evaluate_object bad "
+ pr_warn(DRV_PFX "acpi_evaluate_object bad "
"return type\n");
return -1;
}
@@ -721,34 +735,101 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
return 0;
}
- printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n");
+ pr_warn(DRV_PFX "acpi_evaluate_object failed\n");
return -1;
}
-static int sony_find_snc_handle(int handle)
+struct sony_nc_handles {
+ u16 cap[0x10];
+ struct device_attribute devattr;
+};
+
+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;
int result;
- for (i = 0x20; i < 0x30; i++) {
- acpi_callsetfunc(sony_nc_acpi_handle, "SN00", i, &result);
- if (result == handle)
- return i-0x20;
+ handles = kzalloc(sizeof(*handles), GFP_KERNEL);
+
+ sysfs_attr_init(&handles->devattr.attr);
+ handles->devattr.attr.name = "handles";
+ handles->devattr.attr.mode = S_IRUGO;
+ handles->devattr.show = sony_nc_handles_show;
+
+ for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+ if (!acpi_callsetfunc(sony_nc_acpi_handle,
+ "SN00", i + 0x20, &result)) {
+ dprintk("caching handle 0x%.4x (offset: 0x%.2x)\n",
+ result, i);
+ handles->cap[i] = result;
+ }
+ }
+
+ /* allow reading capabilities via sysfs */
+ if (device_create_file(&pd->dev, &handles->devattr)) {
+ kfree(handles);
+ handles = NULL;
+ return -1;
}
+ return 0;
+}
+
+static int sony_nc_handles_cleanup(struct platform_device *pd)
+{
+ if (handles) {
+ device_remove_file(&pd->dev, &handles->devattr);
+ kfree(handles);
+ handles = NULL;
+ }
+ return 0;
+}
+
+static int sony_find_snc_handle(int handle)
+{
+ int i;
+ 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 -1;
}
static int sony_call_snc_handle(int handle, int argument, int *result)
{
+ int ret = 0;
int offset = sony_find_snc_handle(handle);
if (offset < 0)
return -1;
- return acpi_callsetfunc(sony_nc_acpi_handle, "SN07", offset | argument,
- result);
+ ret = acpi_callsetfunc(sony_nc_acpi_handle, "SN07", offset | argument,
+ result);
+ dprintk("called SN07 with 0x%.4x (result: 0x%.4x)\n", offset | argument,
+ *result);
+ return ret;
}
/*
@@ -857,11 +938,39 @@ static int sony_backlight_get_brightness(struct backlight_device *bd)
return value - 1;
}
-static struct backlight_device *sony_backlight_device;
+static int sony_nc_get_brightness_ng(struct backlight_device *bd)
+{
+ int result;
+ int *handle = (int *)bl_get_data(bd);
+
+ sony_call_snc_handle(*handle, 0x0200, &result);
+
+ return result & 0xff;
+}
+
+static int sony_nc_update_status_ng(struct backlight_device *bd)
+{
+ int value, result;
+ int *handle = (int *)bl_get_data(bd);
+
+ value = bd->props.brightness;
+ sony_call_snc_handle(*handle, 0x0100 | (value << 16), &result);
+
+ return sony_nc_get_brightness_ng(bd);
+}
+
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,
+};
+static int backlight_ng_handle;
+static struct backlight_device *sony_backlight_device;
/*
* New SNC-only Vaios event mapping to driver known keys
@@ -972,7 +1081,7 @@ static void sony_nc_notify(struct acpi_device *device, u32 event)
}
if (!key_event->data)
- printk(KERN_INFO DRV_PFX
+ pr_info(DRV_PFX
"Unknown event: 0x%x 0x%x\n",
key_handle,
ev);
@@ -996,7 +1105,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(DRV_PFX "method: name: %4.4s, args %X\n",
(char *)&info->name, info->param_count);
kfree(info);
@@ -1037,7 +1146,7 @@ static int sony_nc_resume(struct acpi_device *device)
ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset,
item->value, NULL);
if (ret < 0) {
- printk("%s: %d\n", __func__, ret);
+ pr_err(DRV_PFX "%s: %d\n", __func__, ret);
break;
}
}
@@ -1054,11 +1163,6 @@ static int sony_nc_resume(struct acpi_device *device)
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();
@@ -1205,13 +1309,9 @@ static void sony_nc_rfkill_setup(struct acpi_device *device)
}
device_enum = (union acpi_object *) buffer.pointer;
- if (!device_enum) {
- pr_err("Invalid SN06 return object\n");
- goto out_no_enum;
- }
- if (device_enum->type != ACPI_TYPE_BUFFER) {
- pr_err("Invalid SN06 return object type 0x%.2x\n",
- device_enum->type);
+ if (!device_enum || device_enum->type != ACPI_TYPE_BUFFER) {
+ pr_err(DRV_PFX "Invalid SN06 return object 0x%.2x\n",
+ device_enum->type);
goto out_no_enum;
}
@@ -1245,6 +1345,206 @@ out_no_enum:
return;
}
+/* Keyboard backlight feature */
+#define KBDBL_HANDLER 0x137
+#define KBDBL_PRESENT 0xB00
+#define SET_MODE 0xC00
+#define SET_TIMEOUT 0xE00
+
+struct kbd_backlight {
+ int mode;
+ int timeout;
+ struct device_attribute mode_attr;
+ struct device_attribute timeout_attr;
+};
+
+struct kbd_backlight *kbdbl_handle;
+
+static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value)
+{
+ int result;
+
+ if (value > 1)
+ return -EINVAL;
+
+ if (sony_call_snc_handle(KBDBL_HANDLER,
+ (value << 0x10) | SET_MODE, &result))
+ return -EIO;
+
+ kbdbl_handle->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 (strict_strtoul(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_handle->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_HANDLER,
+ (value << 0x10) | SET_TIMEOUT, &result))
+ return -EIO;
+
+ kbdbl_handle->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 (strict_strtoul(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_handle->timeout);
+ return count;
+}
+
+static int sony_nc_kbd_backlight_setup(struct platform_device *pd)
+{
+ int result;
+
+ if (sony_call_snc_handle(0x137, KBDBL_PRESENT, &result))
+ return 0;
+ if (!(result & 0x02))
+ return 0;
+
+ kbdbl_handle = kzalloc(sizeof(*kbdbl_handle), GFP_KERNEL);
+
+ sysfs_attr_init(&kbdbl_handle->mode_attr.attr);
+ kbdbl_handle->mode_attr.attr.name = "kbd_backlight";
+ kbdbl_handle->mode_attr.attr.mode = S_IRUGO | S_IWUSR;
+ kbdbl_handle->mode_attr.show = sony_nc_kbd_backlight_mode_show;
+ kbdbl_handle->mode_attr.store = sony_nc_kbd_backlight_mode_store;
+
+ sysfs_attr_init(&kbdbl_handle->timeout_attr.attr);
+ kbdbl_handle->timeout_attr.attr.name = "kbd_backlight_timeout";
+ kbdbl_handle->timeout_attr.attr.mode = S_IRUGO | S_IWUSR;
+ kbdbl_handle->timeout_attr.show = sony_nc_kbd_backlight_timeout_show;
+ kbdbl_handle->timeout_attr.store = sony_nc_kbd_backlight_timeout_store;
+
+ if (device_create_file(&pd->dev, &kbdbl_handle->mode_attr))
+ goto outkzalloc;
+
+ if (device_create_file(&pd->dev, &kbdbl_handle->timeout_attr))
+ goto outmode;
+
+ __sony_nc_kbd_backlight_mode_set(kbd_backlight);
+ __sony_nc_kbd_backlight_timeout_set(kbd_backlight_timeout);
+
+ return 0;
+
+outmode:
+ device_remove_file(&pd->dev, &kbdbl_handle->mode_attr);
+outkzalloc:
+ kfree(kbdbl_handle);
+ kbdbl_handle = NULL;
+ return -1;
+}
+
+static int sony_nc_kbd_backlight_cleanup(struct platform_device *pd)
+{
+ if (kbdbl_handle) {
+ device_remove_file(&pd->dev, &kbdbl_handle->mode_attr);
+ device_remove_file(&pd->dev, &kbdbl_handle->timeout_attr);
+ kfree(kbdbl_handle);
+ }
+ return 0;
+}
+
+static void sony_nc_backlight_setup(void)
+{
+ acpi_handle unused;
+ int max_brightness = 0;
+ const struct backlight_ops *ops = NULL;
+ struct backlight_properties props;
+
+ if (sony_find_snc_handle(0x12f) != -1) {
+ backlight_ng_handle = 0x12f;
+ ops = &sony_backlight_ng_ops;
+ max_brightness = 0xff;
+
+ } else if (sony_find_snc_handle(0x137) != -1) {
+ backlight_ng_handle = 0x137;
+ ops = &sony_backlight_ng_ops;
+ max_brightness = 0xff;
+
+ } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT",
+ &unused))) {
+ ops = &sony_backlight_ops;
+ max_brightness = SONY_MAX_BRIGHTNESS - 1;
+
+ } else
+ return;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = max_brightness;
+ sony_backlight_device = backlight_device_register("sony", NULL,
+ &backlight_ng_handle,
+ ops, &props);
+
+ if (IS_ERR(sony_backlight_device)) {
+ pr_warning(DRV_PFX "unable to register backlight device\n");
+ sony_backlight_device = NULL;
+ } else
+ sony_backlight_device->props.brightness =
+ ops->get_brightness(sony_backlight_device);
+}
+
+static void sony_nc_backlight_cleanup(void)
+{
+ if (sony_backlight_device)
+ backlight_device_unregister(sony_backlight_device);
+}
+
static int sony_nc_add(struct acpi_device *device)
{
acpi_status status;
@@ -1252,8 +1552,8 @@ static int sony_nc_add(struct acpi_device *device)
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);
+ pr_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");
@@ -1269,13 +1569,18 @@ 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(DRV_PFX "unable to walk acpi resources\n");
result = -ENODEV;
- goto outwalk;
+ goto outpresent;
}
}
@@ -1288,6 +1593,10 @@ static int sony_nc_add(struct acpi_device *device)
if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00",
&handle))) {
dprintk("Doing SNC setup\n");
+ if (sony_nc_handles_setup(sony_pf_device))
+ goto outpresent;
+ if (sony_nc_kbd_backlight_setup(sony_pf_device))
+ goto outsnc;
sony_nc_function_setup(device);
sony_nc_rfkill_setup(device);
}
@@ -1295,38 +1604,15 @@ static int sony_nc_add(struct acpi_device *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;
+ pr_err(DRV_PFX "Unable to create input devices.\n");
+ goto outkbdbacklight;
}
if (acpi_video_backlight_support()) {
- printk(KERN_INFO DRV_PFX "brightness ignored, must be "
+ pr_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);
- }
-
- }
-
- result = sony_pf_add();
- if (result)
- goto outbacklight;
+ } else
+ sony_nc_backlight_setup();
/* create sony_pf sysfs attributes related to the SNC device */
for (item = sony_nc_values; item->name; ++item) {
@@ -1373,14 +1659,19 @@ static int sony_nc_add(struct acpi_device *device)
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_laptop_remove_input();
+ outkbdbacklight:
+ sony_nc_kbd_backlight_cleanup(sony_pf_device);
+
+ outsnc:
+ sony_nc_handles_cleanup(sony_pf_device);
+
+ outpresent:
+ sony_pf_remove();
+
outwalk:
sony_nc_rfkill_cleanup();
return result;
@@ -1390,8 +1681,7 @@ static int sony_nc_remove(struct acpi_device *device, int type)
{
struct sony_nc_value *item;
- if (sony_backlight_device)
- backlight_device_unregister(sony_backlight_device);
+ sony_nc_backlight_cleanup();
sony_nc_acpi_device = NULL;
@@ -1399,6 +1689,8 @@ static int sony_nc_remove(struct acpi_device *device, int type)
device_remove_file(&sony_pf_device->dev, &item->devattr);
}
+ sony_nc_kbd_backlight_cleanup(sony_pf_device);
+ sony_nc_handles_cleanup(sony_pf_device);
sony_pf_remove();
sony_laptop_remove_input();
sony_nc_rfkill_cleanup();
@@ -1437,7 +1729,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
@@ -1583,8 +1874,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 }
};
@@ -1841,7 +2132,7 @@ out:
if (pcidev)
pci_dev_put(pcidev);
- printk(KERN_INFO DRV_PFX "detected Type%d model\n",
+ pr_info(DRV_PFX "detected Type%d model\n",
dev->model == SONYPI_DEVICE_TYPE1 ? 1 :
dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3);
}
@@ -1889,7 +2180,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(DRV_PFX "camera control not enabled\n");
return -ENODEV;
}
@@ -1909,7 +2200,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(DRV_PFX "camera control not enabled\n");
return -ENODEV;
}
@@ -1932,7 +2223,7 @@ static int __sony_pic_camera_on(void)
}
if (j == 0) {
- printk(KERN_WARNING DRV_PFX "failed to power on camera\n");
+ pr_warn(DRV_PFX "failed to power on camera\n");
return -ENODEV;
}
@@ -1988,7 +2279,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",
+ pr_err(DRV_PFX "sony_pic_camera_command invalid: %d\n",
command);
break;
}
@@ -2395,7 +2686,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(DRV_PFX "kfifo_alloc failed\n");
return error;
}
@@ -2405,11 +2696,11 @@ 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(DRV_PFX "misc_register failed\n");
goto err_free_kfifo;
}
if (minor == -1)
- printk(KERN_INFO DRV_PFX "device allocated minor is %d\n",
+ pr_info(DRV_PFX "device allocated minor is %d\n",
sonypi_misc_device.minor);
return 0;
@@ -2469,8 +2760,7 @@ 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",
+ pr_warn(DRV_PFX "Invalid IRQ %d\n",
p->interrupts[i]);
continue;
}
@@ -2509,7 +2799,7 @@ 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(DRV_PFX "Unknown SPIC Type, more than 2 IO Ports\n");
return AE_ERROR;
}
return AE_OK;
@@ -2537,7 +2827,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(DRV_PFX "Unable to read status\n");
goto end;
}
@@ -2553,8 +2843,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",
+ pr_warn(DRV_PFX "Failure evaluating %s\n",
METHOD_NAME__PRS);
result = -ENODEV;
}
@@ -2668,7 +2957,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(DRV_PFX "Error evaluating _SRS\n");
result = -ENODEV;
goto end;
}
@@ -2724,6 +3013,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;
}
}
@@ -2743,7 +3035,6 @@ 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;
}
@@ -2758,7 +3049,7 @@ static int sony_pic_remove(struct acpi_device *device, int type)
struct sony_pic_irq *irq, *tmp_irq;
if (sony_pic_disable(device)) {
- printk(KERN_ERR DRV_PFX "Couldn't disable device.\n");
+ pr_err(DRV_PFX "Couldn't disable device.\n");
return -ENXIO;
}
@@ -2798,8 +3089,8 @@ 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);
+ pr_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");
@@ -2809,16 +3100,14 @@ 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(DRV_PFX "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(DRV_PFX "Unable to create input devices.\n");
goto err_free_resources;
}
@@ -2859,7 +3148,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(DRV_PFX "Failed to request_region.\n");
result = -ENODEV;
goto err_remove_compat;
}
@@ -2879,7 +3168,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(DRV_PFX "Failed to request_irq.\n");
result = -ENODEV;
goto err_release_region;
}
@@ -2887,7 +3176,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(DRV_PFX "Couldn't enable device.\n");
goto err_free_irq;
}
@@ -2996,8 +3285,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(DRV_PFX "Unable to register SPIC driver.");
goto out;
}
spic_drv_registered = 1;
@@ -3005,7 +3293,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(DRV_PFX "Unable to register SNC driver.");
goto out_unregister_pic;
}
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c
new file mode 100644
index 000000000000..c1372ed9d2e9
--- /dev/null
+++ b/drivers/platform/x86/xo15-ebook.c
@@ -0,0 +1,180 @@
+/*
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+#define MODULE_NAME "xo15-ebook"
+#define PREFIX MODULE_NAME ": "
+
+#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;
+ }
+}
+
+static int ebook_switch_resume(struct acpi_device *device)
+{
+ return ebook_send_state(device);
+}
+
+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)) {
+ printk(KERN_ERR PREFIX "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, int type)
+{
+ 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,
+ .resume = ebook_switch_resume,
+ .remove = ebook_switch_remove,
+ .notify = ebook_switch_notify,
+ },
+};
+
+static int __init xo15_ebook_init(void)
+{
+ return acpi_bus_register_driver(&xo15_ebook_driver);
+}
+
+static void __exit xo15_ebook_exit(void)
+{
+ acpi_bus_unregister_driver(&xo15_ebook_driver);
+}
+
+module_init(xo15_ebook_init);
+module_exit(xo15_ebook_exit);
diff --git a/include/linux/input.h b/include/linux/input.h
index 056ae8a5bd9b..f3a7794a18c4 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -664,6 +664,13 @@ struct input_keymap_entry {
#define KEY_TOUCHPAD_ON 0x213
#define KEY_TOUCHPAD_OFF 0x214
+#define KEY_CAMERA_ZOOMIN 0x215
+#define KEY_CAMERA_ZOOMOUT 0x216
+#define KEY_CAMERA_UP 0x217
+#define KEY_CAMERA_DOWN 0x218
+#define KEY_CAMERA_LEFT 0x219
+#define KEY_CAMERA_RIGHT 0x21a
+
#define BTN_TRIGGER_HAPPY 0x2c0
#define BTN_TRIGGER_HAPPY1 0x2c0
#define BTN_TRIGGER_HAPPY2 0x2c1
diff --git a/include/linux/sonypi.h b/include/linux/sonypi.h
index 0e6dc3891942..c0f87da78f8a 100644
--- a/include/linux/sonypi.h
+++ b/include/linux/sonypi.h
@@ -40,6 +40,7 @@
/* events the user application reading /dev/sonypi can use */
+#define SONYPI_EVENT_IGNORE 0
#define SONYPI_EVENT_JOGDIAL_DOWN 1
#define SONYPI_EVENT_JOGDIAL_UP 2
#define SONYPI_EVENT_JOGDIAL_DOWN_PRESSED 3