Merge tag 'platform-drivers-x86-v5.5-1' of git://git.infradead.org/linux-platform-drivers-x86

Pull x86 platform driver updates from Andy Shevchenko:

 - New bootctl driver for Mellanox BlueField SoC.

 - New driver to support System76 laptops.

 - Temperature monitoring and fan control on Acer Aspire 7551 is now
   supported.

 - Previously the Huawei driver handled only hotkeys. After the
   conversion to WMI it has been expanded to support newer laptop
   models.

 - Big refactoring of intel-speed-select tools allows to use it on Intel
   CascadeLake-N systems.

 - Touchscreen support for ezpad 6 m4 and Schneider SCT101CTM tablets

 - Miscellaneous clean ups and fixes here and there.

* tag 'platform-drivers-x86-v5.5-1' of git://git.infradead.org/linux-platform-drivers-x86: (59 commits)
  platform/x86: hp-wmi: Fix ACPI errors caused by passing 0 as input size
  platform/x86: hp-wmi: Fix ACPI errors caused by too small buffer
  platform/x86: intel_pmc_core: Add Comet Lake (CML) platform support to intel_pmc_core driver
  platform/x86: intel_pmc_core: Fix the SoC naming inconsistency
  platform/mellanox: Fix Kconfig indentation
  tools/power/x86/intel-speed-select: Display TRL buckets for just base config level
  tools/power/x86/intel-speed-select: Ignore missing config level
  platform/x86: touchscreen_dmi: Add info for the ezpad 6 m4 tablet
  tools/power/x86/intel-speed-select: Increment version
  tools/power/x86/intel-speed-select: Use core count for base-freq mask
  tools/power/x86/intel-speed-select: Support platform with limited Intel(R) Speed Select
  tools/power/x86/intel-speed-select: Use Frequency weight for CLOS
  tools/power/x86/intel-speed-select: Make CLOS frequency in MHz
  tools/power/x86/intel-speed-select: Use mailbox for CLOS_PM_QOS_CONFIG
  tools/power/x86/intel-speed-select: Auto mode for CLX
  tools/power/x86/intel-speed-select: Correct CLX-N frequency units
  tools/power/x86/intel-speed-select: Change display of "avx" to "avx2"
  tools/power/x86/intel-speed-select: Extend command set for perf-profile
  Add touchscreen platform data for the Schneider SCT101CTM tablet
  platform/x86: intel_int0002_vgpio: Pass irqchip when adding gpiochip
  ...
This commit is contained in:
Linus Torvalds
2019-12-01 18:24:25 -08:00
29 changed files with 3283 additions and 589 deletions

View File

@@ -94,7 +94,6 @@ config ASUS_LAPTOP
depends on RFKILL || RFKILL = n
depends on ACPI_VIDEO || ACPI_VIDEO = n
select INPUT_SPARSEKMAP
select INPUT_POLLDEV
---help---
This is a driver for Asus laptops, Lenovo SL and the Pegatron
Lucid tablet. It may also support some MEDION, JVC or VICTOR
@@ -623,7 +622,6 @@ config THINKPAD_ACPI_HOTKEY_POLL
config SENSORS_HDAPS
tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
depends on INPUT
select INPUT_POLLDEV
help
This driver provides support for the IBM Hard Drive Active Protection
System (hdaps), which provides an accelerometer and other misc. data.
@@ -806,7 +804,6 @@ config PEAQ_WMI
tristate "PEAQ 2-in-1 WMI hotkey driver"
depends on ACPI_WMI
depends on INPUT
select INPUT_POLLDEV
help
Say Y here if you want to support WMI-based hotkeys on PEAQ 2-in-1s.
@@ -834,7 +831,6 @@ config ACPI_TOSHIBA
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on RFKILL || RFKILL = n
depends on IIO
select INPUT_POLLDEV
select INPUT_SPARSEKMAP
---help---
This driver adds support for access to certain system settings
@@ -931,14 +927,20 @@ config INTEL_CHT_INT33FE
This driver add support for the INT33FE ACPI device found on
some Intel Cherry Trail devices.
There are two kinds of INT33FE ACPI device possible: for hardware
with USB Type-C and Micro-B connectors. This driver supports both.
The INT33FE ACPI device has a CRS table with I2cSerialBusV2
resources for 3 devices: Maxim MAX17047 Fuel Gauge Controller,
resources for Fuel Gauge Controller and (in the Type-C variant)
FUSB302 USB Type-C Controller and PI3USB30532 USB switch.
This driver instantiates i2c-clients for these, so that standard
i2c drivers for these chips can bind to the them.
If you enable this driver it is advised to also select
CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m.
CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B
device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m
for Type-C device.
config INTEL_INT0002_VGPIO
tristate "Intel ACPI INT0002 Virtual GPIO driver"
@@ -1305,7 +1307,8 @@ config INTEL_ATOMISP2_PM
will be called intel_atomisp2_pm.
config HUAWEI_WMI
tristate "Huawei WMI hotkeys driver"
tristate "Huawei WMI laptop extras driver"
depends on ACPI_BATTERY
depends on ACPI_WMI
depends on INPUT
select INPUT_SPARSEKMAP
@@ -1314,9 +1317,8 @@ config HUAWEI_WMI
select LEDS_TRIGGER_AUDIO
select NEW_LEDS
help
This driver provides support for Huawei WMI hotkeys.
It enables the missing keys and adds support to the micmute
LED found on some of these laptops.
This driver provides support for Huawei WMI hotkeys, battery charge
control, fn-lock, mic-mute LED, and other extra features.
To compile this driver as a module, choose M here: the module
will be called huawei-wmi.
@@ -1337,6 +1339,19 @@ config PCENGINES_APU2
source "drivers/platform/x86/intel_speed_select_if/Kconfig"
config SYSTEM76_ACPI
tristate "System76 ACPI Driver"
depends on ACPI
select NEW_LEDS
select LEDS_CLASS
select LEDS_TRIGGERS
help
This is a driver for System76 laptops running open firmware. It adds
support for Fn-Fx key combinations, keyboard backlight, and airplane mode
LEDs.
If you have a System76 laptop running open firmware, say Y or M here.
endif # X86_PLATFORM_DEVICES
config PMC_ATOM

View File

@@ -61,6 +61,10 @@ obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o
obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o
obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o
obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o
intel_cht_int33fe-objs := intel_cht_int33fe_common.o \
intel_cht_int33fe_typec.o \
intel_cht_int33fe_microb.o
obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o
obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o
obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o
@@ -100,3 +104,4 @@ obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o
obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o
obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/
obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o

View File

@@ -4,7 +4,7 @@
* of the aspire one netbook, turns on/off the fan
* as soon as the upper/lower threshold is reached.
*
* (C) 2009 - Peter Feuerer peter (a) piie.net
* (C) 2009 - Peter Kaestle peter (a) piie.net
* http://piie.net
* 2009 Borislav Petkov bp (a) alien8.de
*
@@ -224,6 +224,8 @@ static const struct bios_settings bios_tbl[] __initconst = {
{"Acer", "Aspire 5739G", "V1.3311", 0x55, 0x58, {0x20, 0x00}, 0},
/* Acer TravelMate 7730 */
{"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00}, 0},
/* Acer Aspire 7551 */
{"Acer", "Aspire 7551", "V1.18", 0x93, 0xa8, {0x14, 0x04}, 1},
/* Acer TravelMate TM8573T */
{"Acer", "TM8573T", "V1.13", 0x93, 0xa8, {0x14, 0x04}, 1},
/* Gateway */
@@ -801,7 +803,7 @@ static void __exit acerhdf_exit(void)
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Peter Feuerer");
MODULE_AUTHOR("Peter Kaestle");
MODULE_DESCRIPTION("Aspire One temperature and fan driver");
MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:");
MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:");
@@ -815,6 +817,7 @@ MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5739G:");
MODULE_ALIAS("dmi:*:*Acer*:pnAspire*One*753:");
MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5315:");
MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:");
MODULE_ALIAS("dmi:*:*Acer*:pnAspire*7551:");
MODULE_ALIAS("dmi:*:*Acer*:TM8573T:");
MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:");
MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:");

View File

@@ -34,7 +34,6 @@
#include <linux/uaccess.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/input-polldev.h>
#include <linux/rfkill.h>
#include <linux/slab.h>
#include <linux/dmi.h>
@@ -244,7 +243,7 @@ struct asus_laptop {
struct input_dev *inputdev;
struct key_entry *keymap;
struct input_polled_dev *pega_accel_poll;
struct input_dev *pega_accel_poll;
struct asus_led wled;
struct asus_led bled;
@@ -446,9 +445,9 @@ static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method)
return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP);
}
static void pega_accel_poll(struct input_polled_dev *ipd)
static void pega_accel_poll(struct input_dev *input)
{
struct device *parent = ipd->input->dev.parent;
struct device *parent = input->dev.parent;
struct asus_laptop *asus = dev_get_drvdata(parent);
/* In some cases, the very first call to poll causes a
@@ -457,10 +456,10 @@ static void pega_accel_poll(struct input_polled_dev *ipd)
* device, and perhaps a firmware bug. Fake the first report. */
if (!asus->pega_acc_live) {
asus->pega_acc_live = true;
input_report_abs(ipd->input, ABS_X, 0);
input_report_abs(ipd->input, ABS_Y, 0);
input_report_abs(ipd->input, ABS_Z, 0);
input_sync(ipd->input);
input_report_abs(input, ABS_X, 0);
input_report_abs(input, ABS_Y, 0);
input_report_abs(input, ABS_Z, 0);
input_sync(input);
return;
}
@@ -471,25 +470,24 @@ static void pega_accel_poll(struct input_polled_dev *ipd)
/* Note transform, convert to "right/up/out" in the native
* landscape orientation (i.e. the vector is the direction of
* "real up" in the device's cartiesian coordinates). */
input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x);
input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y);
input_report_abs(ipd->input, ABS_Z, asus->pega_acc_z);
input_sync(ipd->input);
input_report_abs(input, ABS_X, -asus->pega_acc_x);
input_report_abs(input, ABS_Y, -asus->pega_acc_y);
input_report_abs(input, ABS_Z, asus->pega_acc_z);
input_sync(input);
}
static void pega_accel_exit(struct asus_laptop *asus)
{
if (asus->pega_accel_poll) {
input_unregister_polled_device(asus->pega_accel_poll);
input_free_polled_device(asus->pega_accel_poll);
input_unregister_device(asus->pega_accel_poll);
asus->pega_accel_poll = NULL;
}
asus->pega_accel_poll = NULL;
}
static int pega_accel_init(struct asus_laptop *asus)
{
int err;
struct input_polled_dev *ipd;
struct input_dev *input;
if (!asus->is_pega_lucid)
return -ENODEV;
@@ -499,37 +497,39 @@ static int pega_accel_init(struct asus_laptop *asus)
acpi_check_handle(asus->handle, METHOD_XLRZ, NULL))
return -ENODEV;
ipd = input_allocate_polled_device();
if (!ipd)
input = input_allocate_device();
if (!input)
return -ENOMEM;
ipd->poll = pega_accel_poll;
ipd->poll_interval = 125;
ipd->poll_interval_min = 50;
ipd->poll_interval_max = 2000;
input->name = PEGA_ACCEL_DESC;
input->phys = PEGA_ACCEL_NAME "/input0";
input->dev.parent = &asus->platform_device->dev;
input->id.bustype = BUS_HOST;
ipd->input->name = PEGA_ACCEL_DESC;
ipd->input->phys = PEGA_ACCEL_NAME "/input0";
ipd->input->dev.parent = &asus->platform_device->dev;
ipd->input->id.bustype = BUS_HOST;
set_bit(EV_ABS, ipd->input->evbit);
input_set_abs_params(ipd->input, ABS_X,
input_set_abs_params(input, ABS_X,
-PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
input_set_abs_params(ipd->input, ABS_Y,
input_set_abs_params(input, ABS_Y,
-PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
input_set_abs_params(ipd->input, ABS_Z,
input_set_abs_params(input, ABS_Z,
-PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
err = input_register_polled_device(ipd);
err = input_setup_polling(input, pega_accel_poll);
if (err)
goto exit;
asus->pega_accel_poll = ipd;
input_set_poll_interval(input, 125);
input_set_min_poll_interval(input, 50);
input_set_max_poll_interval(input, 2000);
err = input_register_device(input);
if (err)
goto exit;
asus->pega_accel_poll = input;
return 0;
exit:
input_free_polled_device(ipd);
input_free_device(input);
return err;
}
@@ -1550,8 +1550,7 @@ static void asus_acpi_notify(struct acpi_device *device, u32 event)
/* Accelerometer "coarse orientation change" event */
if (asus->pega_accel_poll && event == 0xEA) {
kobject_uevent(&asus->pega_accel_poll->input->dev.kobj,
KOBJ_CHANGE);
kobject_uevent(&asus->pega_accel_poll->dev.kobj, KOBJ_CHANGE);
return ;
}

View File

@@ -33,6 +33,7 @@
struct quirk_entry {
bool touchpad_led;
bool kbd_led_not_present;
bool kbd_led_levels_off_1;
bool kbd_missing_ac_tag;
@@ -73,6 +74,10 @@ static struct quirk_entry quirk_dell_latitude_e6410 = {
.kbd_led_levels_off_1 = true,
};
static struct quirk_entry quirk_dell_inspiron_1012 = {
.kbd_led_not_present = true,
};
static struct platform_driver platform_driver = {
.driver = {
.name = "dell-laptop",
@@ -310,6 +315,24 @@ static const struct dmi_system_id dell_quirks[] __initconst = {
},
.driver_data = &quirk_dell_latitude_e6410,
},
{
.callback = dmi_matched,
.ident = "Dell Inspiron 1012",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"),
},
.driver_data = &quirk_dell_inspiron_1012,
},
{
.callback = dmi_matched,
.ident = "Dell Inspiron 1018",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1018"),
},
.driver_data = &quirk_dell_inspiron_1012,
},
{ }
};
@@ -1493,6 +1516,9 @@ static void kbd_init(void)
{
int ret;
if (quirks && quirks->kbd_led_not_present)
return;
ret = kbd_init_info();
kbd_init_tokens();

View File

@@ -18,7 +18,7 @@
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input-polldev.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/module.h>
@@ -59,7 +59,7 @@
#define HDAPS_BOTH_AXES (HDAPS_X_AXIS | HDAPS_Y_AXIS)
static struct platform_device *pdev;
static struct input_polled_dev *hdaps_idev;
static struct input_dev *hdaps_idev;
static unsigned int hdaps_invert;
static u8 km_activity;
static int rest_x;
@@ -318,9 +318,8 @@ static void hdaps_calibrate(void)
__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
}
static void hdaps_mousedev_poll(struct input_polled_dev *dev)
static void hdaps_mousedev_poll(struct input_dev *input_dev)
{
struct input_dev *input_dev = dev->input;
int x, y;
mutex_lock(&hdaps_mtx);
@@ -531,7 +530,6 @@ static const struct dmi_system_id hdaps_whitelist[] __initconst = {
static int __init hdaps_init(void)
{
struct input_dev *idev;
int ret;
if (!dmi_check_system(hdaps_whitelist)) {
@@ -559,31 +557,32 @@ static int __init hdaps_init(void)
if (ret)
goto out_device;
hdaps_idev = input_allocate_polled_device();
hdaps_idev = input_allocate_device();
if (!hdaps_idev) {
ret = -ENOMEM;
goto out_group;
}
hdaps_idev->poll = hdaps_mousedev_poll;
hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
/* initial calibrate for the input device */
hdaps_calibrate();
/* initialize the input class */
idev = hdaps_idev->input;
idev->name = "hdaps";
idev->phys = "isa1600/input0";
idev->id.bustype = BUS_ISA;
idev->dev.parent = &pdev->dev;
idev->evbit[0] = BIT_MASK(EV_ABS);
input_set_abs_params(idev, ABS_X,
hdaps_idev->name = "hdaps";
hdaps_idev->phys = "isa1600/input0";
hdaps_idev->id.bustype = BUS_ISA;
hdaps_idev->dev.parent = &pdev->dev;
input_set_abs_params(hdaps_idev, ABS_X,
-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
input_set_abs_params(idev, ABS_Y,
input_set_abs_params(hdaps_idev, ABS_Y,
-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
ret = input_register_polled_device(hdaps_idev);
ret = input_setup_polling(hdaps_idev, hdaps_mousedev_poll);
if (ret)
goto out_idev;
input_set_poll_interval(hdaps_idev, HDAPS_POLL_INTERVAL);
ret = input_register_device(hdaps_idev);
if (ret)
goto out_idev;
@@ -591,7 +590,7 @@ static int __init hdaps_init(void)
return 0;
out_idev:
input_free_polled_device(hdaps_idev);
input_free_device(hdaps_idev);
out_group:
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
out_device:
@@ -607,8 +606,7 @@ out:
static void __exit hdaps_exit(void)
{
input_unregister_polled_device(hdaps_idev);
input_free_polled_device(hdaps_idev);
input_unregister_device(hdaps_idev);
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
platform_device_unregister(pdev);
platform_driver_unregister(&hdaps_driver);

View File

@@ -65,7 +65,7 @@ struct bios_args {
u32 command;
u32 commandtype;
u32 datasize;
u32 data;
u8 data[128];
};
enum hp_wmi_commandtype {
@@ -216,7 +216,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
.command = command,
.commandtype = query,
.datasize = insize,
.data = 0,
.data = { 0 },
};
struct acpi_buffer input = { sizeof(struct bios_args), &args };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
@@ -228,7 +228,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
if (WARN_ON(insize > sizeof(args.data)))
return -EINVAL;
memcpy(&args.data, buffer, insize);
memcpy(&args.data[0], buffer, insize);
wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output);
@@ -380,7 +380,7 @@ static int hp_wmi_rfkill2_refresh(void)
int err, i;
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state,
0, sizeof(state));
sizeof(state), sizeof(state));
if (err)
return err;
@@ -778,7 +778,7 @@ static int __init hp_wmi_rfkill2_setup(struct platform_device *device)
int err, i;
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state,
0, sizeof(state));
sizeof(state), sizeof(state));
if (err)
return err < 0 ? err : -EINVAL;

View File

@@ -1,32 +1,77 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Huawei WMI hotkeys
* Huawei WMI laptop extras driver
*
* Copyright (C) 2018 Ayman Bagabas <ayman.bagabas@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/sysfs.h>
#include <linux/wmi.h>
#include <acpi/battery.h>
/*
* Huawei WMI GUIDs
*/
#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C90629100000"
#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
/* Legacy GUIDs */
#define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100"
#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
struct huawei_wmi_priv {
struct input_dev *idev;
struct led_classdev cdev;
acpi_handle handle;
char *acpi_method;
/* HWMI commands */
enum {
BATTERY_THRESH_GET = 0x00001103, /* \GBTT */
BATTERY_THRESH_SET = 0x00001003, /* \SBTT */
FN_LOCK_GET = 0x00000604, /* \GFRS */
FN_LOCK_SET = 0x00000704, /* \SFRS */
MICMUTE_LED_SET = 0x00000b04, /* \SMLS */
};
union hwmi_arg {
u64 cmd;
u8 args[8];
};
struct quirk_entry {
bool battery_reset;
bool ec_micmute;
bool report_brightness;
};
static struct quirk_entry *quirks;
struct huawei_wmi_debug {
struct dentry *root;
u64 arg;
};
struct huawei_wmi {
bool battery_available;
bool fn_lock_available;
struct huawei_wmi_debug debug;
struct input_dev *idev[2];
struct led_classdev cdev;
struct device *dev;
struct mutex wmi_lock;
};
static struct huawei_wmi *huawei_wmi;
static const struct key_entry huawei_wmi_keymap[] = {
{ KE_KEY, 0x281, { KEY_BRIGHTNESSDOWN } },
{ KE_KEY, 0x282, { KEY_BRIGHTNESSUP } },
@@ -37,73 +82,614 @@ static const struct key_entry huawei_wmi_keymap[] = {
{ KE_KEY, 0x289, { KEY_WLAN } },
// Huawei |M| key
{ KE_KEY, 0x28a, { KEY_CONFIG } },
// Keyboard backlight
// Keyboard backlit
{ KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } },
{ KE_IGNORE, 0x294, { KEY_KBDILLUMUP } },
{ KE_IGNORE, 0x295, { KEY_KBDILLUMUP } },
{ KE_END, 0 }
};
static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
static int battery_reset = -1;
static int report_brightness = -1;
module_param(battery_reset, bint, 0444);
MODULE_PARM_DESC(battery_reset,
"Reset battery charge values to (0-0) before disabling it using (0-100)");
module_param(report_brightness, bint, 0444);
MODULE_PARM_DESC(report_brightness,
"Report brightness keys.");
/* Quirks */
static int __init dmi_matched(const struct dmi_system_id *dmi)
{
quirks = dmi->driver_data;
return 1;
}
static struct quirk_entry quirk_unknown = {
};
static struct quirk_entry quirk_battery_reset = {
.battery_reset = true,
};
static struct quirk_entry quirk_matebook_x = {
.ec_micmute = true,
.report_brightness = true,
};
static const struct dmi_system_id huawei_quirks[] = {
{
.callback = dmi_matched,
.ident = "Huawei MACH-WX9",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
DMI_MATCH(DMI_PRODUCT_NAME, "MACH-WX9"),
},
.driver_data = &quirk_battery_reset
},
{
.callback = dmi_matched,
.ident = "Huawei MateBook X",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
DMI_MATCH(DMI_PRODUCT_NAME, "HUAWEI MateBook X")
},
.driver_data = &quirk_matebook_x
},
{ }
};
/* Utils */
static int huawei_wmi_call(struct huawei_wmi *huawei,
struct acpi_buffer *in, struct acpi_buffer *out)
{
struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent);
acpi_status status;
union acpi_object args[3];
struct acpi_object_list arg_list = {
.pointer = args,
.count = ARRAY_SIZE(args),
};
args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
args[1].integer.value = 0x04;
if (strcmp(priv->acpi_method, "SPIN") == 0) {
args[0].integer.value = 0;
args[2].integer.value = brightness ? 1 : 0;
} else if (strcmp(priv->acpi_method, "WPIN") == 0) {
args[0].integer.value = 1;
args[2].integer.value = brightness ? 0 : 1;
} else {
return -EINVAL;
mutex_lock(&huawei->wmi_lock);
status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out);
mutex_unlock(&huawei->wmi_lock);
if (ACPI_FAILURE(status)) {
dev_err(huawei->dev, "Failed to evaluate wmi method\n");
return -ENODEV;
}
status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL);
if (ACPI_FAILURE(status))
return -ENXIO;
return 0;
}
static int huawei_wmi_leds_setup(struct wmi_device *wdev)
/* HWMI takes a 64 bit input and returns either a package with 2 buffers, one of
* 4 bytes and the other of 256 bytes, or one buffer of size 0x104 (260) bytes.
* The first 4 bytes are ignored, we ignore the first 4 bytes buffer if we got a
* package, or skip the first 4 if a buffer of 0x104 is used. The first byte of
* the remaining 0x100 sized buffer has the return status of every call. In case
* the return status is non-zero, we return -ENODEV but still copy the returned
* buffer to the given buffer parameter (buf).
*/
static int huawei_wmi_cmd(u64 arg, u8 *buf, size_t buflen)
{
struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
struct huawei_wmi *huawei = huawei_wmi;
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer in;
union acpi_object *obj;
size_t len;
int err, i;
priv->handle = ec_get_handle();
if (!priv->handle)
return 0;
in.length = sizeof(arg);
in.pointer = &arg;
if (acpi_has_method(priv->handle, "SPIN"))
priv->acpi_method = "SPIN";
else if (acpi_has_method(priv->handle, "WPIN"))
priv->acpi_method = "WPIN";
else
return 0;
/* Some models require calling HWMI twice to execute a command. We evaluate
* HWMI and if we get a non-zero return status we evaluate it again.
*/
for (i = 0; i < 2; i++) {
err = huawei_wmi_call(huawei, &in, &out);
if (err)
goto fail_cmd;
priv->cdev.name = "platform::micmute";
priv->cdev.max_brightness = 1;
priv->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set;
priv->cdev.default_trigger = "audio-micmute";
priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
priv->cdev.dev = &wdev->dev;
priv->cdev.flags = LED_CORE_SUSPENDRESUME;
obj = out.pointer;
if (!obj) {
err = -EIO;
goto fail_cmd;
}
return devm_led_classdev_register(&wdev->dev, &priv->cdev);
switch (obj->type) {
/* Models that implement both "legacy" and HWMI tend to return a 0x104
* sized buffer instead of a package of 0x4 and 0x100 buffers.
*/
case ACPI_TYPE_BUFFER:
if (obj->buffer.length == 0x104) {
// Skip the first 4 bytes.
obj->buffer.pointer += 4;
len = 0x100;
} else {
dev_err(huawei->dev, "Bad buffer length, got %d\n", obj->buffer.length);
err = -EIO;
goto fail_cmd;
}
break;
/* HWMI returns a package with 2 buffer elements, one of 4 bytes and the
* other is 256 bytes.
*/
case ACPI_TYPE_PACKAGE:
if (obj->package.count != 2) {
dev_err(huawei->dev, "Bad package count, got %d\n", obj->package.count);
err = -EIO;
goto fail_cmd;
}
obj = &obj->package.elements[1];
if (obj->type != ACPI_TYPE_BUFFER) {
dev_err(huawei->dev, "Bad package element type, got %d\n", obj->type);
err = -EIO;
goto fail_cmd;
}
len = obj->buffer.length;
break;
/* Shouldn't get here! */
default:
dev_err(huawei->dev, "Unexpected obj type, got: %d\n", obj->type);
err = -EIO;
goto fail_cmd;
}
if (!*obj->buffer.pointer)
break;
}
err = (*obj->buffer.pointer) ? -ENODEV : 0;
if (buf) {
len = min(buflen, len);
memcpy(buf, obj->buffer.pointer, len);
}
fail_cmd:
kfree(out.pointer);
return err;
}
static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
/* LEDs */
static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
/* This is a workaround until the "legacy" interface is implemented. */
if (quirks && quirks->ec_micmute) {
char *acpi_method;
acpi_handle handle;
acpi_status status;
union acpi_object args[3];
struct acpi_object_list arg_list = {
.pointer = args,
.count = ARRAY_SIZE(args),
};
handle = ec_get_handle();
if (!handle)
return -ENODEV;
args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
args[1].integer.value = 0x04;
if (acpi_has_method(handle, "SPIN")) {
acpi_method = "SPIN";
args[0].integer.value = 0;
args[2].integer.value = brightness ? 1 : 0;
} else if (acpi_has_method(handle, "WPIN")) {
acpi_method = "WPIN";
args[0].integer.value = 1;
args[2].integer.value = brightness ? 0 : 1;
} else {
return -ENODEV;
}
status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL);
if (ACPI_FAILURE(status))
return -ENODEV;
return 0;
} else {
union hwmi_arg arg;
arg.cmd = MICMUTE_LED_SET;
arg.args[2] = brightness;
return huawei_wmi_cmd(arg.cmd, NULL, 0);
}
}
static void huawei_wmi_leds_setup(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);
huawei->cdev.name = "platform::micmute";
huawei->cdev.max_brightness = 1;
huawei->cdev.brightness_set_blocking = &huawei_wmi_micmute_led_set;
huawei->cdev.default_trigger = "audio-micmute";
huawei->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
huawei->cdev.dev = dev;
huawei->cdev.flags = LED_CORE_SUSPENDRESUME;
devm_led_classdev_register(dev, &huawei->cdev);
}
/* Battery protection */
static int huawei_wmi_battery_get(int *start, int *end)
{
u8 ret[0x100];
int err, i;
err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, 0x100);
if (err)
return err;
/* Find the last two non-zero values. Return status is ignored. */
i = 0xff;
do {
if (start)
*start = ret[i-1];
if (end)
*end = ret[i];
} while (i > 2 && !ret[i--]);
return 0;
}
static int huawei_wmi_battery_set(int start, int end)
{
union hwmi_arg arg;
int err;
if (start < 0 || end < 0 || start > 100 || end > 100)
return -EINVAL;
arg.cmd = BATTERY_THRESH_SET;
arg.args[2] = start;
arg.args[3] = end;
/* This is an edge case were some models turn battery protection
* off without changing their thresholds values. We clear the
* values before turning off protection. Sometimes we need a sleep delay to
* make sure these values make their way to EC memory.
*/
if (quirks && quirks->battery_reset && start == 0 && end == 100) {
err = huawei_wmi_battery_set(0, 0);
if (err)
return err;
msleep(1000);
}
err = huawei_wmi_cmd(arg.cmd, NULL, 0);
return err;
}
static ssize_t charge_control_start_threshold_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int err, start;
err = huawei_wmi_battery_get(&start, NULL);
if (err)
return err;
return sprintf(buf, "%d\n", start);
}
static ssize_t charge_control_end_threshold_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int err, end;
err = huawei_wmi_battery_get(NULL, &end);
if (err)
return err;
return sprintf(buf, "%d\n", end);
}
static ssize_t charge_control_thresholds_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int err, start, end;
err = huawei_wmi_battery_get(&start, &end);
if (err)
return err;
return sprintf(buf, "%d %d\n", start, end);
}
static ssize_t charge_control_start_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err, start, end;
err = huawei_wmi_battery_get(NULL, &end);
if (err)
return err;
if (sscanf(buf, "%d", &start) != 1)
return -EINVAL;
err = huawei_wmi_battery_set(start, end);
if (err)
return err;
return size;
}
static ssize_t charge_control_end_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err, start, end;
err = huawei_wmi_battery_get(&start, NULL);
if (err)
return err;
if (sscanf(buf, "%d", &end) != 1)
return -EINVAL;
err = huawei_wmi_battery_set(start, end);
if (err)
return err;
return size;
}
static ssize_t charge_control_thresholds_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err, start, end;
if (sscanf(buf, "%d %d", &start, &end) != 2)
return -EINVAL;
err = huawei_wmi_battery_set(start, end);
if (err)
return err;
return size;
}
static DEVICE_ATTR_RW(charge_control_start_threshold);
static DEVICE_ATTR_RW(charge_control_end_threshold);
static DEVICE_ATTR_RW(charge_control_thresholds);
static int huawei_wmi_battery_add(struct power_supply *battery)
{
device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);
device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);
return 0;
}
static int huawei_wmi_battery_remove(struct power_supply *battery)
{
device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);
device_remove_file(&battery->dev, &dev_attr_charge_control_end_threshold);
return 0;
}
static struct acpi_battery_hook huawei_wmi_battery_hook = {
.add_battery = huawei_wmi_battery_add,
.remove_battery = huawei_wmi_battery_remove,
.name = "Huawei Battery Extension"
};
static void huawei_wmi_battery_setup(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);
huawei->battery_available = true;
if (huawei_wmi_battery_get(NULL, NULL)) {
huawei->battery_available = false;
return;
}
battery_hook_register(&huawei_wmi_battery_hook);
device_create_file(dev, &dev_attr_charge_control_thresholds);
}
static void huawei_wmi_battery_exit(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);
if (huawei->battery_available) {
battery_hook_unregister(&huawei_wmi_battery_hook);
device_remove_file(dev, &dev_attr_charge_control_thresholds);
}
}
/* Fn lock */
static int huawei_wmi_fn_lock_get(int *on)
{
u8 ret[0x100] = { 0 };
int err, i;
err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100);
if (err)
return err;
/* Find the first non-zero value. Return status is ignored. */
i = 1;
do {
if (on)
*on = ret[i] - 1; // -1 undefined, 0 off, 1 on.
} while (i < 0xff && !ret[i++]);
return 0;
}
static int huawei_wmi_fn_lock_set(int on)
{
union hwmi_arg arg;
arg.cmd = FN_LOCK_SET;
arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on.
return huawei_wmi_cmd(arg.cmd, NULL, 0);
}
static ssize_t fn_lock_state_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int err, on;
err = huawei_wmi_fn_lock_get(&on);
if (err)
return err;
return sprintf(buf, "%d\n", on);
}
static ssize_t fn_lock_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int on, err;
if (kstrtoint(buf, 10, &on) ||
on < 0 || on > 1)
return -EINVAL;
err = huawei_wmi_fn_lock_set(on);
if (err)
return err;
return size;
}
static DEVICE_ATTR_RW(fn_lock_state);
static void huawei_wmi_fn_lock_setup(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);
huawei->fn_lock_available = true;
if (huawei_wmi_fn_lock_get(NULL)) {
huawei->fn_lock_available = false;
return;
}
device_create_file(dev, &dev_attr_fn_lock_state);
}
static void huawei_wmi_fn_lock_exit(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);
if (huawei->fn_lock_available)
device_remove_file(dev, &dev_attr_fn_lock_state);
}
/* debugfs */
static void huawei_wmi_debugfs_call_dump(struct seq_file *m, void *data,
union acpi_object *obj)
{
struct huawei_wmi *huawei = m->private;
int i;
switch (obj->type) {
case ACPI_TYPE_INTEGER:
seq_printf(m, "0x%llx", obj->integer.value);
break;
case ACPI_TYPE_STRING:
seq_printf(m, "\"%.*s\"", obj->string.length, obj->string.pointer);
break;
case ACPI_TYPE_BUFFER:
seq_puts(m, "{");
for (i = 0; i < obj->buffer.length; i++) {
seq_printf(m, "0x%02x", obj->buffer.pointer[i]);
if (i < obj->buffer.length - 1)
seq_puts(m, ",");
}
seq_puts(m, "}");
break;
case ACPI_TYPE_PACKAGE:
seq_puts(m, "[");
for (i = 0; i < obj->package.count; i++) {
huawei_wmi_debugfs_call_dump(m, huawei, &obj->package.elements[i]);
if (i < obj->package.count - 1)
seq_puts(m, ",");
}
seq_puts(m, "]");
break;
default:
dev_err(huawei->dev, "Unexpected obj type, got %d\n", obj->type);
return;
}
}
static int huawei_wmi_debugfs_call_show(struct seq_file *m, void *data)
{
struct huawei_wmi *huawei = m->private;
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer in;
union acpi_object *obj;
int err;
in.length = sizeof(u64);
in.pointer = &huawei->debug.arg;
err = huawei_wmi_call(huawei, &in, &out);
if (err)
return err;
obj = out.pointer;
if (!obj) {
err = -EIO;
goto fail_debugfs_call;
}
huawei_wmi_debugfs_call_dump(m, huawei, obj);
fail_debugfs_call:
kfree(out.pointer);
return err;
}
DEFINE_SHOW_ATTRIBUTE(huawei_wmi_debugfs_call);
static void huawei_wmi_debugfs_setup(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);
huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL);
debugfs_create_x64("arg", 0644, huawei->debug.root,
&huawei->debug.arg);
debugfs_create_file("call", 0400,
huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops);
}
static void huawei_wmi_debugfs_exit(struct device *dev)
{
struct huawei_wmi *huawei = dev_get_drvdata(dev);
debugfs_remove_recursive(huawei->debug.root);
}
/* Input */
static void huawei_wmi_process_key(struct input_dev *idev, int code)
{
struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
const struct key_entry *key;
/*
@@ -127,81 +713,187 @@ static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
kfree(response.pointer);
}
key = sparse_keymap_entry_from_scancode(priv->idev, code);
key = sparse_keymap_entry_from_scancode(idev, code);
if (!key) {
dev_info(&wdev->dev, "Unknown key pressed, code: 0x%04x\n", code);
dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n", code);
return;
}
sparse_keymap_report_entry(priv->idev, key, 1, true);
if (quirks && !quirks->report_brightness &&
(key->sw.code == KEY_BRIGHTNESSDOWN ||
key->sw.code == KEY_BRIGHTNESSUP))
return;
sparse_keymap_report_entry(idev, key, 1, true);
}
static void huawei_wmi_notify(struct wmi_device *wdev,
union acpi_object *obj)
static void huawei_wmi_input_notify(u32 value, void *context)
{
if (obj->type == ACPI_TYPE_INTEGER)
huawei_wmi_process_key(wdev, obj->integer.value);
struct input_dev *idev = (struct input_dev *)context;
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
acpi_status status;
status = wmi_get_event_data(value, &response);
if (ACPI_FAILURE(status)) {
dev_err(&idev->dev, "Unable to get event data\n");
return;
}
obj = (union acpi_object *)response.pointer;
if (obj && obj->type == ACPI_TYPE_INTEGER)
huawei_wmi_process_key(idev, obj->integer.value);
else
dev_info(&wdev->dev, "Bad response type %d\n", obj->type);
dev_err(&idev->dev, "Bad response type\n");
kfree(response.pointer);
}
static int huawei_wmi_input_setup(struct wmi_device *wdev)
static int huawei_wmi_input_setup(struct device *dev,
const char *guid,
struct input_dev **idev)
{
struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
int err;
priv->idev = devm_input_allocate_device(&wdev->dev);
if (!priv->idev)
*idev = devm_input_allocate_device(dev);
if (!*idev)
return -ENOMEM;
priv->idev->name = "Huawei WMI hotkeys";
priv->idev->phys = "wmi/input0";
priv->idev->id.bustype = BUS_HOST;
priv->idev->dev.parent = &wdev->dev;
(*idev)->name = "Huawei WMI hotkeys";
(*idev)->phys = "wmi/input0";
(*idev)->id.bustype = BUS_HOST;
(*idev)->dev.parent = dev;
err = sparse_keymap_setup(priv->idev, huawei_wmi_keymap, NULL);
if (err)
return err;
return input_register_device(priv->idev);
return sparse_keymap_setup(*idev, huawei_wmi_keymap, NULL) ||
input_register_device(*idev) ||
wmi_install_notify_handler(guid, huawei_wmi_input_notify,
*idev);
}
static int huawei_wmi_probe(struct wmi_device *wdev, const void *context)
static void huawei_wmi_input_exit(struct device *dev, const char *guid)
{
struct huawei_wmi_priv *priv;
int err;
priv = devm_kzalloc(&wdev->dev, sizeof(struct huawei_wmi_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dev_set_drvdata(&wdev->dev, priv);
err = huawei_wmi_input_setup(wdev);
if (err)
return err;
return huawei_wmi_leds_setup(wdev);
wmi_remove_notify_handler(guid);
}
static const struct wmi_device_id huawei_wmi_id_table[] = {
/* Huawei driver */
static const struct wmi_device_id huawei_wmi_events_id_table[] = {
{ .guid_string = WMI0_EVENT_GUID },
{ .guid_string = AMW0_EVENT_GUID },
{ .guid_string = HWMI_EVENT_GUID },
{ }
};
static struct wmi_driver huawei_wmi_driver = {
static int huawei_wmi_probe(struct platform_device *pdev)
{
const struct wmi_device_id *guid = huawei_wmi_events_id_table;
int err;
platform_set_drvdata(pdev, huawei_wmi);
huawei_wmi->dev = &pdev->dev;
while (*guid->guid_string) {
struct input_dev *idev = *huawei_wmi->idev;
if (wmi_has_guid(guid->guid_string)) {
err = huawei_wmi_input_setup(&pdev->dev, guid->guid_string, &idev);
if (err) {
dev_err(&pdev->dev, "Failed to setup input on %s\n", guid->guid_string);
return err;
}
}
idev++;
guid++;
}
if (wmi_has_guid(HWMI_METHOD_GUID)) {
mutex_init(&huawei_wmi->wmi_lock);
huawei_wmi_leds_setup(&pdev->dev);
huawei_wmi_fn_lock_setup(&pdev->dev);
huawei_wmi_battery_setup(&pdev->dev);
huawei_wmi_debugfs_setup(&pdev->dev);
}
return 0;
}
static int huawei_wmi_remove(struct platform_device *pdev)
{
const struct wmi_device_id *guid = huawei_wmi_events_id_table;
while (*guid->guid_string) {
if (wmi_has_guid(guid->guid_string))
huawei_wmi_input_exit(&pdev->dev, guid->guid_string);
guid++;
}
if (wmi_has_guid(HWMI_METHOD_GUID)) {
huawei_wmi_debugfs_exit(&pdev->dev);
huawei_wmi_battery_exit(&pdev->dev);
huawei_wmi_fn_lock_exit(&pdev->dev);
}
return 0;
}
static struct platform_driver huawei_wmi_driver = {
.driver = {
.name = "huawei-wmi",
},
.id_table = huawei_wmi_id_table,
.probe = huawei_wmi_probe,
.notify = huawei_wmi_notify,
.remove = huawei_wmi_remove,
};
module_wmi_driver(huawei_wmi_driver);
static __init int huawei_wmi_init(void)
{
struct platform_device *pdev;
int err;
MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table);
huawei_wmi = kzalloc(sizeof(struct huawei_wmi), GFP_KERNEL);
if (!huawei_wmi)
return -ENOMEM;
quirks = &quirk_unknown;
dmi_check_system(huawei_quirks);
if (battery_reset != -1)
quirks->battery_reset = battery_reset;
if (report_brightness != -1)
quirks->report_brightness = report_brightness;
err = platform_driver_register(&huawei_wmi_driver);
if (err)
goto pdrv_err;
pdev = platform_device_register_simple("huawei-wmi", -1, NULL, 0);
if (IS_ERR(pdev)) {
err = PTR_ERR(pdev);
goto pdev_err;
}
return 0;
pdev_err:
platform_driver_unregister(&huawei_wmi_driver);
pdrv_err:
kfree(huawei_wmi);
return err;
}
static __exit void huawei_wmi_exit(void)
{
struct platform_device *pdev = to_platform_device(huawei_wmi->dev);
platform_device_unregister(pdev);
platform_driver_unregister(&huawei_wmi_driver);
kfree(huawei_wmi);
}
module_init(huawei_wmi_init);
module_exit(huawei_wmi_exit);
MODULE_ALIAS("wmi:"HWMI_METHOD_GUID);
MODULE_DEVICE_TABLE(wmi, huawei_wmi_events_id_table);
MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");
MODULE_DESCRIPTION("Huawei WMI hotkeys");
MODULE_DESCRIPTION("Huawei WMI laptop extras driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers
* (USB Micro-B and Type-C connector variants).
*
* Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "intel_cht_int33fe_common.h"
#define EXPECTED_PTYPE 4
static int cht_int33fe_i2c_res_filter(struct acpi_resource *ares, void *data)
{
struct acpi_resource_i2c_serialbus *sb;
int *count = data;
if (i2c_acpi_get_i2c_resource(ares, &sb))
(*count)++;
return 1;
}
static int cht_int33fe_count_i2c_clients(struct device *dev)
{
struct acpi_device *adev;
LIST_HEAD(resource_list);
int count = 0;
adev = ACPI_COMPANION(dev);
if (!adev)
return -EINVAL;
acpi_dev_get_resources(adev, &resource_list,
cht_int33fe_i2c_res_filter, &count);
acpi_dev_free_resource_list(&resource_list);
return count;
}
static int cht_int33fe_check_hw_type(struct device *dev)
{
unsigned long long ptyp;
acpi_status status;
int ret;
status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp);
if (ACPI_FAILURE(status)) {
dev_err(dev, "Error getting PTYPE\n");
return -ENODEV;
}
/*
* The same ACPI HID is used for different configurations check PTYP
* to ensure that we are dealing with the expected config.
*/
if (ptyp != EXPECTED_PTYPE)
return -ENODEV;
/* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */
if (!acpi_dev_present("INT34D3", "1", 3)) {
dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n",
EXPECTED_PTYPE);
return -ENODEV;
}
ret = cht_int33fe_count_i2c_clients(dev);
if (ret < 0)
return ret;
switch (ret) {
case 2:
return INT33FE_HW_MICROB;
case 4:
return INT33FE_HW_TYPEC;
default:
return -ENODEV;
}
}
static int cht_int33fe_probe(struct platform_device *pdev)
{
struct cht_int33fe_data *data;
struct device *dev = &pdev->dev;
int ret;
ret = cht_int33fe_check_hw_type(dev);
if (ret < 0)
return ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->dev = dev;
switch (ret) {
case INT33FE_HW_MICROB:
data->probe = cht_int33fe_microb_probe;
data->remove = cht_int33fe_microb_remove;
break;
case INT33FE_HW_TYPEC:
data->probe = cht_int33fe_typec_probe;
data->remove = cht_int33fe_typec_remove;
break;
}
platform_set_drvdata(pdev, data);
return data->probe(data);
}
static int cht_int33fe_remove(struct platform_device *pdev)
{
struct cht_int33fe_data *data = platform_get_drvdata(pdev);
return data->remove(data);
}
static const struct acpi_device_id cht_int33fe_acpi_ids[] = {
{ "INT33FE", },
{ }
};
MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids);
static struct platform_driver cht_int33fe_driver = {
.driver = {
.name = "Intel Cherry Trail ACPI INT33FE driver",
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
},
.probe = cht_int33fe_probe,
.remove = cht_int33fe_remove,
};
module_platform_driver(cht_int33fe_driver);
MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver");
MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers
* (USB Micro-B and Type-C connector variants), header file
*
* Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
*/
#ifndef _INTEL_CHT_INT33FE_COMMON_H
#define _INTEL_CHT_INT33FE_COMMON_H
#include <linux/device.h>
#include <linux/fwnode.h>
#include <linux/i2c.h>
enum int33fe_hw_type {
INT33FE_HW_MICROB,
INT33FE_HW_TYPEC,
};
struct cht_int33fe_data {
struct device *dev;
int (*probe)(struct cht_int33fe_data *data);
int (*remove)(struct cht_int33fe_data *data);
struct i2c_client *battery_fg;
/* Type-C only */
struct i2c_client *fusb302;
struct i2c_client *pi3usb30532;
struct fwnode_handle *dp;
};
int cht_int33fe_microb_probe(struct cht_int33fe_data *data);
int cht_int33fe_microb_remove(struct cht_int33fe_data *data);
int cht_int33fe_typec_probe(struct cht_int33fe_data *data);
int cht_int33fe_typec_remove(struct cht_int33fe_data *data);
#endif /* _INTEL_CHT_INT33FE_COMMON_H */

View File

@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with
* USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller)
*
* Copyright (C) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
*
* At least one Intel Cherry Trail based device which ship with Windows 10
* (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device
* with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips
* attached to various i2c busses:
* 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device
* 2. TI BQ27542 Fuel Gauge Controller
*
* So this driver is a stub / pseudo driver whose only purpose is to
* instantiate i2c-client for battery fuel gauge, so that standard i2c driver
* for these chip can bind to the it.
*/
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/usb/pd.h>
#include "intel_cht_int33fe_common.h"
static const char * const bq27xxx_suppliers[] = { "bq25890-charger" };
static const struct property_entry bq27xxx_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers),
{ }
};
int cht_int33fe_microb_probe(struct cht_int33fe_data *data)
{
struct device *dev = data->dev;
struct i2c_board_info board_info;
memset(&board_info, 0, sizeof(board_info));
strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type));
board_info.dev_name = "bq27542";
board_info.properties = bq27xxx_props;
data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info);
return PTR_ERR_OR_ZERO(data->battery_fg);
}
int cht_int33fe_microb_remove(struct cht_int33fe_data *data)
{
i2c_unregister_device(data->battery_fg);
return 0;
}

View File

@@ -17,17 +17,15 @@
* for these chips can bind to the them.
*/
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/usb/pd.h>
#define EXPECTED_PTYPE 4
#include "intel_cht_int33fe_common.h"
enum {
INT33FE_NODE_FUSB302,
@@ -38,14 +36,6 @@ enum {
INT33FE_NODE_MAX,
};
struct cht_int33fe_data {
struct i2c_client *max17047;
struct i2c_client *fusb302;
struct i2c_client *pi3usb30532;
struct fwnode_handle *dp;
};
static const struct software_node nodes[];
static const struct software_node_ref_args pi3usb30532_ref = {
@@ -251,43 +241,20 @@ cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data)
strlcpy(board_info.type, "max17047", I2C_NAME_SIZE);
board_info.dev_name = "max17047";
board_info.fwnode = fwnode;
data->max17047 = i2c_acpi_new_device(dev, 1, &board_info);
data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info);
return PTR_ERR_OR_ZERO(data->max17047);
return PTR_ERR_OR_ZERO(data->battery_fg);
}
static int cht_int33fe_probe(struct platform_device *pdev)
int cht_int33fe_typec_probe(struct cht_int33fe_data *data)
{
struct device *dev = &pdev->dev;
struct device *dev = data->dev;
struct i2c_board_info board_info;
struct cht_int33fe_data *data;
struct fwnode_handle *fwnode;
struct regulator *regulator;
unsigned long long ptyp;
acpi_status status;
int fusb302_irq;
int ret;
status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp);
if (ACPI_FAILURE(status)) {
dev_err(dev, "Error getting PTYPE\n");
return -ENODEV;
}
/*
* The same ACPI HID is used for different configurations check PTYP
* to ensure that we are dealing with the expected config.
*/
if (ptyp != EXPECTED_PTYPE)
return -ENODEV;
/* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */
if (!acpi_dev_present("INT34D3", "1", 3)) {
dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n",
EXPECTED_PTYPE);
return -ENODEV;
}
/*
* We expect the WC PMIC to be paired with a TI bq24292i charger-IC.
* We check for the bq24292i vbus regulator here, this has 2 purposes:
@@ -317,10 +284,6 @@ static int cht_int33fe_probe(struct platform_device *pdev)
return fusb302_irq;
}
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = cht_int33fe_add_nodes(data);
if (ret)
return ret;
@@ -365,15 +328,13 @@ static int cht_int33fe_probe(struct platform_device *pdev)
goto out_unregister_fusb302;
}
platform_set_drvdata(pdev, data);
return 0;
out_unregister_fusb302:
i2c_unregister_device(data->fusb302);
out_unregister_max17047:
i2c_unregister_device(data->max17047);
i2c_unregister_device(data->battery_fg);
out_remove_nodes:
cht_int33fe_remove_nodes(data);
@@ -381,36 +342,13 @@ out_remove_nodes:
return ret;
}
static int cht_int33fe_remove(struct platform_device *pdev)
int cht_int33fe_typec_remove(struct cht_int33fe_data *data)
{
struct cht_int33fe_data *data = platform_get_drvdata(pdev);
i2c_unregister_device(data->pi3usb30532);
i2c_unregister_device(data->fusb302);
i2c_unregister_device(data->max17047);
i2c_unregister_device(data->battery_fg);
cht_int33fe_remove_nodes(data);
return 0;
}
static const struct acpi_device_id cht_int33fe_acpi_ids[] = {
{ "INT33FE", },
{ }
};
MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids);
static struct platform_driver cht_int33fe_driver = {
.driver = {
.name = "Intel Cherry Trail ACPI INT33FE driver",
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
},
.probe = cht_int33fe_probe,
.remove = cht_int33fe_remove,
};
module_platform_driver(cht_int33fe_driver);
MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver");
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_LICENSE("GPL v2");

View File

@@ -164,8 +164,8 @@ static int int0002_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct x86_cpu_id *cpu_id;
struct irq_chip *irq_chip;
struct gpio_chip *chip;
struct gpio_irq_chip *girq;
int irq, ret;
/* Menlow has a different INT0002 device? <sigh> */
@@ -192,15 +192,11 @@ static int int0002_probe(struct platform_device *pdev)
chip->ngpio = GPE0A_PME_B0_VIRT_GPIO_PIN + 1;
chip->irq.init_valid_mask = int0002_init_irq_valid_mask;
ret = devm_gpiochip_add_data(&pdev->dev, chip, NULL);
if (ret) {
dev_err(dev, "Error adding gpio chip: %d\n", ret);
return ret;
}
/*
* We manually request the irq here instead of passing a flow-handler
* We directly request the irq here instead of passing a flow-handler
* to gpiochip_set_chained_irqchip, because the irq is shared.
* FIXME: augment this if we managed to pull handling of shared
* IRQs into gpiolib.
*/
ret = devm_request_irq(dev, irq, int0002_irq,
IRQF_SHARED, "INT0002", chip);
@@ -209,17 +205,21 @@ static int int0002_probe(struct platform_device *pdev)
return ret;
}
irq_chip = (struct irq_chip *)cpu_id->driver_data;
girq = &chip->irq;
girq->chip = (struct irq_chip *)cpu_id->driver_data;
/* This let us handle the parent IRQ in the driver */
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_edge_irq;
ret = gpiochip_irqchip_add(chip, irq_chip, 0, handle_edge_irq,
IRQ_TYPE_NONE);
ret = devm_gpiochip_add_data(dev, chip, NULL);
if (ret) {
dev_err(dev, "Error adding irqchip: %d\n", ret);
dev_err(dev, "Error adding gpio chip: %d\n", ret);
return ret;
}
gpiochip_set_chained_irqchip(chip, irq_chip, irq, NULL);
device_init_wakeup(dev, true);
return 0;
}

View File

@@ -158,8 +158,9 @@ static const struct pmc_reg_map spt_reg_map = {
.pm_vric1_offset = SPT_PMC_VRIC1_OFFSET,
};
/* Cannonlake: PGD PFET Enable Ack Status Register(s) bitmap */
/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */
static const struct pmc_bit_map cnp_pfear_map[] = {
/* Reserved for Cannon Lake but valid for Comet Lake */
{"PMC", BIT(0)},
{"OPI-DMI", BIT(1)},
{"SPI/eSPI", BIT(2)},
@@ -185,7 +186,7 @@ static const struct pmc_bit_map cnp_pfear_map[] = {
{"SDX", BIT(4)},
{"SPE", BIT(5)},
{"Fuse", BIT(6)},
/* Reserved for Cannonlake but valid for Icelake */
/* Reserved for Cannon Lake but valid for Ice Lake and Comet Lake */
{"SBR8", BIT(7)},
{"CSME_FSC", BIT(0)},
@@ -229,12 +230,12 @@ static const struct pmc_bit_map cnp_pfear_map[] = {
{"HDA_PGD4", BIT(2)},
{"HDA_PGD5", BIT(3)},
{"HDA_PGD6", BIT(4)},
/* Reserved for Cannonlake but valid for Icelake */
/* Reserved for Cannon Lake but valid for Ice Lake and Comet Lake */
{"PSF6", BIT(5)},
{"PSF7", BIT(6)},
{"PSF8", BIT(7)},
/* Icelake generation onwards only */
/* Ice Lake generation onwards only */
{"RES_65", BIT(0)},
{"RES_66", BIT(1)},
{"RES_67", BIT(2)},
@@ -324,7 +325,7 @@ static const struct pmc_bit_map cnp_ltr_show_map[] = {
{"ISH", CNP_PMC_LTR_ISH},
{"UFSX2", CNP_PMC_LTR_UFSX2},
{"EMMC", CNP_PMC_LTR_EMMC},
/* Reserved for Cannonlake but valid for Icelake */
/* Reserved for Cannon Lake but valid for Ice Lake */
{"WIGIG", ICL_PMC_LTR_WIGIG},
/* Below two cannot be used for LTR_IGNORE */
{"CURRENT_PLATFORM", CNP_PMC_LTR_CUR_PLT},
@@ -813,6 +814,8 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
INTEL_CPU_FAM6(CANNONLAKE_L, cnp_reg_map),
INTEL_CPU_FAM6(ICELAKE_L, icl_reg_map),
INTEL_CPU_FAM6(ICELAKE_NNPI, icl_reg_map),
INTEL_CPU_FAM6(COMETLAKE, cnp_reg_map),
INTEL_CPU_FAM6(COMETLAKE_L, cnp_reg_map),
{}
};
@@ -871,8 +874,8 @@ static int pmc_core_probe(struct platform_device *pdev)
pmcdev->map = (struct pmc_reg_map *)cpu_id->driver_data;
/*
* Coffeelake has CPU ID of Kabylake and Cannonlake PCH. So here
* Sunrisepoint PCH regmap can't be used. Use Cannonlake PCH regmap
* Coffee Lake has CPU ID of Kaby Lake and Cannon Lake PCH. So here
* Sunrisepoint PCH regmap can't be used. Use Cannon Lake PCH regmap
* in this case.
*/
if (pmcdev->map == &spt_reg_map && !pci_dev_present(pmc_pci_ids))

View File

@@ -224,7 +224,6 @@ static irqreturn_t intel_punit_ioc(int irq, void *dev_id)
static int intel_punit_get_bars(struct platform_device *pdev)
{
struct resource *res;
void __iomem *addr;
/*
@@ -232,14 +231,12 @@ static int intel_punit_get_bars(struct platform_device *pdev)
* - BIOS_IPC BASE_DATA
* - BIOS_IPC BASE_IFACE
*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
addr = devm_ioremap_resource(&pdev->dev, res);
addr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
addr = devm_ioremap_resource(&pdev->dev, res);
addr = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(addr))
return PTR_ERR(addr);
punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr;
@@ -251,33 +248,21 @@ static int intel_punit_get_bars(struct platform_device *pdev)
* - GTDRIVER_IPC BASE_DATA
* - GTDRIVER_IPC BASE_IFACE
*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
if (res) {
addr = devm_ioremap_resource(&pdev->dev, res);
if (!IS_ERR(addr))
punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr;
}
addr = devm_platform_ioremap_resource(pdev, 2);
if (!IS_ERR(addr))
punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
if (res) {
addr = devm_ioremap_resource(&pdev->dev, res);
if (!IS_ERR(addr))
punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr;
}
addr = devm_platform_ioremap_resource(pdev, 3);
if (!IS_ERR(addr))
punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
if (res) {
addr = devm_ioremap_resource(&pdev->dev, res);
if (!IS_ERR(addr))
punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr;
}
addr = devm_platform_ioremap_resource(pdev, 4);
if (!IS_ERR(addr))
punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr;
res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
if (res) {
addr = devm_ioremap_resource(&pdev->dev, res);
if (!IS_ERR(addr))
punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr;
}
addr = devm_platform_ioremap_resource(pdev, 5);
if (!IS_ERR(addr))
punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr;
return 0;
}
@@ -309,14 +294,13 @@ static int intel_punit_ipc_probe(struct platform_device *pdev)
ret = intel_punit_get_bars(pdev);
if (ret)
goto out;
return ret;
punit_ipcdev->dev = &pdev->dev;
mutex_init(&punit_ipcdev->lock);
init_completion(&punit_ipcdev->cmd_complete);
out:
return ret;
return 0;
}
static int intel_punit_ipc_remove(struct platform_device *pdev)

View File

@@ -6,7 +6,7 @@
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/input-polldev.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -18,8 +18,7 @@
MODULE_ALIAS("wmi:"PEAQ_DOLBY_BUTTON_GUID);
static unsigned int peaq_ignore_events_counter;
static struct input_polled_dev *peaq_poll_dev;
static struct input_dev *peaq_poll_dev;
/*
* The Dolby button (yes really a Dolby button) causes an ACPI variable to get
@@ -28,8 +27,10 @@ static struct input_polled_dev *peaq_poll_dev;
* (if polling after the release) or twice (polling between press and release).
* We ignore events for 0.5s after the first event to avoid reporting 2 presses.
*/
static void peaq_wmi_poll(struct input_polled_dev *dev)
static void peaq_wmi_poll(struct input_dev *input_dev)
{
static unsigned long last_event_time;
static bool had_events;
union acpi_object obj;
acpi_status status;
u32 dummy = 0;
@@ -44,22 +45,25 @@ static void peaq_wmi_poll(struct input_polled_dev *dev)
return;
if (obj.type != ACPI_TYPE_INTEGER) {
dev_err(&peaq_poll_dev->input->dev,
dev_err(&input_dev->dev,
"Error WMBC did not return an integer\n");
return;
}
if (peaq_ignore_events_counter && peaq_ignore_events_counter--)
if (!obj.integer.value)
return;
if (obj.integer.value) {
input_event(peaq_poll_dev->input, EV_KEY, KEY_SOUND, 1);
input_sync(peaq_poll_dev->input);
input_event(peaq_poll_dev->input, EV_KEY, KEY_SOUND, 0);
input_sync(peaq_poll_dev->input);
peaq_ignore_events_counter = max(1u,
PEAQ_POLL_IGNORE_MS / peaq_poll_dev->poll_interval);
}
if (had_events && time_before(jiffies, last_event_time +
msecs_to_jiffies(PEAQ_POLL_IGNORE_MS)))
return;
input_event(input_dev, EV_KEY, KEY_SOUND, 1);
input_sync(input_dev);
input_event(input_dev, EV_KEY, KEY_SOUND, 0);
input_sync(input_dev);
last_event_time = jiffies;
had_events = true;
}
/* Some other devices (Shuttle XS35) use the same WMI GUID for other purposes */
@@ -75,6 +79,8 @@ static const struct dmi_system_id peaq_dmi_table[] __initconst = {
static int __init peaq_wmi_init(void)
{
int err;
/* WMI GUID is not unique, also check for a DMI match */
if (!dmi_check_system(peaq_dmi_table))
return -ENODEV;
@@ -82,24 +88,36 @@ static int __init peaq_wmi_init(void)
if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID))
return -ENODEV;
peaq_poll_dev = input_allocate_polled_device();
peaq_poll_dev = input_allocate_device();
if (!peaq_poll_dev)
return -ENOMEM;
peaq_poll_dev->poll = peaq_wmi_poll;
peaq_poll_dev->poll_interval = PEAQ_POLL_INTERVAL_MS;
peaq_poll_dev->poll_interval_max = PEAQ_POLL_MAX_MS;
peaq_poll_dev->input->name = "PEAQ WMI hotkeys";
peaq_poll_dev->input->phys = "wmi/input0";
peaq_poll_dev->input->id.bustype = BUS_HOST;
input_set_capability(peaq_poll_dev->input, EV_KEY, KEY_SOUND);
peaq_poll_dev->name = "PEAQ WMI hotkeys";
peaq_poll_dev->phys = "wmi/input0";
peaq_poll_dev->id.bustype = BUS_HOST;
input_set_capability(peaq_poll_dev, EV_KEY, KEY_SOUND);
return input_register_polled_device(peaq_poll_dev);
err = input_setup_polling(peaq_poll_dev, peaq_wmi_poll);
if (err)
goto err_out;
input_set_poll_interval(peaq_poll_dev, PEAQ_POLL_INTERVAL_MS);
input_set_max_poll_interval(peaq_poll_dev, PEAQ_POLL_MAX_MS);
err = input_register_device(peaq_poll_dev);
if (err)
goto err_out;
return 0;
err_out:
input_free_device(peaq_poll_dev);
return err;
}
static void __exit peaq_wmi_exit(void)
{
input_unregister_polled_device(peaq_poll_dev);
input_unregister_device(peaq_poll_dev);
}
module_init(peaq_wmi_init);

View File

@@ -0,0 +1,384 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* System76 ACPI Driver
*
* Copyright (C) 2019 System76
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/pci_ids.h>
#include <linux/types.h>
struct system76_data {
struct acpi_device *acpi_dev;
struct led_classdev ap_led;
struct led_classdev kb_led;
enum led_brightness kb_brightness;
enum led_brightness kb_toggle_brightness;
int kb_color;
};
static const struct acpi_device_id device_ids[] = {
{"17761776", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, device_ids);
// Array of keyboard LED brightness levels
static const enum led_brightness kb_levels[] = {
48,
72,
96,
144,
192,
255
};
// Array of keyboard LED colors in 24-bit RGB format
static const int kb_colors[] = {
0xFFFFFF,
0x0000FF,
0xFF0000,
0xFF00FF,
0x00FF00,
0x00FFFF,
0xFFFF00
};
// Get a System76 ACPI device value by name
static int system76_get(struct system76_data *data, char *method)
{
acpi_handle handle;
acpi_status status;
unsigned long long ret = 0;
handle = acpi_device_handle(data->acpi_dev);
status = acpi_evaluate_integer(handle, method, NULL, &ret);
if (ACPI_SUCCESS(status))
return (int)ret;
else
return -1;
}
// Set a System76 ACPI device value by name
static int system76_set(struct system76_data *data, char *method, int value)
{
union acpi_object obj;
struct acpi_object_list obj_list;
acpi_handle handle;
acpi_status status;
obj.type = ACPI_TYPE_INTEGER;
obj.integer.value = value;
obj_list.count = 1;
obj_list.pointer = &obj;
handle = acpi_device_handle(data->acpi_dev);
status = acpi_evaluate_object(handle, method, &obj_list, NULL);
if (ACPI_SUCCESS(status))
return 0;
else
return -1;
}
// Get the airplane mode LED brightness
static enum led_brightness ap_led_get(struct led_classdev *led)
{
struct system76_data *data;
int value;
data = container_of(led, struct system76_data, ap_led);
value = system76_get(data, "GAPL");
if (value > 0)
return (enum led_brightness)value;
else
return LED_OFF;
}
// Set the airplane mode LED brightness
static void ap_led_set(struct led_classdev *led, enum led_brightness value)
{
struct system76_data *data;
data = container_of(led, struct system76_data, ap_led);
system76_set(data, "SAPL", value == LED_OFF ? 0 : 1);
}
// Get the last set keyboard LED brightness
static enum led_brightness kb_led_get(struct led_classdev *led)
{
struct system76_data *data;
data = container_of(led, struct system76_data, kb_led);
return data->kb_brightness;
}
// Set the keyboard LED brightness
static void kb_led_set(struct led_classdev *led, enum led_brightness value)
{
struct system76_data *data;
data = container_of(led, struct system76_data, kb_led);
data->kb_brightness = value;
system76_set(data, "SKBL", (int)data->kb_brightness);
}
// Get the last set keyboard LED color
static ssize_t kb_led_color_show(
struct device *dev,
struct device_attribute *dev_attr,
char *buf)
{
struct led_classdev *led;
struct system76_data *data;
led = (struct led_classdev *)dev->driver_data;
data = container_of(led, struct system76_data, kb_led);
return sprintf(buf, "%06X\n", data->kb_color);
}
// Set the keyboard LED color
static ssize_t kb_led_color_store(
struct device *dev,
struct device_attribute *dev_attr,
const char *buf,
size_t size)
{
struct led_classdev *led;
struct system76_data *data;
unsigned int val;
int ret;
led = (struct led_classdev *)dev->driver_data;
data = container_of(led, struct system76_data, kb_led);
ret = kstrtouint(buf, 16, &val);
if (ret)
return ret;
if (val > 0xFFFFFF)
return -EINVAL;
data->kb_color = (int)val;
system76_set(data, "SKBC", data->kb_color);
return size;
}
static const struct device_attribute kb_led_color_dev_attr = {
.attr = {
.name = "color",
.mode = 0644,
},
.show = kb_led_color_show,
.store = kb_led_color_store,
};
// Notify that the keyboard LED was changed by hardware
static void kb_led_notify(struct system76_data *data)
{
led_classdev_notify_brightness_hw_changed(
&data->kb_led,
data->kb_brightness
);
}
// Read keyboard LED brightness as set by hardware
static void kb_led_hotkey_hardware(struct system76_data *data)
{
int value;
value = system76_get(data, "GKBL");
if (value < 0)
return;
data->kb_brightness = value;
kb_led_notify(data);
}
// Toggle the keyboard LED
static void kb_led_hotkey_toggle(struct system76_data *data)
{
if (data->kb_brightness > 0) {
data->kb_toggle_brightness = data->kb_brightness;
kb_led_set(&data->kb_led, 0);
} else {
kb_led_set(&data->kb_led, data->kb_toggle_brightness);
}
kb_led_notify(data);
}
// Decrease the keyboard LED brightness
static void kb_led_hotkey_down(struct system76_data *data)
{
int i;
if (data->kb_brightness > 0) {
for (i = ARRAY_SIZE(kb_levels); i > 0; i--) {
if (kb_levels[i - 1] < data->kb_brightness) {
kb_led_set(&data->kb_led, kb_levels[i - 1]);
break;
}
}
} else {
kb_led_set(&data->kb_led, data->kb_toggle_brightness);
}
kb_led_notify(data);
}
// Increase the keyboard LED brightness
static void kb_led_hotkey_up(struct system76_data *data)
{
int i;
if (data->kb_brightness > 0) {
for (i = 0; i < ARRAY_SIZE(kb_levels); i++) {
if (kb_levels[i] > data->kb_brightness) {
kb_led_set(&data->kb_led, kb_levels[i]);
break;
}
}
} else {
kb_led_set(&data->kb_led, data->kb_toggle_brightness);
}
kb_led_notify(data);
}
// Cycle the keyboard LED color
static void kb_led_hotkey_color(struct system76_data *data)
{
int i;
if (data->kb_color < 0)
return;
if (data->kb_brightness > 0) {
for (i = 0; i < ARRAY_SIZE(kb_colors); i++) {
if (kb_colors[i] == data->kb_color)
break;
}
i += 1;
if (i >= ARRAY_SIZE(kb_colors))
i = 0;
data->kb_color = kb_colors[i];
system76_set(data, "SKBC", data->kb_color);
} else {
kb_led_set(&data->kb_led, data->kb_toggle_brightness);
}
kb_led_notify(data);
}
// Handle ACPI notification
static void system76_notify(struct acpi_device *acpi_dev, u32 event)
{
struct system76_data *data;
data = acpi_driver_data(acpi_dev);
switch (event) {
case 0x80:
kb_led_hotkey_hardware(data);
break;
case 0x81:
kb_led_hotkey_toggle(data);
break;
case 0x82:
kb_led_hotkey_down(data);
break;
case 0x83:
kb_led_hotkey_up(data);
break;
case 0x84:
kb_led_hotkey_color(data);
break;
}
}
// Add a System76 ACPI device
static int system76_add(struct acpi_device *acpi_dev)
{
struct system76_data *data;
int err;
data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
acpi_dev->driver_data = data;
data->acpi_dev = acpi_dev;
err = system76_get(data, "INIT");
if (err)
return err;
data->ap_led.name = "system76_acpi::airplane";
data->ap_led.flags = LED_CORE_SUSPENDRESUME;
data->ap_led.brightness_get = ap_led_get;
data->ap_led.brightness_set = ap_led_set;
data->ap_led.max_brightness = 1;
data->ap_led.default_trigger = "rfkill-none";
err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led);
if (err)
return err;
data->kb_led.name = "system76_acpi::kbd_backlight";
data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME;
data->kb_led.brightness_get = kb_led_get;
data->kb_led.brightness_set = kb_led_set;
if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) {
data->kb_led.max_brightness = 255;
data->kb_toggle_brightness = 72;
data->kb_color = 0xffffff;
system76_set(data, "SKBC", data->kb_color);
} else {
data->kb_led.max_brightness = 5;
data->kb_color = -1;
}
err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led);
if (err)
return err;
if (data->kb_color >= 0) {
err = device_create_file(
data->kb_led.dev,
&kb_led_color_dev_attr
);
if (err)
return err;
}
return 0;
}
// Remove a System76 ACPI device
static int system76_remove(struct acpi_device *acpi_dev)
{
struct system76_data *data;
data = acpi_driver_data(acpi_dev);
if (data->kb_color >= 0)
device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr);
devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led);
devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led);
system76_get(data, "FINI");
return 0;
}
static struct acpi_driver system76_driver = {
.name = "System76 ACPI Driver",
.class = "hotkey",
.ids = device_ids,
.ops = {
.add = system76_add,
.remove = system76_remove,
.notify = system76_notify,
},
};
module_acpi_driver(system76_driver);
MODULE_DESCRIPTION("System76 ACPI Driver");
MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>");
MODULE_LICENSE("GPL");

View File

@@ -310,6 +310,22 @@ static const struct ts_dmi_data jumper_ezpad_6_pro_b_data = {
.properties = jumper_ezpad_6_pro_b_props,
};
static const struct property_entry jumper_ezpad_6_m4_props[] = {
PROPERTY_ENTRY_U32("touchscreen-min-x", 35),
PROPERTY_ENTRY_U32("touchscreen-min-y", 15),
PROPERTY_ENTRY_U32("touchscreen-size-x", 1950),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1525),
PROPERTY_ENTRY_STRING("firmware-name", "gsl3692-jumper-ezpad-6-m4.fw"),
PROPERTY_ENTRY_U32("silead,max-fingers", 10),
PROPERTY_ENTRY_BOOL("silead,home-button"),
{ }
};
static const struct ts_dmi_data jumper_ezpad_6_m4_data = {
.acpi_name = "MSSL1680:00",
.properties = jumper_ezpad_6_m4_props,
};
static const struct property_entry jumper_ezpad_mini3_props[] = {
PROPERTY_ENTRY_U32("touchscreen-min-x", 23),
PROPERTY_ENTRY_U32("touchscreen-min-y", 16),
@@ -498,6 +514,24 @@ static const struct ts_dmi_data pov_mobii_wintab_p1006w_v10_data = {
.properties = pov_mobii_wintab_p1006w_v10_props,
};
static const struct property_entry schneider_sct101ctm_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1715),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1140),
PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"),
PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
PROPERTY_ENTRY_STRING("firmware-name",
"gsl1680-schneider-sct101ctm.fw"),
PROPERTY_ENTRY_U32("silead,max-fingers", 10),
PROPERTY_ENTRY_BOOL("silead,home-button"),
{ }
};
static const struct ts_dmi_data schneider_sct101ctm_data = {
.acpi_name = "MSSL1680:00",
.properties = schneider_sct101ctm_props,
};
static const struct property_entry teclast_x3_plus_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1980),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1500),
@@ -788,6 +822,16 @@ static const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_BIOS_DATE, "04/24/2018"),
},
},
{
/* Jumper EZpad 6 m4 */
.driver_data = (void *)&jumper_ezpad_6_m4_data,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "jumper"),
DMI_MATCH(DMI_PRODUCT_NAME, "EZpad"),
/* Jumper8.S106x.A00C.1066 with the version dropped */
DMI_MATCH(DMI_BIOS_VERSION, "Jumper8.S106x"),
},
},
{
/* Jumper EZpad mini3 */
.driver_data = (void *)&jumper_ezpad_mini3_data,
@@ -908,6 +952,14 @@ static const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_EXACT_MATCH(DMI_BOARD_NAME, "0E57"),
},
},
{
/* Schneider SCT101CTM */
.driver_data = (void *)&schneider_sct101ctm_data,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Default string"),
DMI_MATCH(DMI_PRODUCT_NAME, "SCT101CTM"),
},
},
{
/* Teclast X3 Plus */
.driver_data = (void *)&teclast_x3_plus_data,