1440 lines
40 KiB
Diff
1440 lines
40 KiB
Diff
|
|
Documentation/ABI/testing/sysfs-platform-shuttle-wmi | 72 +++++++++
|
|
MAINTAINERS | 6
|
|
drivers/platform/x86/Kconfig | 15 ++
|
|
drivers/platform/x86/Makefile | 3
|
|
drivers/platform/x86/shuttle-wmi.c | 1297 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
5 files changed, 1393 insertions(+)
|
|
|
|
diff -up linux-6.11/Documentation/ABI/testing/sysfs-platform-shuttle-wmi.wmi linux-6.11/Documentation/ABI/testing/sysfs-platform-shuttle-wmi
|
|
--- linux-6.11/Documentation/ABI/testing/sysfs-platform-shuttle-wmi.wmi 2024-11-18 10:43:15.072608505 +0100
|
|
+++ linux-6.11/Documentation/ABI/testing/sysfs-platform-shuttle-wmi 2024-11-18 10:43:15.072608505 +0100
|
|
@@ -0,0 +1,72 @@
|
|
+What: /sys/devices/platform/shuttle_wmi/lcd_auto_adjust
|
|
+Date: December 2010
|
|
+KernelVersion: 2.6.36
|
|
+Contact: "Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
|
|
+Description:
|
|
+ This is a write only option (accepts any single value, eg.
|
|
+ "echo 1 > lcd_auto_adjust") that starts LCD auto-adjust
|
|
+ function, if the machine has this function enabled. Some
|
|
+ shuttle machines have LCD attached to analog VGA connector,
|
|
+ so uses/needs auto-adjust.
|
|
+
|
|
+What: /sys/devices/platform/shuttle_wmi/model_name
|
|
+Date: December 2010
|
|
+KernelVersion: 2.6.36
|
|
+Contact: "Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
|
|
+Description:
|
|
+ This is a read only attribute which outputs a string with model
|
|
+ name of the machine. When shuttle-wmi can't determine which
|
|
+ model it is, "Unknown" is returned. Otherwise, the possible
|
|
+ models are "Shuttle MA", "Shuttle DA18IE", "Shuttle DA18IM",
|
|
+ "Shuttle X50 V2", "Positivo A14IE01", "Positivo P13",
|
|
+ "Positivo P14".
|
|
+
|
|
+What: /sys/devices/platform/shuttle_wmi/panel_set_default
|
|
+Date: December 2010
|
|
+KernelVersion: 2.6.36
|
|
+Contact: "Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
|
|
+Description:
|
|
+ This is a write only option (accepts any single value, eg.
|
|
+ "echo 1 > panel_set_default"). Probably resets panel/lcd to
|
|
+ default configuration, function not explained in shuttle wmi
|
|
+ documentation. It also starts an auto adjust and color adjust
|
|
+ cycle. The function should only work in shuttle machines with
|
|
+ LCD attached to an analog VGA connector.
|
|
+
|
|
+What: /sys/devices/platform/shuttle_wmi/powersave
|
|
+Date: December 2010
|
|
+KernelVersion: 2.6.36
|
|
+Contact: "Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
|
|
+Description:
|
|
+ Control powersave state. 1 means on, 0 means off.
|
|
+ When enabled, it basically forces the cpu to stay on powersave
|
|
+ state (only works if cpu has P-states support, it is similar to
|
|
+ powersave governor in cpufreq) when machine is only running on
|
|
+ battery. If not running on battery, this function isn't expected
|
|
+ to work, any attempt to enable this returns -EIO.
|
|
+
|
|
+What: /sys/devices/platform/shuttle_wmi/touchpad_off
|
|
+Date: December 2010
|
|
+KernelVersion: 2.6.36
|
|
+Contact: "Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
|
|
+Description:
|
|
+ Control touchpad state. 1 means off, 0 means on.
|
|
+
|
|
+What: /sys/devices/platform/shuttle_wmi/webcam
|
|
+Date: December 2010
|
|
+KernelVersion: 2.6.36
|
|
+Contact: "Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
|
|
+Description:
|
|
+ Control webcam state. 1 means on, 0 means off.
|
|
+
|
|
+What: /sys/devices/platform/shuttle_wmi/white_balance
|
|
+Date: December 2010
|
|
+KernelVersion: 2.6.36
|
|
+Contact: "Herton Ronaldo Krzesinski" <herton@mandriva.com.br>
|
|
+Description:
|
|
+ This is a write only option (accepts any single value, eg.
|
|
+ "echo 1 > white_balance"). Probably triggers an automatic
|
|
+ white balance adjustment for lcd, function not explained in
|
|
+ shuttle wmi documentation. It also starts an auto adjust and
|
|
+ color adjust cycle. The function should only work in shuttle
|
|
+ machines with LCD attached to an analog VGA connector.
|
|
diff -up linux-6.11/drivers/platform/x86/Kconfig.wmi linux-6.11/drivers/platform/x86/Kconfig
|
|
--- linux-6.11/drivers/platform/x86/Kconfig.wmi 2024-11-18 00:42:21.841340904 +0100
|
|
+++ linux-6.11/drivers/platform/x86/Kconfig 2024-11-18 10:43:15.072608505 +0100
|
|
@@ -947,6 +947,21 @@ config PANASONIC_LAPTOP
|
|
If you have a Panasonic Let's note laptop (such as the R1(N variant),
|
|
R2, R3, R5, T2, W2 and Y2 series), say Y.
|
|
|
|
+config SHUTTLE_WMI
|
|
+ tristate "Shuttle WMI Extras Driver"
|
|
+ depends on ACPI_WMI
|
|
+ depends on BACKLIGHT_CLASS_DEVICE
|
|
+ depends on RFKILL
|
|
+ depends on INPUT
|
|
+ select INPUT_SPARSEKMAP
|
|
+ help
|
|
+ This is a driver for the WMI interface present on some Shuttle
|
|
+ machines. It adds controls for wireless, bluetooth and 3g radios,
|
|
+ webcam switch, backlight controls, among others.
|
|
+
|
|
+ If you have a Shuttle machine with ACPI-WMI interface say Y or M
|
|
+ here.
|
|
+
|
|
config SONY_LAPTOP
|
|
tristate "Sony Laptop Extras"
|
|
depends on ACPI
|
|
diff -up linux-6.11/drivers/platform/x86/Makefile.wmi linux-6.11/drivers/platform/x86/Makefile
|
|
--- linux-6.11/drivers/platform/x86/Makefile.wmi 2024-09-15 16:57:56.000000000 +0200
|
|
+++ linux-6.11/drivers/platform/x86/Makefile 2024-11-18 10:43:15.072608505 +0100
|
|
@@ -98,6 +98,9 @@ obj-$(CONFIG_BARCO_P50_GPIO) += barco-p5
|
|
obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o
|
|
obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o
|
|
|
|
+# Shuttle
|
|
+obj-$(CONFIG_SHUTTLE_WMI) += shuttle-wmi.o
|
|
+
|
|
# Toshiba
|
|
obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o
|
|
obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o
|
|
diff -up linux-6.11/drivers/platform/x86/shuttle-wmi.c.wmi linux-6.11/drivers/platform/x86/shuttle-wmi.c
|
|
--- linux-6.11/drivers/platform/x86/shuttle-wmi.c.wmi 2024-11-18 10:43:15.072608505 +0100
|
|
+++ linux-6.11/drivers/platform/x86/shuttle-wmi.c 2024-11-18 10:43:15.072608505 +0100
|
|
@@ -0,0 +1,1297 @@
|
|
+/*
|
|
+ * ACPI-WMI driver for Shuttle WMI interface
|
|
+ *
|
|
+ * Copyright (c) 2010 Herton Ronaldo Krzesinski <herton@mandriva.com.br>
|
|
+ *
|
|
+ * Development of this driver was funded by Positivo Informatica S.A.
|
|
+ * Parts of the driver were based on some WMI documentation provided by Shuttle
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License version 2 as
|
|
+ * published by the Free Software Foundation.
|
|
+ */
|
|
+
|
|
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/dmi.h>
|
|
+#include <linux/acpi.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/debugfs.h>
|
|
+#include <linux/rfkill.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/input/sparse-keymap.h>
|
|
+#include <linux/backlight.h>
|
|
+#include <linux/fb.h>
|
|
+
|
|
+MODULE_AUTHOR("Herton Ronaldo Krzesinski");
|
|
+MODULE_DESCRIPTION("Shuttle WMI Extras Driver");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+#define SHUTTLE_WMI_SETGET_GUID "abbc0f6f-8ea1-11d1-00a0-c90629100000"
|
|
+#define SHUTTLE_WMI_EVENT_GUID "abbc0f72-8ea1-11d1-00a0-c90629100000"
|
|
+MODULE_ALIAS("wmi:"SHUTTLE_WMI_SETGET_GUID);
|
|
+MODULE_ALIAS("wmi:"SHUTTLE_WMI_EVENT_GUID);
|
|
+
|
|
+#define CMD_WRITEEC 0x00
|
|
+#define CMD_READEC 0x01
|
|
+#define CMD_SCMD 0x02
|
|
+#define CMD_INT15 0x03
|
|
+#define CMD_HWSW 0x07
|
|
+#define CMD_LCTRL 0x09
|
|
+#define CMD_CUTLVDS 0x11
|
|
+#define CMD_MA 0x18
|
|
+#define CMD_DA18IE 0x19
|
|
+#define CMD_DA18IM 0x20
|
|
+
|
|
+#define ECRAM_ER0 0x443
|
|
+#define ECRAM_ER1 0x45a
|
|
+#define ECRAM_ER2 0x47b
|
|
+#define ECRAM_ER3 0x758
|
|
+#define ECRAM_ER4 0x759
|
|
+
|
|
+struct shuttle_ecram {
|
|
+ unsigned short addr;
|
|
+ u32 mask;
|
|
+};
|
|
+
|
|
+struct shuttle_state {
|
|
+ struct shuttle_ecram ecram;
|
|
+ struct device_attribute *dev_attr;
|
|
+};
|
|
+
|
|
+static struct shuttle_state state_powersave = {
|
|
+ .ecram = {
|
|
+ .addr = ECRAM_ER3,
|
|
+ .mask = 0x10,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct shuttle_state state_touchpad_off = {
|
|
+ .ecram = {
|
|
+ .addr = ECRAM_ER2,
|
|
+ .mask = 0x02,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct shuttle_state state_webcam = {
|
|
+ .ecram = {
|
|
+ .addr = ECRAM_ER2,
|
|
+ .mask = 0x10,
|
|
+ },
|
|
+};
|
|
+
|
|
+struct shuttle_rfkill {
|
|
+ struct rfkill *rfk;
|
|
+ enum rfkill_type type;
|
|
+ struct shuttle_ecram ecram_state;
|
|
+ struct shuttle_ecram ecram_present;
|
|
+ /* lists of rf state switch notification codes */
|
|
+ u32 rf_on[3];
|
|
+ u32 rf_off[3];
|
|
+};
|
|
+
|
|
+static struct shuttle_rfkill srfk_3g = {
|
|
+ .type = RFKILL_TYPE_WWAN,
|
|
+ .ecram_state = {
|
|
+ .addr = ECRAM_ER2,
|
|
+ .mask = 0x40,
|
|
+ },
|
|
+ .rf_on = { 0x10, 0x29 },
|
|
+ .rf_off = { 0x11, 0x2a },
|
|
+};
|
|
+
|
|
+static struct shuttle_rfkill srfk_bluetooth = {
|
|
+ .type = RFKILL_TYPE_BLUETOOTH,
|
|
+ .ecram_state = {
|
|
+ .addr = ECRAM_ER2,
|
|
+ .mask = 0x20,
|
|
+ },
|
|
+ .rf_on = { 0x0c, 0x29 },
|
|
+ .rf_off = { 0x0d, 0x2a },
|
|
+};
|
|
+
|
|
+static struct shuttle_rfkill srfk_wlan = {
|
|
+ .type = RFKILL_TYPE_WLAN,
|
|
+ .ecram_state = {
|
|
+ .addr = ECRAM_ER2,
|
|
+ .mask = 0x80,
|
|
+ },
|
|
+ .ecram_present = {
|
|
+ .addr = ECRAM_ER1,
|
|
+ .mask = 0x80,
|
|
+ },
|
|
+ .rf_on = { 0x08 },
|
|
+ .rf_off = { 0x09 },
|
|
+};
|
|
+
|
|
+enum fn_type {
|
|
+ FN_CMD,
|
|
+ FN_CMD_DEBUG,
|
|
+ FN_RFKILL,
|
|
+ FN_STATE
|
|
+};
|
|
+
|
|
+struct shuttle_fn_map {
|
|
+ char *name;
|
|
+ enum fn_type type;
|
|
+ unsigned short cmd;
|
|
+ unsigned short arg;
|
|
+ unsigned short fn;
|
|
+ void *data;
|
|
+};
|
|
+
|
|
+static struct shuttle_fn_map unknown_fn_map[] = {
|
|
+ { "fn_f1", FN_CMD_DEBUG, CMD_SCMD, 0, 0x01, NULL },
|
|
+ { "fn_f2", FN_CMD_DEBUG, CMD_SCMD, 0, 0x02, NULL },
|
|
+ { "fn_f3", FN_CMD_DEBUG, CMD_SCMD, 0, 0x03, NULL },
|
|
+ { "fn_f4", FN_CMD_DEBUG, CMD_SCMD, 0, 0x04, NULL },
|
|
+ { "fn_f5", FN_CMD_DEBUG, CMD_SCMD, 0, 0x05, NULL },
|
|
+ { "fn_f6", FN_CMD_DEBUG, CMD_SCMD, 0, 0x06, NULL },
|
|
+ { "fn_f7", FN_CMD_DEBUG, CMD_SCMD, 0, 0x07, NULL },
|
|
+ { "fn_f8", FN_CMD_DEBUG, CMD_SCMD, 0, 0x08, NULL },
|
|
+ { "fn_f9", FN_CMD_DEBUG, CMD_SCMD, 0, 0x09, NULL },
|
|
+ { "fn_f10", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0a, NULL },
|
|
+ { "fn_f11", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0b, NULL },
|
|
+ { "fn_f12", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0c, NULL },
|
|
+ { "fn_f13", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0d, NULL },
|
|
+ { "fn_f14", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0e, NULL },
|
|
+ { "fn_f15", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0f, NULL },
|
|
+ { "lcd_auto_adjust", FN_CMD, CMD_SCMD, 0, 0x81, NULL },
|
|
+ { "lightbar_brightness_down", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x00, NULL },
|
|
+ { "lightbar_brightness_up", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x01, NULL },
|
|
+ { "panel_set_default", FN_CMD, CMD_SCMD, 0, 0x83, NULL },
|
|
+ { "video_output_on", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x01, NULL},
|
|
+ { "video_output_off", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x00, NULL},
|
|
+ { "white_balance", FN_CMD, CMD_SCMD, 0, 0x82, NULL },
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct shuttle_fn_map fn_map_1[] = {
|
|
+ { "brightness_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0b, NULL },
|
|
+ { "brightness_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0c, NULL },
|
|
+ { "lcd_auto_adjust", FN_CMD, CMD_SCMD, 0, 0x81, NULL },
|
|
+ { "lightbar_brightness_down", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x00, NULL },
|
|
+ { "lightbar_brightness_up", FN_CMD_DEBUG, CMD_LCTRL, 1, 0x01, NULL },
|
|
+ { "panel_set_default", FN_CMD, CMD_SCMD, 0, 0x83, NULL },
|
|
+ { "powersave", FN_STATE, CMD_SCMD, 0, 0x02, &state_powersave },
|
|
+ { "shuttle_3g", FN_RFKILL, CMD_SCMD, 0, 0x05, &srfk_3g },
|
|
+ { "shuttle_bluetooth", FN_RFKILL, CMD_SCMD, 0, 0x0d, &srfk_bluetooth },
|
|
+ { "shuttle_wlan", FN_RFKILL, CMD_SCMD, 0, 0x04, &srfk_wlan },
|
|
+ { "sleep", FN_CMD_DEBUG, CMD_SCMD, 0, 0x01, NULL },
|
|
+ { "sound_mute", FN_CMD_DEBUG, CMD_SCMD, 0, 0x08, NULL },
|
|
+ { "switch_video", FN_CMD_DEBUG, CMD_SCMD, 0, 0x03, NULL },
|
|
+ { "touchpad_off", FN_STATE, CMD_SCMD, 0, 0x06, &state_touchpad_off },
|
|
+ { "volume_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x09, NULL },
|
|
+ { "volume_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x0a, NULL },
|
|
+ { "video_output_on", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x01, NULL},
|
|
+ { "video_output_off", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x00, NULL},
|
|
+ { "webcam", FN_STATE, CMD_SCMD, 0, 0x07, &state_webcam },
|
|
+ { "white_balance", FN_CMD, CMD_SCMD, 0, 0x82, NULL },
|
|
+ { }
|
|
+};
|
|
+
|
|
+static struct shuttle_fn_map fn_map_2[] = {
|
|
+ { "brightness_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x08, NULL },
|
|
+ { "brightness_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x09, NULL },
|
|
+ { "video_output", FN_CMD_DEBUG, CMD_SCMD, 0, 0x02, NULL },
|
|
+ { "video_output_on", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x01, NULL},
|
|
+ { "video_output_off", FN_CMD_DEBUG, CMD_CUTLVDS, 0, 0x00, NULL},
|
|
+ { "shuttle_wlan", FN_RFKILL, CMD_SCMD, 0, 0x0b, &srfk_wlan },
|
|
+ { "sleep", FN_CMD_DEBUG, CMD_SCMD, 0, 0x03, NULL },
|
|
+ { "sound_mute", FN_CMD_DEBUG, CMD_SCMD, 0, 0x04, NULL },
|
|
+ { "switch_video", FN_CMD_DEBUG, CMD_SCMD, 0, 0x07, NULL },
|
|
+ { "touchpad_off", FN_STATE, CMD_SCMD, 0, 0x01, &state_touchpad_off },
|
|
+ { "volume_down", FN_CMD_DEBUG, CMD_SCMD, 0, 0x05, NULL },
|
|
+ { "volume_up", FN_CMD_DEBUG, CMD_SCMD, 0, 0x06, NULL },
|
|
+ { "webcam", FN_STATE, CMD_SCMD, 0, 0x0a, &state_webcam },
|
|
+ { }
|
|
+};
|
|
+
|
|
+struct shuttle_backlight {
|
|
+ u8 ec_addr;
|
|
+ struct shuttle_fn_map *fn_bl_down;
|
|
+ struct shuttle_fn_map *fn_bl_up;
|
|
+};
|
|
+
|
|
+static struct shuttle_backlight common_bl_desc = {
|
|
+ .ec_addr = 0x79,
|
|
+};
|
|
+
|
|
+static struct shuttle_backlight quirk_bl_desc = {
|
|
+ .ec_addr = 0x79,
|
|
+ .fn_bl_down = &fn_map_1[0],
|
|
+ .fn_bl_up = &fn_map_1[1],
|
|
+};
|
|
+
|
|
+struct shuttle_id {
|
|
+ unsigned char cmd_id;
|
|
+ const char *model_name;
|
|
+ struct shuttle_backlight *bl_desc;
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+};
|
|
+
|
|
+static struct shuttle_id shuttle_ids[] = {
|
|
+ { CMD_MA, "Shuttle MA", &common_bl_desc, fn_map_1 },
|
|
+ { CMD_DA18IE, "Shuttle DA18IE", &quirk_bl_desc, fn_map_1 },
|
|
+ { CMD_DA18IM, "Shuttle DA18IM", &common_bl_desc, fn_map_1 }
|
|
+};
|
|
+
|
|
+static struct shuttle_id id_unknown = {
|
|
+ .model_name = "Unknown",
|
|
+ .fn_map = unknown_fn_map,
|
|
+};
|
|
+
|
|
+static struct shuttle_id shuttle_dmi_id;
|
|
+
|
|
+static int shuttle_dmi_matched(const struct dmi_system_id *dmi)
|
|
+{
|
|
+ shuttle_dmi_id.model_name = dmi->ident;
|
|
+ shuttle_dmi_id.bl_desc = &common_bl_desc;
|
|
+ shuttle_dmi_id.fn_map = dmi->driver_data;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static struct dmi_system_id shuttle_dmi_ids[] = {
|
|
+ {
|
|
+ .callback = shuttle_dmi_matched,
|
|
+ .ident = "Shuttle X50 V2",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_PRODUCT_NAME, "X50-V2"),
|
|
+ },
|
|
+ .driver_data = fn_map_1,
|
|
+ },
|
|
+ {
|
|
+ .callback = shuttle_dmi_matched,
|
|
+ .ident = "Positivo A14IE01",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "A14IE01"),
|
|
+ },
|
|
+ .driver_data = fn_map_2,
|
|
+ },
|
|
+ {
|
|
+ .callback = shuttle_dmi_matched,
|
|
+ .ident = "Positivo P13",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "P13"),
|
|
+ },
|
|
+ .driver_data = fn_map_2,
|
|
+ },
|
|
+ {
|
|
+ .callback = shuttle_dmi_matched,
|
|
+ .ident = "Positivo P14",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "P14"),
|
|
+ },
|
|
+ .driver_data = fn_map_2,
|
|
+ },
|
|
+ {}
|
|
+};
|
|
+
|
|
+static struct dmi_system_id __devinitdata shuttle_quirk_bl_dmi_ids[] = {
|
|
+ {
|
|
+ .ident = "Positivo M13",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "M13"),
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .ident = "Positivo M14",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "M14"),
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .ident = "Positivo A14IM01",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "A14IM01"),
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .ident = "Positivo J14IM21",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_BOARD_VENDOR, "POSITIVO"),
|
|
+ DMI_MATCH(DMI_BOARD_NAME, "J14IM21"),
|
|
+ },
|
|
+ },
|
|
+ {}
|
|
+};
|
|
+
|
|
+struct shuttle_wmi {
|
|
+ struct platform_device *pdev;
|
|
+ struct shuttle_id *id;
|
|
+ struct dentry *dbg_root;
|
|
+ struct attribute_group *attr_group;
|
|
+ struct input_dev *inputdev;
|
|
+ struct backlight_device *bd;
|
|
+};
|
|
+
|
|
+struct shuttle_cmd {
|
|
+ u16 param2;
|
|
+ u16 param1;
|
|
+ u8 arg;
|
|
+ u8 cmd;
|
|
+ u16 hdr;
|
|
+};
|
|
+
|
|
+static int wmi_setget_mtd(struct shuttle_cmd *scmd, u32 *res)
|
|
+{
|
|
+ acpi_status status;
|
|
+ union acpi_object *obj;
|
|
+ struct acpi_buffer input;
|
|
+ static DEFINE_MUTEX(mtd_lock);
|
|
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
+
|
|
+ input.length = sizeof(struct shuttle_cmd);
|
|
+ scmd->hdr = 0xec00;
|
|
+ input.pointer = (u8 *) scmd;
|
|
+
|
|
+ /* We must serialize access to wmi_evaluate_method: the wmi interface
|
|
+ * functions in the bios save its parameters on a common shared buffer,
|
|
+ * which gets overwritten on parallel calls with unpredicted results;
|
|
+ * AML code doesn't have any locking, so we must do this here */
|
|
+ mutex_lock(&mtd_lock);
|
|
+ status = wmi_evaluate_method(SHUTTLE_WMI_SETGET_GUID, 0, 2,
|
|
+ &input, &output);
|
|
+ mutex_unlock(&mtd_lock);
|
|
+ if (ACPI_FAILURE(status))
|
|
+ return -1;
|
|
+
|
|
+ obj = output.pointer;
|
|
+ if (obj) {
|
|
+ if (obj->type == ACPI_TYPE_INTEGER) {
|
|
+ if (res)
|
|
+ *res = obj->integer.value;
|
|
+ } else {
|
|
+ pr_err("Unsupported object returned (%s)", __func__);
|
|
+ res = NULL;
|
|
+ }
|
|
+ kfree(obj);
|
|
+ } else {
|
|
+ if (res) {
|
|
+ pr_warning("No result from WMI method (%s)", __func__);
|
|
+ res = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return (res) ? 0 : 1;
|
|
+}
|
|
+
|
|
+static int wmi_ec_cmd(unsigned char cmd, unsigned char arg,
|
|
+ unsigned short param1, unsigned short param2,
|
|
+ u32 *res)
|
|
+{
|
|
+ struct shuttle_cmd scmd = {
|
|
+ .cmd = cmd,
|
|
+ .arg = arg,
|
|
+ .param1 = param1,
|
|
+ .param2 = param2
|
|
+ };
|
|
+
|
|
+ return wmi_setget_mtd(&scmd, res);
|
|
+}
|
|
+
|
|
+static int wmi_ec_state(struct shuttle_ecram *ecram)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ if (wmi_ec_cmd(CMD_READEC, 0, 0, ecram->addr, &val))
|
|
+ return -EIO;
|
|
+ return (val & ecram->mask) ? 1 : 0;
|
|
+}
|
|
+
|
|
+static int rfkill_common_set_block(void *data, bool blocked)
|
|
+{
|
|
+ int sw;
|
|
+ struct shuttle_fn_map *fn_map = data;
|
|
+ struct shuttle_rfkill *srfk = fn_map->data;
|
|
+
|
|
+ sw = wmi_ec_state(&srfk->ecram_state);
|
|
+ if (sw < 0)
|
|
+ return sw;
|
|
+
|
|
+ if (blocked == sw)
|
|
+ wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn, NULL);
|
|
+ else
|
|
+ return 0;
|
|
+
|
|
+ sw = wmi_ec_state(&srfk->ecram_state);
|
|
+ if (sw < 0)
|
|
+ return sw;
|
|
+
|
|
+ return (sw != blocked) ? 0 : -EIO;
|
|
+}
|
|
+
|
|
+static const struct rfkill_ops rfkill_common_ops = {
|
|
+ .set_block = rfkill_common_set_block,
|
|
+};
|
|
+
|
|
+static void pr_possible_dev_state(void)
|
|
+{
|
|
+ static bool pr;
|
|
+ u32 val;
|
|
+
|
|
+ if (!pr) {
|
|
+ pr = true;
|
|
+ pr_info("need to unblock some rfkills to check device"
|
|
+ " presence\n");
|
|
+ if (!wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
|
|
+ pr_info("possible device present state at address"
|
|
+ " 0x%04x is 0x%08x\n", ECRAM_ER1, val);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int shuttle_rfkill_init(struct shuttle_fn_map *fn_map,
|
|
+ struct device *dev)
|
|
+{
|
|
+ int rc;
|
|
+ struct shuttle_rfkill *srfk = fn_map->data;
|
|
+
|
|
+ /* Try to detect if device controlled by this rfkill is present, to
|
|
+ * avoid having an rfkill switch when not needed */
|
|
+ if (srfk->ecram_present.mask && srfk->ecram_present.addr) {
|
|
+ /* we have an address to read to check if device is present */
|
|
+ rc = wmi_ec_state(&srfk->ecram_present);
|
|
+ if (rc <= 0)
|
|
+ return rc;
|
|
+
|
|
+ rc = wmi_ec_state(&srfk->ecram_state);
|
|
+ if (rc < 0)
|
|
+ return rc;
|
|
+ } else {
|
|
+ /* print only once the possible value of devices presence, for
|
|
+ * extra information (useful to check if really device presence
|
|
+ * is or isn't available at usual ECRAM_ER1 address) */
|
|
+ pr_possible_dev_state();
|
|
+
|
|
+ /* no address/mask to check, detect if device is available by
|
|
+ * trying to enable it, in case it's disabled */
|
|
+ rc = wmi_ec_state(&srfk->ecram_state);
|
|
+ if (rc < 0)
|
|
+ return rc;
|
|
+ if (!rc) {
|
|
+ if (rfkill_common_set_block(fn_map, false))
|
|
+ return 0;
|
|
+
|
|
+ /* after check, reset to initial setting; should be
|
|
+ * unlikely this returns with error, but really check if
|
|
+ * we could reset to initial blocked setting, otherwise
|
|
+ * don't make it a fatal error and assume rfkill not
|
|
+ * blocked */
|
|
+ if (rfkill_common_set_block(fn_map, true))
|
|
+ rc = 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ srfk->rfk = rfkill_alloc(fn_map->name, dev, srfk->type,
|
|
+ &rfkill_common_ops, fn_map);
|
|
+ if (!srfk->rfk)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ rfkill_init_sw_state(srfk->rfk, !rc);
|
|
+
|
|
+ rc = rfkill_register(srfk->rfk);
|
|
+ if (rc) {
|
|
+ rfkill_destroy(srfk->rfk);
|
|
+ srfk->rfk = NULL;
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void shuttle_rfkill_remove(struct shuttle_fn_map *fn_map)
|
|
+{
|
|
+ struct shuttle_rfkill *srfk = fn_map->data;
|
|
+
|
|
+ if (srfk->rfk) {
|
|
+ rfkill_unregister(srfk->rfk);
|
|
+ rfkill_destroy(srfk->rfk);
|
|
+ srfk->rfk = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int shuttle_rfkill_resume(struct device *dev)
|
|
+{
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+ struct shuttle_rfkill *srfk;
|
|
+ int rc;
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct shuttle_wmi *priv = platform_get_drvdata(pdev);
|
|
+
|
|
+ for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
|
|
+ if (fn_map->type != FN_RFKILL)
|
|
+ continue;
|
|
+
|
|
+ srfk = fn_map->data;
|
|
+ if (srfk->rfk) {
|
|
+ rc = wmi_ec_state(&srfk->ecram_state);
|
|
+ if (rc < 0)
|
|
+ return rc;
|
|
+ rfkill_set_sw_state(srfk->rfk, rc);
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static bool set_rfkill_sw(u32 *list, u32 code, struct rfkill *rfk, bool blocked)
|
|
+{
|
|
+ while (*list) {
|
|
+ if (*list == code) {
|
|
+ rfkill_set_sw_state(rfk, blocked);
|
|
+ return true;
|
|
+ }
|
|
+ list++;
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static bool notify_switch_rfkill(struct shuttle_wmi *priv, u32 code)
|
|
+{
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+ struct shuttle_rfkill *srfk;
|
|
+ struct rfkill *rfk;
|
|
+ bool res = false;
|
|
+
|
|
+ for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
|
|
+ if (fn_map->type != FN_RFKILL)
|
|
+ continue;
|
|
+
|
|
+ srfk = fn_map->data;
|
|
+ rfk = srfk->rfk;
|
|
+ if (!rfk)
|
|
+ continue;
|
|
+
|
|
+ /* check if notification code means radio turned on, looking
|
|
+ * at list_on array of "on" notification codes for this rfkill;
|
|
+ * if code is in this list, we notify rfkill core (set_rfkill_sw
|
|
+ * does the check, notification and returns true) and we can
|
|
+ * skip to next rfkill on the list (some notification codes are
|
|
+ * shared, firmware may want to turn on two radios at same time,
|
|
+ * so we must check all rfkills with this code) */
|
|
+ if (set_rfkill_sw(srfk->rf_on, code, rfk, false)) {
|
|
+ res = true;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* same as above, but we check the list_off to see if
|
|
+ * notification code means radio turned off */
|
|
+ if (set_rfkill_sw(srfk->rf_off, code, rfk, true))
|
|
+ res = true;
|
|
+ }
|
|
+ /* if we found that notification code was indeed a radio on/off event,
|
|
+ * return true here */
|
|
+ return res;
|
|
+}
|
|
+
|
|
+static bool notify_switch_attr(struct platform_device *pdev, u32 code)
|
|
+{
|
|
+ int i;
|
|
+ struct shuttle_switch {
|
|
+ u32 switch_on;
|
|
+ u32 switch_off;
|
|
+ char *sys_attr;
|
|
+ };
|
|
+ static const struct shuttle_switch codes[] = {
|
|
+ { 0x04, 0x05, "touchpad_off" },
|
|
+ { 0x12, 0x13, "webcam" },
|
|
+ { 0x31, 0x32, "powersave" }
|
|
+ };
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(codes); i++) {
|
|
+ if (codes[i].switch_on == code || codes[i].switch_off == code) {
|
|
+ sysfs_notify(&pdev->dev.kobj, NULL, codes[i].sys_attr);
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static void shuttle_wmi_notify(u32 value, void *data)
|
|
+{
|
|
+ acpi_status status;
|
|
+ union acpi_object *obj;
|
|
+ u8 type;
|
|
+ u32 code;
|
|
+ struct acpi_buffer res = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
+ struct shuttle_wmi *priv = data;
|
|
+
|
|
+ status = wmi_get_event_data(value, &res);
|
|
+ if (status != AE_OK) {
|
|
+ pr_warning("unable to retrieve wmi event status"
|
|
+ " (error=0x%x)\n", status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ obj = (union acpi_object *) res.pointer;
|
|
+ if (!obj)
|
|
+ return;
|
|
+ if (obj->type != ACPI_TYPE_INTEGER) {
|
|
+ pr_info("unknown object returned in wmi event\n");
|
|
+ goto notify_exit;
|
|
+ }
|
|
+
|
|
+ type = (obj->integer.value >> 24) & 0xFF;
|
|
+ switch (type) {
|
|
+ case 0: /* OSD/Scancode event */
|
|
+ code = obj->integer.value & 0xFFFFFF;
|
|
+
|
|
+ /* update rfkill switches */
|
|
+ if (notify_switch_rfkill(priv, code))
|
|
+ break;
|
|
+
|
|
+ /* send notification on state switch attributes */
|
|
+ if (notify_switch_attr(priv->pdev, code))
|
|
+ break;
|
|
+
|
|
+ if (priv->bd && (code == 0x14 || code == 0x15)) {
|
|
+ backlight_force_update(priv->bd,
|
|
+ BACKLIGHT_UPDATE_HOTKEY);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!sparse_keymap_report_event(priv->inputdev, code, 1, true))
|
|
+ pr_info("unhandled scancode (0x%06x)\n", code);
|
|
+ break;
|
|
+ case 1: /* Power management event */
|
|
+ /* Events not used.
|
|
+ * Possible values for obj->integer.value:
|
|
+ * 0x01000000 - silent mode
|
|
+ * 0x01010000 - brightness sync */
|
|
+ case 2: /* i-PowerXross event */
|
|
+ /* i-PowerXross is a overclocking feature, not
|
|
+ * implemented, there are no further details, possible
|
|
+ * values for obj->integer.value in documentation:
|
|
+ * 0x02000000 - idle mode
|
|
+ * 0x02010000 - action mode
|
|
+ * 0x02020000 - entry s3 */
|
|
+ break;
|
|
+ case 0xec: /* Lost event */
|
|
+ if (printk_ratelimit())
|
|
+ pr_warning("lost event because of buggy BIOS");
|
|
+ break;
|
|
+ default:
|
|
+ pr_info("unknown wmi notification type (0x%02x)\n", type);
|
|
+ }
|
|
+
|
|
+notify_exit:
|
|
+ kfree(obj);
|
|
+}
|
|
+
|
|
+static const struct key_entry shuttle_wmi_keymap[] = {
|
|
+ { KE_IGNORE, 0x14, { KEY_BRIGHTNESSUP } },
|
|
+ { KE_IGNORE, 0x15, { KEY_BRIGHTNESSDOWN } },
|
|
+ { KE_KEY, 0x16, { KEY_FASTFORWARD } },
|
|
+ { KE_KEY, 0x17, { KEY_REWIND } },
|
|
+ { KE_KEY, 0x18, { KEY_F13 } }, /* OSD Beep */
|
|
+ { KE_KEY, 0x2b, { KEY_F14 } }, /* OSD menu 1 */
|
|
+ { KE_KEY, 0x2c, { KEY_F15 } }, /* OSD menu 2 */
|
|
+ { KE_KEY, 0x2d, { KEY_F16 } }, /* OSD menu 3 */
|
|
+ { KE_KEY, 0x2e, { KEY_F17 } }, /* OSD menu 4 */
|
|
+ { KE_KEY, 0x33, { KEY_F18 } }, /* Update OSD bar status */
|
|
+ { KE_KEY, 0x90, { KEY_WWW } },
|
|
+ { KE_KEY, 0x95, { KEY_PREVIOUSSONG } },
|
|
+ { KE_KEY, 0xa0, { KEY_PROG1 } }, /* Call OSD software */
|
|
+ { KE_KEY, 0xa1, { KEY_VOLUMEDOWN } },
|
|
+ { KE_KEY, 0xa3, { KEY_MUTE } },
|
|
+ { KE_KEY, 0xb2, { KEY_VOLUMEUP } },
|
|
+ { KE_KEY, 0xb4, { KEY_PLAYPAUSE } },
|
|
+ { KE_KEY, 0xbb, { KEY_STOPCD } },
|
|
+ { KE_KEY, 0xc8, { KEY_MAIL } },
|
|
+ { KE_KEY, 0xcd, { KEY_NEXTSONG } },
|
|
+ { KE_KEY, 0xd0, { KEY_MEDIA } },
|
|
+
|
|
+ /* Known non hotkey events don't handled, that we don't care or
|
|
+ * which we must ignore */
|
|
+ { KE_IGNORE, 0x01, }, /* Caps Lock toggled */
|
|
+ { KE_IGNORE, 0x02, }, /* Num Lock toggled */
|
|
+ { KE_IGNORE, 0x03, }, /* Scroll Lock toggled */
|
|
+ { KE_IGNORE, 0x06, }, /* Downclock/Silent on */
|
|
+ { KE_IGNORE, 0x07, }, /* Downclock/Silent off */
|
|
+ { KE_IGNORE, 0x0a, }, /* WiMax on */
|
|
+ { KE_IGNORE, 0x0b, }, /* WiMax off */
|
|
+ { KE_IGNORE, 0x0e, }, /* RF on */
|
|
+ { KE_IGNORE, 0x0f, }, /* RF off */
|
|
+ { KE_IGNORE, 0x1a, }, /* Auto Brightness on */
|
|
+ { KE_IGNORE, 0x1b, }, /* Auto Brightness off */
|
|
+ { KE_IGNORE, 0x1c, }, /* Auto-KB Brightness on */
|
|
+ { KE_IGNORE, 0x1d, }, /* Auto-KB Brightness off */
|
|
+ { KE_IGNORE, 0x1e, }, /* Light Bar Brightness up */
|
|
+ { KE_IGNORE, 0x1f, }, /* Light Bar Brightness down */
|
|
+ { KE_IGNORE, 0x20, }, /* China Telecom AP enable */
|
|
+ { KE_IGNORE, 0x21, }, /* China Mobile AP enable */
|
|
+ { KE_IGNORE, 0x22, }, /* Huawei AP enable */
|
|
+ { KE_IGNORE, 0x23, }, /* Docking in */
|
|
+ { KE_IGNORE, 0x24, }, /* Docking out */
|
|
+ { KE_IGNORE, 0x25, }, /* Device no function */
|
|
+ { KE_IGNORE, 0x26, }, /* i-PowerXross OverClocking */
|
|
+ { KE_IGNORE, 0x27, }, /* i-PowerXross PowerSaving */
|
|
+ { KE_IGNORE, 0x28, }, /* i-PowerXross off */
|
|
+ { KE_IGNORE, 0x2f, }, /* Optimus on */
|
|
+ { KE_IGNORE, 0x30, }, /* Optimus off */
|
|
+ { KE_IGNORE, 0x91, }, /* ICO 2 on */
|
|
+ { KE_IGNORE, 0x92, }, /* ICO 2 off */
|
|
+
|
|
+ { KE_END, 0 }
|
|
+};
|
|
+
|
|
+static int shuttle_wmi_input_init(struct shuttle_wmi *priv)
|
|
+{
|
|
+ struct input_dev *input;
|
|
+ int rc;
|
|
+
|
|
+ input = input_allocate_device();
|
|
+ if (!input)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ input->name = "Shuttle WMI hotkeys";
|
|
+ input->phys = KBUILD_MODNAME "/input0";
|
|
+ input->id.bustype = BUS_HOST;
|
|
+
|
|
+ rc = sparse_keymap_setup(input, shuttle_wmi_keymap, NULL);
|
|
+ if (rc)
|
|
+ goto err_free_dev;
|
|
+
|
|
+ rc = input_register_device(input);
|
|
+ if (rc)
|
|
+ goto err_free_keymap;
|
|
+
|
|
+ priv->inputdev = input;
|
|
+ return 0;
|
|
+
|
|
+err_free_keymap:
|
|
+ sparse_keymap_free(input);
|
|
+err_free_dev:
|
|
+ input_free_device(input);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static void shuttle_wmi_input_remove(struct shuttle_wmi *priv)
|
|
+{
|
|
+ struct input_dev *input = priv->inputdev;
|
|
+
|
|
+ sparse_keymap_free(input);
|
|
+ input_unregister_device(input);
|
|
+}
|
|
+
|
|
+static int shuttle_wmi_get_bl(struct backlight_device *bd)
|
|
+{
|
|
+ u8 val;
|
|
+ int rc;
|
|
+ struct shuttle_wmi *priv = bl_get_data(bd);
|
|
+ struct shuttle_backlight *sbl = priv->id->bl_desc;
|
|
+
|
|
+ rc = ec_read(sbl->ec_addr, &val);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ return val & 7;
|
|
+}
|
|
+
|
|
+static int shuttle_wmi_update_bl(struct backlight_device *bd)
|
|
+{
|
|
+ int rc, steps;
|
|
+ u8 val;
|
|
+ struct shuttle_fn_map *fn_down, *fn_up;
|
|
+ struct shuttle_wmi *priv = bl_get_data(bd);
|
|
+ struct shuttle_backlight *sbl = priv->id->bl_desc;
|
|
+
|
|
+ fn_down = sbl->fn_bl_down;
|
|
+ fn_up = sbl->fn_bl_up;
|
|
+ if (!fn_down || !fn_up) {
|
|
+ rc = ec_write(sbl->ec_addr, bd->props.brightness);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ } else {
|
|
+ /* change brightness by steps, this is a quirk for shuttle
|
|
+ * machines which don't accept direct write to ec for this */
|
|
+ rc = ec_read(sbl->ec_addr, &val);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ steps = bd->props.brightness - (val & 7);
|
|
+ while (steps > 0) {
|
|
+ wmi_ec_cmd(fn_up->cmd, fn_up->arg, 0, fn_up->fn, NULL);
|
|
+ steps--;
|
|
+ }
|
|
+ while (steps < 0) {
|
|
+ wmi_ec_cmd(fn_down->cmd, fn_down->arg, 0, fn_down->fn,
|
|
+ NULL);
|
|
+ steps++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ wmi_ec_cmd(CMD_CUTLVDS, 0, 0,
|
|
+ (bd->props.power == FB_BLANK_UNBLANK) ? 1 : 0,
|
|
+ NULL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct backlight_ops shuttle_wmi_bl_ops = {
|
|
+ .get_brightness = shuttle_wmi_get_bl,
|
|
+ .update_status = shuttle_wmi_update_bl,
|
|
+};
|
|
+
|
|
+static int shuttle_wmi_backlight_init(struct shuttle_wmi *priv)
|
|
+{
|
|
+ int rc;
|
|
+ u8 val;
|
|
+ struct backlight_properties props;
|
|
+ struct backlight_device *bd;
|
|
+ struct shuttle_backlight *sbl = priv->id->bl_desc;
|
|
+
|
|
+ rc = ec_read(sbl->ec_addr, &val);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ memset(&props, 0, sizeof(struct backlight_properties));
|
|
+ props.max_brightness = 7;
|
|
+ props.brightness = val & 7;
|
|
+ props.power = FB_BLANK_UNBLANK;
|
|
+
|
|
+ bd = backlight_device_register(KBUILD_MODNAME, &priv->pdev->dev, priv,
|
|
+ &shuttle_wmi_bl_ops, &props);
|
|
+ if (IS_ERR(bd))
|
|
+ return PTR_ERR(bd);
|
|
+ priv->bd = bd;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void shuttle_wmi_backlight_exit(struct shuttle_wmi *priv)
|
|
+{
|
|
+ if (priv->bd)
|
|
+ backlight_device_unregister(priv->bd);
|
|
+}
|
|
+
|
|
+static ssize_t store_fn_cmd(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct shuttle_wmi *priv = platform_get_drvdata(pdev);
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+
|
|
+ for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
|
|
+ if (fn_map->name == attr->attr.name) {
|
|
+ wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn,
|
|
+ NULL);
|
|
+ return count;
|
|
+ }
|
|
+ }
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+static int set_fn_cmd_debug(void *data, u64 val)
|
|
+{
|
|
+ struct shuttle_fn_map *fn_map = data;
|
|
+
|
|
+ wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn, NULL);
|
|
+ /* we don't know yet how many brightness values or maximum brightness
|
|
+ * values for lightbar, for now print possible brightness value change
|
|
+ * to aid in discovering these */
|
|
+ if (fn_map->cmd == CMD_LCTRL) {
|
|
+ u32 val;
|
|
+ if (!wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER4, &val))
|
|
+ pr_info("possible lightbar brightness change to value"
|
|
+ " 0x%08x\n", val);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+DEFINE_SIMPLE_ATTRIBUTE(fops_fn_cmd_debug, NULL, set_fn_cmd_debug, "%llu");
|
|
+
|
|
+static ssize_t show_fn_state(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+ struct shuttle_state *state;
|
|
+ int sw;
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct shuttle_wmi *priv = platform_get_drvdata(pdev);
|
|
+
|
|
+ for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
|
|
+ if (fn_map->name != attr->attr.name)
|
|
+ continue;
|
|
+
|
|
+ state = fn_map->data;
|
|
+ sw = wmi_ec_state(&state->ecram);
|
|
+ if (sw < 0)
|
|
+ return sw;
|
|
+ return sprintf(buf, "%d\n", sw);
|
|
+ }
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+static ssize_t store_fn_state(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ int enable, sw;
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+ struct shuttle_state *state;
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct shuttle_wmi *priv = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (sscanf(buf, "%i", &enable) != 1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (fn_map = priv->id->fn_map; fn_map->name; fn_map++) {
|
|
+ if (fn_map->name != attr->attr.name)
|
|
+ continue;
|
|
+
|
|
+ state = fn_map->data;
|
|
+ sw = wmi_ec_state(&state->ecram);
|
|
+ if (sw < 0)
|
|
+ return sw;
|
|
+ enable = enable ? 1 : 0;
|
|
+ if (enable != sw) {
|
|
+ wmi_ec_cmd(fn_map->cmd, fn_map->arg, 0, fn_map->fn,
|
|
+ NULL);
|
|
+ sw = wmi_ec_state(&state->ecram);
|
|
+ if (sw < 0)
|
|
+ return sw;
|
|
+ if (enable != sw)
|
|
+ return -EIO;
|
|
+ }
|
|
+ return count;
|
|
+ }
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+static void shuttle_fn_exit(struct shuttle_wmi *priv)
|
|
+{
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+ struct dentry *dbg_entry;
|
|
+ struct shuttle_state *state;
|
|
+
|
|
+ if (priv->attr_group) {
|
|
+ sysfs_remove_group(&priv->pdev->dev.kobj, priv->attr_group);
|
|
+ kfree(priv->attr_group->attrs);
|
|
+ kfree(priv->attr_group);
|
|
+ priv->attr_group = NULL;
|
|
+ }
|
|
+
|
|
+ fn_map = priv->id->fn_map;
|
|
+ while (fn_map->name) {
|
|
+ switch (fn_map->type) {
|
|
+ case FN_CMD:
|
|
+ kfree(fn_map->data);
|
|
+ fn_map->data = NULL;
|
|
+ break;
|
|
+ case FN_CMD_DEBUG:
|
|
+ dbg_entry = fn_map->data;
|
|
+ if (dbg_entry) {
|
|
+ debugfs_remove(dbg_entry);
|
|
+ fn_map->data = NULL;
|
|
+ }
|
|
+ break;
|
|
+ case FN_RFKILL:
|
|
+ shuttle_rfkill_remove(fn_map);
|
|
+ break;
|
|
+ case FN_STATE:
|
|
+ state = fn_map->data;
|
|
+ kfree(state->dev_attr);
|
|
+ state->dev_attr = NULL;
|
|
+ break;
|
|
+ }
|
|
+ fn_map++;
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct device_attribute *new_dev_attr(struct shuttle_fn_map *fn_map)
|
|
+{
|
|
+ struct device_attribute *dev_attr;
|
|
+
|
|
+ dev_attr = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
|
|
+ if (!dev_attr)
|
|
+ return NULL;
|
|
+ dev_attr->attr.name = fn_map->name;
|
|
+ return dev_attr;
|
|
+}
|
|
+
|
|
+static int shuttle_fn_init(struct shuttle_wmi *priv)
|
|
+{
|
|
+ struct shuttle_fn_map *fn_map;
|
|
+ struct device_attribute *dev_attr;
|
|
+ struct dentry *dbg_entry;
|
|
+ struct shuttle_state *state;
|
|
+ struct attribute **attr;
|
|
+ int nattr = 0;
|
|
+ int rc = -ENOMEM;
|
|
+
|
|
+ fn_map = priv->id->fn_map;
|
|
+ while (fn_map->name) {
|
|
+ switch (fn_map->type) {
|
|
+ case FN_CMD:
|
|
+ dev_attr = new_dev_attr(fn_map);
|
|
+ if (!dev_attr)
|
|
+ goto fn_init_err;
|
|
+ dev_attr->attr.mode = 0200;
|
|
+ dev_attr->store = store_fn_cmd;
|
|
+ fn_map->data = dev_attr;
|
|
+ nattr++;
|
|
+ break;
|
|
+ case FN_CMD_DEBUG:
|
|
+ dbg_entry = debugfs_create_file(fn_map->name, 0200,
|
|
+ priv->dbg_root, fn_map,
|
|
+ &fops_fn_cmd_debug);
|
|
+ if (!dbg_entry)
|
|
+ goto fn_init_err;
|
|
+ fn_map->data = dbg_entry;
|
|
+ break;
|
|
+ case FN_RFKILL:
|
|
+ rc = shuttle_rfkill_init(fn_map, &priv->pdev->dev);
|
|
+ if (rc)
|
|
+ goto fn_init_err;
|
|
+ break;
|
|
+ case FN_STATE:
|
|
+ dev_attr = new_dev_attr(fn_map);
|
|
+ if (!dev_attr)
|
|
+ goto fn_init_err;
|
|
+ dev_attr->attr.mode = 0644;
|
|
+ dev_attr->show = show_fn_state;
|
|
+ dev_attr->store = store_fn_state;
|
|
+ state = fn_map->data;
|
|
+ state->dev_attr = dev_attr;
|
|
+ nattr++;
|
|
+ break;
|
|
+ }
|
|
+ fn_map++;
|
|
+ }
|
|
+
|
|
+ /* create array of sysfs attributes (FN_CMD and FN_STATE types) */
|
|
+ if (nattr > 0) {
|
|
+ priv->attr_group = kzalloc(sizeof(struct attribute_group),
|
|
+ GFP_KERNEL);
|
|
+ if (!priv->attr_group)
|
|
+ goto fn_init_err;
|
|
+ priv->attr_group->attrs = kzalloc(sizeof(struct attribute *) *
|
|
+ (nattr + 1), GFP_KERNEL);
|
|
+ if (!priv->attr_group->attrs)
|
|
+ goto fn_attrs_err;
|
|
+ attr = priv->attr_group->attrs;
|
|
+ fn_map = priv->id->fn_map;
|
|
+ while (fn_map->name) {
|
|
+ if (fn_map->type == FN_CMD) {
|
|
+ dev_attr = fn_map->data;
|
|
+ *attr = &dev_attr->attr;
|
|
+ attr++;
|
|
+ } else if (fn_map->type == FN_STATE) {
|
|
+ state = fn_map->data;
|
|
+ *attr = &state->dev_attr->attr;
|
|
+ attr++;
|
|
+ }
|
|
+ fn_map++;
|
|
+ }
|
|
+ rc = sysfs_create_group(&priv->pdev->dev.kobj,
|
|
+ priv->attr_group);
|
|
+ if (rc)
|
|
+ goto fn_grp_err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fn_grp_err:
|
|
+ kfree(priv->attr_group->attrs);
|
|
+fn_attrs_err:
|
|
+ kfree(priv->attr_group);
|
|
+ priv->attr_group = NULL;
|
|
+fn_init_err:
|
|
+ shuttle_fn_exit(priv);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int __devinit shuttle_wmi_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct shuttle_wmi *priv;
|
|
+ int rc, i;
|
|
+ acpi_status status;
|
|
+ u32 val;
|
|
+
|
|
+ priv = kzalloc(sizeof(struct shuttle_wmi), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+ priv->pdev = pdev;
|
|
+ platform_set_drvdata(pdev, priv);
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(shuttle_ids); i++) {
|
|
+ rc = wmi_ec_cmd(shuttle_ids[i].cmd_id, 0, 0, 0, &val);
|
|
+ if (!rc && val == 1) {
|
|
+ priv->id = &shuttle_ids[i];
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ /* If we can't identify the system using a WMI command, try using a DMI
|
|
+ * match, otherwise set id to unknown model */
|
|
+ if (i == ARRAY_SIZE(shuttle_ids)) {
|
|
+ if (dmi_check_system(shuttle_dmi_ids))
|
|
+ priv->id = &shuttle_dmi_id;
|
|
+ else
|
|
+ priv->id = &id_unknown;
|
|
+ }
|
|
+
|
|
+ /* Process backlight quirks for some models based on DA18IM */
|
|
+ if (priv->id->cmd_id == CMD_DA18IM) {
|
|
+ if (dmi_check_system(shuttle_quirk_bl_dmi_ids))
|
|
+ priv->id->bl_desc = &quirk_bl_desc;
|
|
+ }
|
|
+
|
|
+ priv->dbg_root = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
|
+ if (!priv->dbg_root) {
|
|
+ rc = -ENOMEM;
|
|
+ goto err_debugfs;
|
|
+ }
|
|
+
|
|
+ rc = shuttle_fn_init(priv);
|
|
+ if (rc)
|
|
+ goto err_fn;
|
|
+
|
|
+ rc = shuttle_wmi_input_init(priv);
|
|
+ if (rc)
|
|
+ goto err_input;
|
|
+
|
|
+ status = wmi_install_notify_handler(SHUTTLE_WMI_EVENT_GUID,
|
|
+ shuttle_wmi_notify, priv);
|
|
+ if (ACPI_FAILURE(status)) {
|
|
+ rc = -EIO;
|
|
+ goto err_notify;
|
|
+ }
|
|
+
|
|
+ if (!acpi_video_backlight_support()) {
|
|
+ rc = shuttle_wmi_backlight_init(priv);
|
|
+ if (rc)
|
|
+ goto err_backlight;
|
|
+ }
|
|
+ return 0;
|
|
+
|
|
+err_backlight:
|
|
+ wmi_remove_notify_handler(SHUTTLE_WMI_EVENT_GUID);
|
|
+err_notify:
|
|
+ shuttle_wmi_input_remove(priv);
|
|
+err_input:
|
|
+ shuttle_fn_exit(priv);
|
|
+err_fn:
|
|
+ debugfs_remove(priv->dbg_root);
|
|
+err_debugfs:
|
|
+ kfree(priv);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int __devexit shuttle_wmi_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct shuttle_wmi *priv = platform_get_drvdata(pdev);
|
|
+
|
|
+ shuttle_wmi_backlight_exit(priv);
|
|
+ wmi_remove_notify_handler(SHUTTLE_WMI_EVENT_GUID);
|
|
+ shuttle_wmi_input_remove(priv);
|
|
+ shuttle_fn_exit(priv);
|
|
+ debugfs_remove(priv->dbg_root);
|
|
+ kfree(priv);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int shuttle_wmi_resume(struct device *dev)
|
|
+{
|
|
+ return shuttle_rfkill_resume(dev);
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops shuttle_wmi_pm_ops = {
|
|
+ .restore = shuttle_wmi_resume,
|
|
+ .resume = shuttle_wmi_resume,
|
|
+};
|
|
+
|
|
+static struct platform_driver shuttle_wmi_driver = {
|
|
+ .driver = {
|
|
+ .name = KBUILD_MODNAME,
|
|
+ .owner = THIS_MODULE,
|
|
+ .pm = &shuttle_wmi_pm_ops,
|
|
+ },
|
|
+ .probe = shuttle_wmi_probe,
|
|
+ .remove = __devexit_p(shuttle_wmi_remove),
|
|
+};
|
|
+
|
|
+static struct platform_device *shuttle_wmi_device;
|
|
+
|
|
+static ssize_t show_model_name(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct shuttle_wmi *priv = platform_get_drvdata(pdev);
|
|
+
|
|
+ return sprintf(buf, "%s\n", priv->id->model_name);
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(model_name, 0444, show_model_name, NULL);
|
|
+
|
|
+static struct attribute *shuttle_platform_attributes[] = {
|
|
+ &dev_attr_model_name.attr,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static struct attribute_group shuttle_attribute_group = {
|
|
+ .attrs = shuttle_platform_attributes
|
|
+};
|
|
+
|
|
+static int __init shuttle_wmi_init(void)
|
|
+{
|
|
+ int rc;
|
|
+ u32 val;
|
|
+
|
|
+ if (!wmi_has_guid(SHUTTLE_WMI_SETGET_GUID) ||
|
|
+ !wmi_has_guid(SHUTTLE_WMI_EVENT_GUID)) {
|
|
+ pr_err("Required WMI GUID not available\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /* Check that we are really on a shuttle BIOS */
|
|
+ rc = wmi_ec_cmd(CMD_INT15, 0, 0, 0, &val);
|
|
+ if (rc || val != 0x534c) {
|
|
+ pr_err("Shuttle WMI device not found or unsupported"
|
|
+ " (val=0x%08x)\n", val);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ rc = platform_driver_register(&shuttle_wmi_driver);
|
|
+ if (rc)
|
|
+ goto err_driver_register;
|
|
+ shuttle_wmi_device = platform_device_alloc(KBUILD_MODNAME, -1);
|
|
+ if (!shuttle_wmi_device) {
|
|
+ rc = -ENOMEM;
|
|
+ goto err_device_alloc;
|
|
+ }
|
|
+ rc = platform_device_add(shuttle_wmi_device);
|
|
+ if (rc)
|
|
+ goto err_device_add;
|
|
+
|
|
+ rc = sysfs_create_group(&shuttle_wmi_device->dev.kobj,
|
|
+ &shuttle_attribute_group);
|
|
+ if (rc)
|
|
+ goto err_sysfs;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_sysfs:
|
|
+ platform_device_del(shuttle_wmi_device);
|
|
+err_device_add:
|
|
+ platform_device_put(shuttle_wmi_device);
|
|
+err_device_alloc:
|
|
+ platform_driver_unregister(&shuttle_wmi_driver);
|
|
+err_driver_register:
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static void __exit shuttle_wmi_exit(void)
|
|
+{
|
|
+ sysfs_remove_group(&shuttle_wmi_device->dev.kobj,
|
|
+ &shuttle_attribute_group);
|
|
+ platform_device_unregister(shuttle_wmi_device);
|
|
+ platform_driver_unregister(&shuttle_wmi_driver);
|
|
+}
|
|
+
|
|
+module_init(shuttle_wmi_init);
|
|
+module_exit(shuttle_wmi_exit);
|
|
diff -up linux-6.11/MAINTAINERS.wmi linux-6.11/MAINTAINERS
|
|
--- linux-6.11/MAINTAINERS.wmi 2024-11-18 00:42:21.251332731 +0100
|
|
+++ linux-6.11/MAINTAINERS 2024-11-18 10:43:15.076608549 +0100
|
|
@@ -20958,6 +20958,12 @@ S: Orphan
|
|
F: drivers/media/platform/renesas/sh_vou.c
|
|
F: include/media/drv-intf/sh_vou.h
|
|
|
|
+SHUTTLE WMI EXTRAS DRIVER
|
|
+M: Herton Ronaldo Krzesinski <herton@mandriva.com.br>
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
+S: Maintained
|
|
+F: drivers/platform/x86/shuttle-wmi.c
|
|
+
|
|
SI2157 MEDIA DRIVER
|
|
L: linux-media@vger.kernel.org
|
|
S: Orphan
|