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:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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*:");
|
||||
|
@@ -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 ;
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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");
|
||||
|
147
drivers/platform/x86/intel_cht_int33fe_common.c
Normal file
147
drivers/platform/x86/intel_cht_int33fe_common.c
Normal 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");
|
41
drivers/platform/x86/intel_cht_int33fe_common.h
Normal file
41
drivers/platform/x86/intel_cht_int33fe_common.h
Normal 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 */
|
57
drivers/platform/x86/intel_cht_int33fe_microb.c
Normal file
57
drivers/platform/x86/intel_cht_int33fe_microb.c
Normal 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;
|
||||
}
|
@@ -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");
|
@@ -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;
|
||||
}
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
384
drivers/platform/x86/system76_acpi.c
Normal file
384
drivers/platform/x86/system76_acpi.c
Normal 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");
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user