Commit 99a7583d authored by Linus Torvalds's avatar Linus Torvalds

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

Pull x86 platform-drivers update from Darren Hart:
 "This represents a significantly larger and more complex set of changes
  than those of prior merge windows.

  In particular, we had several changes with dependencies on other
  subsystems which we felt were best managed through merges of immutable
  branches, including one each from input, i2c, and leds. Two patches
  for the watchdog subsystem are included after discussion with Wim and
  Guenter following a collision in linux-next (this should be resolved
  and you should only see these two appear in this pull request). These
  are called out in the "External" section below.

  Summary of changes:
   - significant further cleanup of fujitsu-laptop and hp-wmi
   - new model support for ideapad, asus, silead, and xiaomi
   - new hotkeys for thinkpad and models using intel-vbtn
   - dell keyboard backlight improvements
   - build and dependency improvements
   - intel * ipc fixes, cleanups, and api updates
   - single isolated fixes noted below

  External:
   - watchdog: iTCO_wdt: Add PMC specific noreboot update api
   - watchdog: iTCO_wdt: cleanup set/unset no_reboot_bit functions
   - Merge branch 'ib/4.10-sparse-keymap-managed'
   - Merge branch 'i2c/for-INT33FE'
   - Merge branch 'linux-leds/dell-laptop-changes-for-4.12'

  platform/x86:
   - Add Intel Cherry Trail ACPI INT33FE device driver
   - remove sparse_keymap_free() calls
   - Make SILEAD_DMI depend on TOUCHSCREEN_SILEAD

  asus-wmi:
   - try to set als by default
   - fix cpufv sysfs file permission

  acer-wmi:
   - setup accelerometer when ACPI device was found

  ideapad-laptop:
   - Add IdeaPad V310-15ISK to no_hw_rfkill
   - Add IdeaPad 310-15IKB to no_hw_rfkill

  intel_pmc_ipc:
   - use gcr mem base for S0ix counter read
   - Fix iTCO_wdt GCS memory mapping failure
   - Add pmc gcr read/write/update api's
   - fix gcr offset

  dell-laptop:
   - Add keyboard backlight timeout AC settings
   - Handle return error form dell_get_intensity.
   - Protect kbd_state against races
   - Refactor kbd_led_triggers_store()

  hp-wireless:
   - reuse module_acpi_driver
   - add Xiaomi's hardware id to the supported list

  intel-vbtn:
   - add volume up and down

  INT33FE:
   - add i2c dependency

  hp-wmi:
   - Cleanup exit paths
   - Do not shadow errors in sysfs show functions
   - Use DEVICE_ATTR_(RO|RW) helper macros
   - Refactor dock and tablet state fetchers
   - Cleanup wireless get_(hw|sw)state functions
   - Refactor redundant HPWMI_READ functions
   - Standardize enum usage for constants
   - Cleanup local variable declarations
   - Do not shadow error values
   - Fix detection for dock and tablet mode
   - Fix error value for hp_wmi_tablet_state

  fujitsu-laptop:
   - simplify error handling in acpi_fujitsu_laptop_add()
   - do not log LED registration failures
   - switch to managed LED class devices
   - reorganize LED-related code
   - refactor LED registration
   - select LEDS_CLASS
   - remove redundant fields from struct fujitsu_bl
   - account for backlight power when determining brightness
   - do not log set_lcd_level() failures in bl_update_status()
   - ignore errors when setting backlight power
   - make disable_brightness_adjust a boolean
   - clean up use_alt_lcd_levels handling
   - sync brightness in set_lcd_level()
   - simplify set_lcd_level()
   - merge set_lcd_level_alt() into set_lcd_level()
   - switch to a managed backlight device
   - only handle backlight when appropriate
   - update debug message logged by call_fext_func()
   - rename call_fext_func() arguments
   - simplify call_fext_func()
   - clean up local variables in call_fext_func()
   - remove keycode fields from struct fujitsu_bl
   - model-dependent sparse keymap overrides
   - use a sparse keymap for hotkey event generation
   - switch to a managed hotkey input device
   - refactor hotkey input device setup
   - use a sparse keymap for brightness key events
   - switch to a managed backlight input device
   - refactor backlight input device setup
   - remove pf_device field from struct fujitsu_bl
   - only register platform device if FUJ02E3 is present
   - add and remove platform device in separate functions
   - simplify platform device attribute definitions
   - remove backlight-related attributes from the platform device
   - cleanup error labels in fujitsu_init()
   - only register backlight device if FUJ02B1 is present
   - sync backlight power status in acpi_fujitsu_laptop_add()
   - register backlight device in a separate function
   - simplify brightness key event generation logic
   - decrease indentation in acpi_fujitsu_bl_notify()

  intel-hid:
   - Add missing ->thaw callback
   - do not set parents of input devices explicitly
   - remove redundant set_bit() call
   - use devm_input_allocate_device() for HID events input device
   - make intel_hid_set_enable() take a boolean argument
   - simplify enabling/disabling HID events

  silead_dmi:
   - Add touchscreen info for Surftab Wintron 7.0
   - Abort early if DMI does not match
   - Do not treat all devices as i2c_clients
   - Add entry for Insyde 7W tablets
   - Constify properties arrays

  intel_scu_ipc:
   - Introduce intel_scu_ipc_raw_command()
   - Introduce SCU_DEVICE() macro
   - Remove redundant subarch check
   - Rearrange init sequence
   - Platform data is mandatory

  asus-nb-wmi:
   - Add wapf4 quirk for the X302UA

  dell-*:
   - Call new led hw_changed API on kbd brightness change
   - Add a generic dell-laptop notifier chain

  eeepc-laptop:
   - Skip unknown key messages 0x50 0x51

  thinkpad_acpi:
   - add mapping for new hotkeys
   - guard generic hotkey case"

* tag 'platform-drivers-x86-v4.12-1' of git://git.infradead.org/linux-platform-drivers-x86: (108 commits)
  platform/x86: Make SILEAD_DMI depend on TOUCHSCREEN_SILEAD
  platform/x86: asus-wmi: try to set als by default
  platform/x86: asus-wmi: fix cpufv sysfs file permission
  platform/x86: acer-wmi: setup accelerometer when ACPI device was found
  platform/x86: ideapad-laptop: Add IdeaPad V310-15ISK to no_hw_rfkill
  platform/x86: intel_pmc_ipc: use gcr mem base for S0ix counter read
  platform/x86: intel_pmc_ipc: Fix iTCO_wdt GCS memory mapping failure
  watchdog: iTCO_wdt: Add PMC specific noreboot update api
  watchdog: iTCO_wdt: cleanup set/unset no_reboot_bit functions
  platform/x86: intel_pmc_ipc: Add pmc gcr read/write/update api's
  platform/x86: intel_pmc_ipc: fix gcr offset
  platform/x86: dell-laptop: Add keyboard backlight timeout AC settings
  platform/x86: dell-laptop: Handle return error form dell_get_intensity.
  platform/x86: hp-wireless: reuse module_acpi_driver
  platform/x86: intel-vbtn: add volume up and down
  platform/x86: INT33FE: add i2c dependency
  platform/x86: hp-wmi: Cleanup exit paths
  platform/x86: hp-wmi: Do not shadow errors in sysfs show functions
  platform/x86: hp-wmi: Use DEVICE_ATTR_(RO|RW) helper macros
  platform/x86: hp-wmi: Refactor dock and tablet state fetchers
  ...
parents c336bf8e 6df97f85
......@@ -23,6 +23,11 @@
#define IPC_ERR_EMSECURITY 6
#define IPC_ERR_UNSIGNEDKERNEL 7
/* GCR reg offsets from gcr base*/
#define PMC_GCR_PMC_CFG_REG 0x08
#define PMC_GCR_TELEM_DEEP_S0IX_REG 0x78
#define PMC_GCR_TELEM_SHLW_S0IX_REG 0x80
#if IS_ENABLED(CONFIG_INTEL_PMC_IPC)
int intel_pmc_ipc_simple_command(int cmd, int sub);
......@@ -31,6 +36,9 @@ int intel_pmc_ipc_raw_cmd(u32 cmd, u32 sub, u8 *in, u32 inlen,
int intel_pmc_ipc_command(u32 cmd, u32 sub, u8 *in, u32 inlen,
u32 *out, u32 outlen);
int intel_pmc_s0ix_counter_read(u64 *data);
int intel_pmc_gcr_read(u32 offset, u32 *data);
int intel_pmc_gcr_write(u32 offset, u32 data);
int intel_pmc_gcr_update(u32 offset, u32 mask, u32 val);
#else
......@@ -56,6 +64,21 @@ static inline int intel_pmc_s0ix_counter_read(u64 *data)
return -EINVAL;
}
static inline int intel_pmc_gcr_read(u32 offset, u32 *data)
{
return -EINVAL;
}
static inline int intel_pmc_gcr_write(u32 offset, u32 data)
{
return -EINVAL;
}
static inline int intel_pmc_gcr_update(u32 offset, u32 mask, u32 val)
{
return -EINVAL;
}
#endif /*CONFIG_INTEL_PMC_IPC*/
#endif
......@@ -3,6 +3,9 @@
#include <linux/notifier.h>
#define IPCMSG_INDIRECT_READ 0x02
#define IPCMSG_INDIRECT_WRITE 0x05
#define IPCMSG_COLD_OFF 0x80 /* Only for Tangier */
#define IPCMSG_WARM_RESET 0xF0
......@@ -46,6 +49,9 @@ int intel_scu_ipc_update_register(u16 addr, u8 data, u8 mask);
int intel_scu_ipc_simple_command(int cmd, int sub);
int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
u32 *out, int outlen);
int intel_scu_ipc_raw_command(int cmd, int sub, u8 *in, int inlen,
u32 *out, int outlen, u32 dptr, u32 sptr);
/* I2C control api */
int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data);
......
......@@ -182,7 +182,8 @@ config FUJITSU_LAPTOP
depends on INPUT
depends on BACKLIGHT_CLASS_DEVICE
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on LEDS_CLASS || LEDS_CLASS=n
select INPUT_SPARSEKMAP
select LEDS_CLASS
---help---
This is a driver for laptops built by Fujitsu:
......@@ -780,6 +781,19 @@ config ACPI_CMPC
keys as input device, backlight device, tablet and accelerometer
devices.
config INTEL_CHT_INT33FE
tristate "Intel Cherry Trail ACPI INT33FE Driver"
depends on X86 && ACPI && I2C
---help---
This driver add support for the INT33FE ACPI device found on
some Intel Cherry Trail devices.
The INT33FE ACPI device has a CRS table with I2cSerialBusV2
resources for 3 devices: Maxim MAX17047 Fuel Gauge Controller,
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.
config INTEL_HID_EVENT
tristate "INTEL HID Event"
depends on ACPI
......@@ -1087,7 +1101,7 @@ config INTEL_TURBO_MAX_3
config SILEAD_DMI
bool "Tablets with Silead touchscreens"
depends on ACPI && DMI && I2C=y && INPUT
depends on ACPI && DMI && I2C=y && TOUCHSCREEN_SILEAD
---help---
Certain ACPI based tablets with Silead touchscreens do not have
enough data in ACPI tables for the touchscreen driver to handle
......
......@@ -45,6 +45,7 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
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
obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o
obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
......
......@@ -1896,7 +1896,7 @@ static acpi_status __init acer_wmi_get_handle_cb(acpi_handle ah, u32 level,
if (!strcmp(ctx, "SENR")) {
if (acpi_bus_get_device(ah, &dev))
return AE_OK;
if (!strcmp(ACER_WMID_ACCEL_HID, acpi_device_hid(dev)))
if (strcmp(ACER_WMID_ACCEL_HID, acpi_device_hid(dev)))
return AE_OK;
} else
return AE_OK;
......@@ -1917,8 +1917,7 @@ static int __init acer_wmi_get_handle(const char *name, const char *prop,
handle = NULL;
status = acpi_get_devices(prop, acer_wmi_get_handle_cb,
(void *)name, &handle);
if (ACPI_SUCCESS(status)) {
if (ACPI_SUCCESS(status) && handle) {
*ah = handle;
return 0;
} else {
......@@ -1987,7 +1986,7 @@ static int __init acer_wmi_input_setup(void)
acer_wmi_notify, NULL);
if (ACPI_FAILURE(status)) {
err = -EIO;
goto err_free_keymap;
goto err_free_dev;
}
err = input_register_device(acer_wmi_input_dev);
......@@ -1998,8 +1997,6 @@ static int __init acer_wmi_input_setup(void)
err_uninstall_notifier:
wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
err_free_keymap:
sparse_keymap_free(acer_wmi_input_dev);
err_free_dev:
input_free_device(acer_wmi_input_dev);
return err;
......@@ -2008,7 +2005,6 @@ static int __init acer_wmi_input_setup(void)
static void acer_wmi_input_destroy(void)
{
wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
sparse_keymap_free(acer_wmi_input_dev);
input_unregister_device(acer_wmi_input_dev);
}
......@@ -2290,8 +2286,8 @@ static int __init acer_wmi_init(void)
if (err)
return err;
err = acer_wmi_accel_setup();
if (err)
return err;
if (err && err != -ENODEV)
pr_warn("Cannot enable accelerometer\n");
}
err = platform_driver_register(&acer_platform_driver);
......
......@@ -1516,14 +1516,12 @@ static int asus_input_init(struct asus_laptop *asus)
error = input_register_device(input);
if (error) {
pr_warn("Unable to register input device\n");
goto err_free_keymap;
goto err_free_dev;
}
asus->inputdev = input;
return 0;
err_free_keymap:
sparse_keymap_free(input);
err_free_dev:
input_free_device(input);
return error;
......@@ -1531,10 +1529,8 @@ static int asus_input_init(struct asus_laptop *asus)
static void asus_input_exit(struct asus_laptop *asus)
{
if (asus->inputdev) {
sparse_keymap_free(asus->inputdev);
if (asus->inputdev)
input_unregister_device(asus->inputdev);
}
asus->inputdev = NULL;
}
......
......@@ -111,6 +111,10 @@ static struct quirk_entry quirk_asus_x550lb = {
.xusb2pr = 0x01D9,
};
static struct quirk_entry quirk_asus_ux330uak = {
.wmi_force_als_set = true,
};
static int dmi_matched(const struct dmi_system_id *dmi)
{
pr_info("Identified laptop model '%s'\n", dmi->ident);
......@@ -142,6 +146,15 @@ static const struct dmi_system_id asus_quirks[] = {
*/
.driver_data = &quirk_asus_wapf4,
},
{
.callback = dmi_matched,
.ident = "ASUSTeK COMPUTER INC. X302UA",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "X302UA"),
},
.driver_data = &quirk_asus_wapf4,
},
{
.callback = dmi_matched,
.ident = "ASUSTeK COMPUTER INC. X401U",
......@@ -367,6 +380,15 @@ static const struct dmi_system_id asus_quirks[] = {
},
.driver_data = &quirk_asus_ux303ub,
},
{
.callback = dmi_matched,
.ident = "ASUSTeK COMPUTER INC. UX330UAK",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "UX330UAK"),
},
.driver_data = &quirk_asus_ux330uak,
},
{
.callback = dmi_matched,
.ident = "ASUSTeK COMPUTER INC. X550LB",
......
......@@ -269,12 +269,10 @@ static int asus_wmi_input_init(struct asus_wmi *asus)
err = input_register_device(asus->inputdev);
if (err)
goto err_free_keymap;
goto err_free_dev;
return 0;
err_free_keymap:
sparse_keymap_free(asus->inputdev);
err_free_dev:
input_free_device(asus->inputdev);
return err;
......@@ -282,10 +280,8 @@ static int asus_wmi_input_init(struct asus_wmi *asus)
static void asus_wmi_input_exit(struct asus_wmi *asus)
{
if (asus->inputdev) {
sparse_keymap_free(asus->inputdev);
if (asus->inputdev)
input_unregister_device(asus->inputdev);
}
asus->inputdev = NULL;
}
......@@ -1108,6 +1104,15 @@ static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
orig_ports_available, ports_available);
}
/*
* Some devices dont support or have borcken get_als method
* but still support set method.
*/
static void asus_wmi_set_als(void)
{
asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL);
}
/*
* Hwmon device
*/
......@@ -1761,7 +1766,7 @@ ASUS_WMI_CREATE_DEVICE_ATTR(cardr, 0644, ASUS_WMI_DEVID_CARDREADER);
ASUS_WMI_CREATE_DEVICE_ATTR(lid_resume, 0644, ASUS_WMI_DEVID_LID_RESUME);
ASUS_WMI_CREATE_DEVICE_ATTR(als_enable, 0644, ASUS_WMI_DEVID_ALS_ENABLE);
static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr,
static ssize_t cpufv_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int value, rv;
......@@ -1778,7 +1783,7 @@ static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr,
return count;
}
static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv);
static DEVICE_ATTR_WO(cpufv);
static struct attribute *platform_attributes[] = {
&dev_attr_cpufv.attr,
......@@ -2117,6 +2122,9 @@ static int asus_wmi_add(struct platform_device *pdev)
goto fail_rfkill;
}
if (asus->driver->quirks->wmi_force_als_set)
asus_wmi_set_als();
/* Some Asus desktop boards export an acpi-video backlight interface,
stop this from showing up */
chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE);
......
......@@ -44,6 +44,7 @@ struct quirk_entry {
bool store_backlight_power;
bool wmi_backlight_power;
bool wmi_backlight_native;
bool wmi_force_als_set;
int wapf;
/*
* For machines with AMD graphic chips, it will send out WMI event
......
......@@ -45,6 +45,7 @@
#define KBD_LED_AUTO_100_TOKEN 0x02F6
#define GLOBAL_MIC_MUTE_ENABLE 0x0364
#define GLOBAL_MIC_MUTE_DISABLE 0x0365
#define KBD_LED_AC_TOKEN 0x0451
struct quirk_entry {
u8 touchpad_led;
......@@ -1027,7 +1028,7 @@ static void touchpad_led_exit(void)
* bit 2 Pointing stick
* bit 3 Any mouse
* bits 4-7 Reserved for future use
* cbRES2, byte3 Current Timeout
* cbRES2, byte3 Current Timeout on battery
* bits 7:6 Timeout units indicator:
* 00b Seconds
* 01b Minutes
......@@ -1039,6 +1040,15 @@ static void touchpad_led_exit(void)
* cbRES3, byte0 Current setting of ALS value that turns the light on or off.
* cbRES3, byte1 Current ALS reading
* cbRES3, byte2 Current keyboard light level.
* cbRES3, byte3 Current timeout on AC Power
* bits 7:6 Timeout units indicator:
* 00b Seconds
* 01b Minutes
* 10b Hours
* 11b Days
* Bits 5:0 Timeout value (0-63) in sec/min/hr/day
* NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte2
* are set upon return from the upon return from the [Get Feature information] call.
*
* cbArg1 0x2 = Set New State
* cbRES1 Standard return codes (0, -1, -2)
......@@ -1061,7 +1071,7 @@ static void touchpad_led_exit(void)
* bit 2 Pointing stick
* bit 3 Any mouse
* bits 4-7 Reserved for future use
* cbArg2, byte3 Desired Timeout
* cbArg2, byte3 Desired Timeout on battery
* bits 7:6 Timeout units indicator:
* 00b Seconds
* 01b Minutes
......@@ -1070,6 +1080,13 @@ static void touchpad_led_exit(void)
* bits 5:0 Timeout value (0-63) in sec/min/hr/day
* cbArg3, byte0 Desired setting of ALS value that turns the light on or off.
* cbArg3, byte2 Desired keyboard light level.
* cbArg3, byte3 Desired Timeout on AC power
* bits 7:6 Timeout units indicator:
* 00b Seconds
* 01b Minutes
* 10b Hours
* 11b Days
* bits 5:0 Timeout value (0-63) in sec/min/hr/day
*/
......@@ -1115,6 +1132,8 @@ struct kbd_state {
u8 triggers;
u8 timeout_value;
u8 timeout_unit;
u8 timeout_value_ac;
u8 timeout_unit_ac;
u8 als_setting;
u8 als_value;
u8 level;
......@@ -1134,6 +1153,7 @@ static u16 kbd_token_bits;
static struct kbd_info kbd_info;
static bool kbd_als_supported;
static bool kbd_triggers_supported;
static bool kbd_timeout_ac_supported;
static u8 kbd_mode_levels[16];
static int kbd_mode_levels_count;
......@@ -1142,6 +1162,7 @@ static u8 kbd_previous_level;
static u8 kbd_previous_mode_bit;
static bool kbd_led_present;
static DEFINE_MUTEX(kbd_led_mutex);
/*
* NOTE: there are three ways to set the keyboard backlight level.
......@@ -1272,6 +1293,8 @@ static int kbd_get_state(struct kbd_state *state)
state->als_setting = buffer->output[2] & 0xFF;
state->als_value = (buffer->output[2] >> 8) & 0xFF;
state->level = (buffer->output[2] >> 16) & 0xFF;
state->timeout_value_ac = (buffer->output[2] >> 24) & 0x3F;
state->timeout_unit_ac = (buffer->output[2] >> 30) & 0x3;
out:
dell_smbios_release_buffer();
......@@ -1291,6 +1314,8 @@ static int kbd_set_state(struct kbd_state *state)
buffer->input[1] |= (state->timeout_unit & 0x3) << 30;
buffer->input[2] = state->als_setting & 0xFF;
buffer->input[2] |= (state->level & 0xFF) << 16;
buffer->input[2] |= (state->timeout_value_ac & 0x3F) << 24;
buffer->input[2] |= (state->timeout_unit_ac & 0x3) << 30;
dell_smbios_send_request(4, 11);
ret = buffer->output[0];
dell_smbios_release_buffer();
......@@ -1397,6 +1422,13 @@ static inline int kbd_init_info(void)
if (ret)
return ret;
/* NOTE: Old models without KBD_LED_AC_TOKEN token supports only one
* timeout value which is shared for both battery and AC power
* settings. So do not try to set AC values on old models.
*/
if (dell_smbios_find_token(KBD_LED_AC_TOKEN))
kbd_timeout_ac_supported = true;
kbd_get_state(&state);
/* NOTE: timeout value is stored in 6 bits so max value is 63 */
......@@ -1571,35 +1603,56 @@ static ssize_t kbd_led_timeout_store(struct device *dev,
}
}
mutex_lock(&kbd_led_mutex);
ret = kbd_get_state(&state);
if (ret)
return ret;
goto out;
new_state = state;
if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) {
new_state.timeout_value_ac = value;
new_state.timeout_unit_ac = unit;
} else {
new_state.timeout_value = value;
new_state.timeout_unit = unit;
}
ret = kbd_set_state_safe(&new_state, &state);
if (ret)
return ret;
goto out;
return count;
ret = count;
out:
mutex_unlock(&kbd_led_mutex);
return ret;
}
static ssize_t kbd_led_timeout_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct kbd_state state;
int value;
int ret;
int len;
u8 unit;
ret = kbd_get_state(&state);
if (ret)
return ret;
len = sprintf(buf, "%d", state.timeout_value);
if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) {
value = state.timeout_value_ac;
unit = state.timeout_unit_ac;
} else {
value = state.timeout_value;
unit = state.timeout_unit;
}
len = sprintf(buf, "%d", value);
switch (state.timeout_unit) {
switch (unit) {
case KBD_TIMEOUT_SECONDS:
return len + sprintf(buf+len, "s\n");
case KBD_TIMEOUT_MINUTES:
......@@ -1643,9 +1696,11 @@ static ssize_t kbd_led_triggers_store(struct device *dev,
if (trigger[0] != '+' && trigger[0] != '-')
return -EINVAL;
mutex_lock(&kbd_led_mutex);
ret = kbd_get_state(&state);
if (ret)
return ret;
goto out;
if (kbd_triggers_supported)
triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit);
......@@ -1659,48 +1714,62 @@ static ssize_t kbd_led_triggers_store(struct device *dev,
if (strcmp(trigger+1, kbd_led_triggers[i]) != 0)
continue;
if (trigger[0] == '+' &&
triggers_enabled && (state.triggers & BIT(i)))
return count;
triggers_enabled && (state.triggers & BIT(i))) {
ret = count;
goto out;
}
if (trigger[0] == '-' &&
(!triggers_enabled || !(state.triggers & BIT(i))))
return count;
(!triggers_enabled || !(state.triggers & BIT(i)))) {
ret = count;
goto out;
}
trigger_bit = i;
break;
}
}
if (trigger_bit != -1) {
if (trigger_bit == -1) {
ret = -EINVAL;
goto out;
}
new_state = state;
if (trigger[0] == '+')
new_state.triggers |= BIT(trigger_bit);
else {
new_state.triggers &= ~BIT(trigger_bit);
/* NOTE: trackstick bit (2) must be disabled when
/*
* NOTE: trackstick bit (2) must be disabled when
* disabling touchpad bit (1), otherwise touchpad
* bit (1) will not be disabled */
* bit (1) will not be disabled
*/
if (trigger_bit == 1)
new_state.triggers &= ~BIT(2);
}
if ((kbd_info.triggers & new_state.triggers) !=
new_state.triggers)
return -EINVAL;
new_state.triggers) {
ret = -EINVAL;
goto out;
}
if (new_state.triggers && !triggers_enabled) {
new_state.mode_bit = KBD_MODE_BIT_TRIGGER;
kbd_set_level(&new_state, kbd_previous_level);
} else if (new_state.triggers == 0) {
kbd_set_level(&new_state, 0);
}
if (!(kbd_info.modes & BIT(new_state.mode_bit)))
return -EINVAL;
if (!(kbd_info.modes & BIT(new_state.mode_bit))) {
ret = -EINVAL;
goto out;
}
ret = kbd_set_state_safe(&new_state, &state);
if (ret)
return ret;
goto out;
if (new_state.mode_bit != KBD_MODE_BIT_OFF)
kbd_previous_mode_bit = new_state.mode_bit;
return count;
}
return -EINVAL;
ret = count;
out:
mutex_unlock(&kbd_led_mutex);
return ret;
}
static ssize_t kbd_led_triggers_show(struct device *dev,
......@@ -1757,12 +1826,16 @@ static ssize_t kbd_led_als_enabled_store(struct device *dev,
if (ret)
return ret;
mutex_lock(&kbd_led_mutex);
ret = kbd_get_state(&state);
if (ret)
return ret;
goto out;
if (enable == kbd_is_als_mode_bit(state.mode_bit))
return count;
if (enable == kbd_is_als_mode_bit(state.mode_bit)) {
ret = count;
goto out;
}
new_state = state;
......@@ -1782,15 +1855,20 @@ static ssize_t kbd_led_als_enabled_store(struct device *dev,
new_state.mode_bit = KBD_MODE_BIT_ON;
}
}
if (!(kbd_info.modes & BIT(new_state.mode_bit)))
return -EINVAL;
if (!(kbd_info.modes & BIT(new_state.mode_bit))) {
ret = -EINVAL;
goto out;
}
ret = kbd_set_state_safe(&new_state, &state);
if (ret)
return ret;
goto out;
kbd_previous_mode_bit = new_state.mode_bit;
return count;
ret = count;
out:
mutex_unlock(&kbd_led_mutex);
return ret;
}
static ssize_t kbd_led_als_enabled_show(struct device *dev,
......@@ -1825,18 +1903,23 @@ static ssize_t kbd_led_als_setting_store(struct device *dev,
if (ret)
return ret;
mutex_lock(&kbd_led_mutex);
ret = kbd_get_state(&state);
if (ret)
return ret;
goto out;
new_state = state;
new_state.als_setting = setting;
ret = kbd_set_state_safe(&new_state, &state);
if (ret)
return ret;
goto out;
return count;
ret = count;
out:
mutex_unlock(&kbd_led_mutex);
return ret;
}
static ssize_t kbd_led_als_setting_show(struct device *dev,
......@@ -1921,31 +2004,37 @@ static int kbd_led_level_set(struct led_classdev *led_cdev,
u16 num;
int ret;
mutex_lock(&kbd_led_mutex);
if (kbd_get_max_level()) {
ret = kbd_get_state(&state);
if (ret)
return ret;
goto out;
new_state = state;
ret = kbd_set_level(&new_state, value);
if (ret)
return ret;
return kbd_set_state_safe(&new_state, &state);
}
if (kbd_get_valid_token_counts()) {
goto out;
ret = kbd_set_state_safe(&new_state, &state);
} else if (kbd_get_valid_token_counts()) {
for (num = kbd_token_bits; num != 0 && value > 0; --value)
num &= num - 1; /* clear the first bit set */
if (num == 0)
return 0;
return kbd_set_token_bit(ffs(num) - 1);
ret = 0;
else
ret = kbd_set_token_bit(ffs(num) - 1);
} else {
pr_warn("Keyboard brightness level control not supported\n");
ret = -ENXIO;
}
pr_warn("Keyboard brightness level control not supported\n");
return -ENXIO;
out:
mutex_unlock(&kbd_led_mutex);
return ret;
}
static struct led_classdev kbd_led = {
.name = "dell::kbd_backlight",
.flags = LED_BRIGHT_HW_CHANGED,
.brightness_set_blocking = kbd_led_level_set,
.brightness_get = kbd_led_level_get,
.groups = kbd_led_groups,
......@@ -1953,6 +2042,8 @@ static struct led_classdev kbd_led = {
static int __init kbd_led_init(struct device *dev)
{
int ret;
kbd_init();
if (!kbd_led_present)
return -ENODEV;
......@@ -1964,7 +2055,11 @@ static int __init kbd_led_init(struct device *dev)
if (kbd_led.max_brightness)
kbd_led.max_brightness--;
}
return led_classdev_register(dev, &kbd_led);
ret = led_classdev_register(dev, &kbd_led);
if (ret)
kbd_led_present = false;
return ret;
}
static void brightness_set_exit(struct led_classdev *led_cdev,
......@@ -1981,6 +2076,26 @@ static void kbd_led_exit(void)
led_classdev_unregister(&kbd_led);
}
static int dell_laptop_notifier_call(struct notifier_block *nb,
unsigned long action, void *data)
{
switch (action) {
case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED:
if (!kbd_led_present)
break;
led_classdev_notify_brightness_hw_changed(&kbd_led,
kbd_led_level_get(&kbd_led));
break;
}
return NOTIFY_OK;
}
static struct notifier_block dell_laptop_notifier = {
.notifier_call = dell_laptop_notifier_call,
};
int dell_micmute_led_set(int state)
{
struct calling_interface_buffer *buffer;
......@@ -2049,6 +2164,8 @@ static int __init dell_init(void)
debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL,
&dell_debugfs_fops);
dell_laptop_register_notifier(&dell_laptop_notifier);
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
return 0;
......@@ -2081,11 +2198,17 @@ static int __init dell_init(void)
dell_backlight_device->props.brightness =
dell_get_intensity(dell_backlight_device);
if (dell_backlight_device->props.brightness < 0) {
ret = dell_backlight_device->props.brightness;
goto fail_get_brightness;
}
backlight_update_status(dell_backlight_device);
}
return 0;
fail_get_brightness:
backlight_device_unregister(dell_backlight_device);
fail_backlight:
dell_cleanup_rfkill();
fail_rfkill:
......@@ -2100,6 +2223,7 @@ static int __init dell_init(void)
static void __exit dell_exit(void)
{
dell_laptop_unregister_notifier(&dell_laptop_notifier);
debugfs_remove_recursive(dell_laptop_dir);
if (quirks && quirks->touchpad_led)
touchpad_led_exit();
......
......@@ -105,6 +105,26 @@ struct calling_interface_token *dell_smbios_find_token(int tokenid)
}
EXPORT_SYMBOL_GPL(dell_smbios_find_token);
static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head);
int dell_laptop_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&dell_laptop_chain_head, nb);
}
EXPORT_SYMBOL_GPL(dell_laptop_register_notifier);
int dell_laptop_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb);
}
EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier);
void dell_laptop_call_notifier(unsigned long action, void *data)
{
blocking_notifier_call_chain(&dell_laptop_chain_head, action, data);
}
EXPORT_SYMBOL_GPL(dell_laptop_call_notifier);
static void __init parse_da_table(const struct dmi_header *dm)
{
/* Final token is a terminator, so we don't want to copy it */
......
......@@ -16,6 +16,8 @@
#ifndef _DELL_SMBIOS_H_
#define _DELL_SMBIOS_H_
struct notifier_block;
/* This structure will be modified by the firmware when we enter
* system management mode, hence the volatiles */
......@@ -43,4 +45,13 @@ void dell_smbios_release_buffer(void);
void dell_smbios_send_request(int class, int select);
struct calling_interface_token *dell_smbios_find_token(int tokenid);
enum dell_laptop_notifier_actions {
DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED,
};
int dell_laptop_register_notifier(struct notifier_block *nb);
int dell_laptop_unregister_notifier(struct notifier_block *nb);
void dell_laptop_call_notifier(unsigned long action, void *data);
#endif
......@@ -152,12 +152,10 @@ static int __init dell_wmi_aio_input_setup(void)
err = input_register_device(dell_wmi_aio_input_dev);
if (err) {
pr_info("Unable to register input device\n");
goto err_free_keymap;
goto err_free_dev;
}
return 0;
err_free_keymap:
sparse_keymap_free(dell_wmi_aio_input_dev);
err_free_dev:
input_free_device(dell_wmi_aio_input_dev);
return err;
......@@ -192,7 +190,6 @@ static int __init dell_wmi_aio_init(void)
err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL);
if (err) {
pr_err("Unable to register notify handler - %d\n", err);
sparse_keymap_free(dell_wmi_aio_input_dev);
input_unregister_device(dell_wmi_aio_input_dev);
return err;
}
......@@ -206,7 +203,6 @@ static void __exit dell_wmi_aio_exit(void)
guid = dell_wmi_aio_find();
wmi_remove_notify_handler(guid);
sparse_keymap_free(dell_wmi_aio_input_dev);
input_unregister_device(dell_wmi_aio_input_dev);
}
......
......@@ -329,6 +329,10 @@ static void dell_wmi_process_key(int type, int code)
if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request)
return;
if (key->keycode == KEY_KBDILLUMTOGGLE)
dell_laptop_call_notifier(
DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL);
sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true);
}
......@@ -603,23 +607,15 @@ static int __init dell_wmi_input_setup(void)
err = input_register_device(dell_wmi_input_dev);
if (err)
goto err_free_keymap;
goto err_free_dev;
return 0;
err_free_keymap:
sparse_keymap_free(dell_wmi_input_dev);
err_free_dev:
input_free_device(dell_wmi_input_dev);
return err;
}
static void dell_wmi_input_destroy(void)
{
sparse_keymap_free(dell_wmi_input_dev);
input_unregister_device(dell_wmi_input_dev);
}
/*
* Descriptor buffer is 128 byte long and contains:
*
......@@ -740,7 +736,7 @@ static int __init dell_wmi_init(void)
status = wmi_install_notify_handler(DELL_EVENT_GUID,
dell_wmi_notify, NULL);
if (ACPI_FAILURE(status)) {
dell_wmi_input_destroy();
input_unregister_device(dell_wmi_input_dev);
pr_err("Unable to register notify handler - %d\n", status);
return -ENODEV;
}
......@@ -752,7 +748,7 @@ static int __init dell_wmi_init(void)
if (err) {
pr_err("Failed to enable WMI events\n");
wmi_remove_notify_handler(DELL_EVENT_GUID);
dell_wmi_input_destroy();
input_unregister_device(dell_wmi_input_dev);
return err;
}
}
......@@ -766,6 +762,6 @@ static void __exit dell_wmi_exit(void)
if (wmi_requires_smbios_request)
dell_wmi_events_set_enabled(false);
wmi_remove_notify_handler(DELL_EVENT_GUID);
dell_wmi_input_destroy();
input_unregister_device(dell_wmi_input_dev);
}
module_exit(dell_wmi_exit);
......@@ -150,6 +150,8 @@ static const struct key_entry eeepc_keymap[] = {
{ KE_KEY, 0x32, { KEY_SWITCHVIDEOMODE } },
{ KE_KEY, 0x37, { KEY_F13 } }, /* Disable Touchpad */
{ KE_KEY, 0x38, { KEY_F14 } },
{ KE_IGNORE, 0x50, { KEY_RESERVED } }, /* AC plugged */
{ KE_IGNORE, 0x51, { KEY_RESERVED } }, /* AC unplugged */
{ KE_END, 0 },
};
......@@ -1205,14 +1207,12 @@ static int eeepc_input_init(struct eeepc_laptop *eeepc)
error = input_register_device(input);
if (error) {
pr_err("Unable to register input device\n");
goto err_free_keymap;
goto err_free_dev;
}
eeepc->inputdev = input;
return 0;
err_free_keymap:
sparse_keymap_free(input);
err_free_dev:
input_free_device(input);
return error;
......@@ -1220,10 +1220,8 @@ static int eeepc_input_init(struct eeepc_laptop *eeepc)
static void eeepc_input_exit(struct eeepc_laptop *eeepc)
{
if (eeepc->inputdev) {
sparse_keymap_free(eeepc->inputdev);
if (eeepc->inputdev)
input_unregister_device(eeepc->inputdev);
}
eeepc->inputdev = NULL;
}
......
......@@ -32,18 +32,9 @@
* features made available on a range of Fujitsu laptops including the
* P2xxx/P5xxx/S6xxx/S7xxx series.
*
* This driver exports a few files in /sys/devices/platform/fujitsu-laptop/;
* others may be added at a later date.
*
* lcd_level - Screen brightness: contains a single integer in the
* range 0..7. (rw)
*
* In addition to these platform device attributes the driver
* registers itself in the Linux backlight control subsystem and is
* available to userspace under /sys/class/backlight/fujitsu-laptop/.
*
* Hotkeys present on certain Fujitsu laptops (eg: the S6xxx series) are
* also supported by this driver.
* This driver implements a vendor-specific backlight control interface for
* Fujitsu laptops and provides support for hotkeys present on certain Fujitsu
* laptops.
*
* This driver has been tested on a Fujitsu Lifebook S6410, S7020 and
* P8010. It should work on most P-series and S-series Lifebooks, but
......@@ -66,12 +57,11 @@
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kfifo.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#if IS_ENABLED(CONFIG_LEDS_CLASS)
#include <linux/leds.h>
#endif
#include <acpi/video.h>
#define FUJITSU_DRIVER_VERSION "0.6.0"
......@@ -102,7 +92,6 @@
#define FLAG_LID 0x100
#define FLAG_DOCK 0x200
#if IS_ENABLED(CONFIG_LEDS_CLASS)
/* FUNC interface - LED control */
#define FUNC_LED_OFF 0x1
#define FUNC_LED_ON 0x30001
......@@ -112,7 +101,6 @@
#define RADIO_LED_ON 0x20
#define ECO_LED 0x10000
#define ECO_LED_ON 0x80000
#endif
/* Hotkey details */
#define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */
......@@ -143,21 +131,16 @@
/* Device controlling the backlight and associated keys */
struct fujitsu_bl {
acpi_handle acpi_handle;
struct acpi_device *dev;
struct input_dev *input;
char phys[32];
struct backlight_device *bl_device;
struct platform_device *pf_device;
int keycode1, keycode2, keycode3, keycode4, keycode5;
unsigned int max_brightness;
unsigned int brightness_changed;
unsigned int brightness_level;
};
static struct fujitsu_bl *fujitsu_bl;
static int use_alt_lcd_levels = -1;
static int disable_brightness_adjust = -1;
static bool disable_brightness_adjust;
/* Device used to access hotkeys and other features on the laptop */
struct fujitsu_laptop {
......@@ -170,247 +153,77 @@ struct fujitsu_laptop {
spinlock_t fifo_lock;
int flags_supported;
int flags_state;
int logolamp_registered;
int kblamps_registered;
int radio_led_registered;
int eco_led_registered;
};
static struct fujitsu_laptop *fujitsu_laptop;
#if IS_ENABLED(CONFIG_LEDS_CLASS)
static enum led_brightness logolamp_get(struct led_classdev *cdev);
static int logolamp_set(struct led_classdev *cdev,
enum led_brightness brightness);
static struct led_classdev logolamp_led = {
.name = "fujitsu::logolamp",
.brightness_get = logolamp_get,
.brightness_set_blocking = logolamp_set
};
static enum led_brightness kblamps_get(struct led_classdev *cdev);
static int kblamps_set(struct led_classdev *cdev,
enum led_brightness brightness);
static struct led_classdev kblamps_led = {
.name = "fujitsu::kblamps",
.brightness_get = kblamps_get,
.brightness_set_blocking = kblamps_set
};
static enum led_brightness radio_led_get(struct led_classdev *cdev);
static int radio_led_set(struct led_classdev *cdev,
enum led_brightness brightness);
static struct led_classdev radio_led = {
.name = "fujitsu::radio_led",
.default_trigger = "rfkill-any",
.brightness_get = radio_led_get,
.brightness_set_blocking = radio_led_set
};
static enum led_brightness eco_led_get(struct led_classdev *cdev);
static int eco_led_set(struct led_classdev *cdev,
enum led_brightness brightness);
static struct led_classdev eco_led = {
.name = "fujitsu::eco_led",
.brightness_get = eco_led_get,
.brightness_set_blocking = eco_led_set
};
#endif
#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
static u32 dbg_level = 0x03;
#endif
/* Fujitsu ACPI interface function */
static int call_fext_func(int cmd, int arg0, int arg1, int arg2)
static int call_fext_func(int func, int op, int feature, int state)
{
acpi_status status = AE_OK;
union acpi_object params[4] = {
{ .type = ACPI_TYPE_INTEGER },
{ .type = ACPI_TYPE_INTEGER },
{ .type = ACPI_TYPE_INTEGER },
{ .type = ACPI_TYPE_INTEGER }
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = func },
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = op },
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature },
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = state }
};
struct acpi_object_list arg_list = { 4, &params[0] };
struct acpi_object_list arg_list = { 4, params };
unsigned long long value;
acpi_handle handle = NULL;
acpi_status status;
status = acpi_get_handle(fujitsu_laptop->acpi_handle, "FUNC", &handle);
status = acpi_evaluate_integer(fujitsu_laptop->acpi_handle, "FUNC",
&arg_list, &value);
if (ACPI_FAILURE(status)) {
vdbg_printk(FUJLAPTOP_DBG_ERROR,
"FUNC interface is not present\n");
vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate FUNC\n");
return -ENODEV;
}
params[0].integer.value = cmd;
params[1].integer.value = arg0;
params[2].integer.value = arg1;
params[3].integer.value = arg2;
status = acpi_evaluate_integer(handle, NULL, &arg_list, &value);
if (ACPI_FAILURE(status)) {
vdbg_printk(FUJLAPTOP_DBG_WARN,
"FUNC 0x%x (args 0x%x, 0x%x, 0x%x) call failed\n",
cmd, arg0, arg1, arg2);
return -ENODEV;
}
vdbg_printk(FUJLAPTOP_DBG_TRACE,
"FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
cmd, arg0, arg1, arg2, (int)value);
vdbg_printk(FUJLAPTOP_DBG_TRACE, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
func, op, feature, state, (int)value);
return value;
}
#if IS_ENABLED(CONFIG_LEDS_CLASS)
/* LED class callbacks */
static int logolamp_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
int poweron = FUNC_LED_ON, always = FUNC_LED_ON;
int ret;
if (brightness < LED_HALF)
poweron = FUNC_LED_OFF;
if (brightness < LED_FULL)
always = FUNC_LED_OFF;
ret = call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron);
if (ret < 0)
return ret;
return call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always);
}
static int kblamps_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
if (brightness >= LED_FULL)
return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_ON);
else
return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF);
}
static int radio_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
if (brightness >= LED_FULL)
return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, RADIO_LED_ON);
else
return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, 0x0);
}
static int eco_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
int curr;
curr = call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0);
if (brightness >= LED_FULL)
return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, curr | ECO_LED_ON);
else
return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, curr & ~ECO_LED_ON);
}
static enum led_brightness logolamp_get(struct led_classdev *cdev)
{
int ret;
ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
if (ret == FUNC_LED_ON)
return LED_FULL;
ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
if (ret == FUNC_LED_ON)
return LED_HALF;
return LED_OFF;
}
static enum led_brightness kblamps_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
if (call_fext_func(FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
brightness = LED_FULL;
return brightness;
}
static enum led_brightness radio_led_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
if (call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON)
brightness = LED_FULL;
return brightness;
}
static enum led_brightness eco_led_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
if (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON)
brightness = LED_FULL;
return brightness;
}
#endif
/* Hardware access for LCD brightness control */
static int set_lcd_level(int level)
{
acpi_status status = AE_OK;
acpi_handle handle = NULL;
vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBLL [%d]\n",
level);
acpi_status status;
char *method;
if (level < 0 || level >= fujitsu_bl->max_brightness)
return -EINVAL;
status = acpi_get_handle(fujitsu_bl->acpi_handle, "SBLL", &handle);
if (ACPI_FAILURE(status)) {
vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBLL not present\n");
return -ENODEV;
switch (use_alt_lcd_levels) {
case -1:
if (acpi_has_method(fujitsu_bl->acpi_handle, "SBL2"))
method = "SBL2";
else
method = "SBLL";
break;
case 1:
method = "SBL2";
break;
default:
method = "SBLL";
break;
}
status = acpi_execute_simple_method(handle, NULL, level);
if (ACPI_FAILURE(status))
return -ENODEV;
return 0;
}
static int set_lcd_level_alt(int level)
{
acpi_status status = AE_OK;
acpi_handle handle = NULL;
vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBL2 [%d]\n",
level);
vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via %s [%d]\n",
method, level);
if (level < 0 || level >= fujitsu_bl->max_brightness)
return -EINVAL;
status = acpi_get_handle(fujitsu_bl->acpi_handle, "SBL2", &handle);
status = acpi_execute_simple_method(fujitsu_bl->acpi_handle, method,
level);
if (ACPI_FAILURE(status)) {
vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBL2 not present\n");
vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate %s\n",
method);
return -ENODEV;
}
status = acpi_execute_simple_method(handle, NULL, level);
if (ACPI_FAILURE(status))
return -ENODEV;
fujitsu_bl->brightness_level = level;
return 0;
}
......@@ -429,11 +242,6 @@ static int get_lcd_level(void)
fujitsu_bl->brightness_level = state & 0x0fffffff;
if (state & 0x80000000)
fujitsu_bl->brightness_changed = 1;
else
fujitsu_bl->brightness_changed = 0;
return fujitsu_bl->brightness_level;
}
......@@ -458,30 +266,17 @@ static int get_max_brightness(void)
static int bl_get_brightness(struct backlight_device *b)
{
return get_lcd_level();
return b->props.power == FB_BLANK_POWERDOWN ? 0 : get_lcd_level();
}
static int bl_update_status(struct backlight_device *b)
{
int ret;
if (b->props.power == FB_BLANK_POWERDOWN)
ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3);
call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3);
else
ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0);
if (ret != 0)
vdbg_printk(FUJLAPTOP_DBG_ERROR,
"Unable to adjust backlight power, error code %i\n",
ret);
if (use_alt_lcd_levels)
ret = set_lcd_level_alt(b->props.brightness);
else
ret = set_lcd_level(b->props.brightness);
if (ret != 0)
vdbg_printk(FUJLAPTOP_DBG_ERROR,
"Unable to adjust LCD brightness, error code %i\n",
ret);
return ret;
call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0);
return set_lcd_level(b->props.brightness);
}
static const struct backlight_ops fujitsu_bl_ops = {
......@@ -489,84 +284,8 @@ static const struct backlight_ops fujitsu_bl_ops = {
.update_status = bl_update_status,
};
/* Platform LCD brightness device */
static ssize_t
show_max_brightness(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
ret = get_max_brightness();
if (ret < 0)
return ret;
return sprintf(buf, "%i\n", ret);
}
static ssize_t
show_brightness_changed(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
ret = fujitsu_bl->brightness_changed;
if (ret < 0)
return ret;
return sprintf(buf, "%i\n", ret);
}
static ssize_t show_lcd_level(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
ret = get_lcd_level();
if (ret < 0)
return ret;
return sprintf(buf, "%i\n", ret);
}
static ssize_t store_lcd_level(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
int level, ret;
if (sscanf(buf, "%i", &level) != 1
|| (level < 0 || level >= fujitsu_bl->max_brightness))
return -EINVAL;
if (use_alt_lcd_levels)
ret = set_lcd_level_alt(level);
else
ret = set_lcd_level(level);
if (ret < 0)
return ret;
ret = get_lcd_level();
if (ret < 0)
return ret;
return count;
}
static ssize_t
ignore_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
return count;
}
static ssize_t
show_lid_state(struct device *dev,
struct device_attribute *attr, char *buf)
static ssize_t lid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
if (!(fujitsu_laptop->flags_supported & FLAG_LID))
return sprintf(buf, "unknown\n");
......@@ -576,9 +295,8 @@ show_lid_state(struct device *dev,
return sprintf(buf, "closed\n");
}
static ssize_t
show_dock_state(struct device *dev,
struct device_attribute *attr, char *buf)
static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
if (!(fujitsu_laptop->flags_supported & FLAG_DOCK))
return sprintf(buf, "unknown\n");
......@@ -588,9 +306,8 @@ show_dock_state(struct device *dev,
return sprintf(buf, "undocked\n");
}
static ssize_t
show_radios_state(struct device *dev,
struct device_attribute *attr, char *buf)
static ssize_t radios_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
if (!(fujitsu_laptop->flags_supported & FLAG_RFKILL))
return sprintf(buf, "unknown\n");
......@@ -600,18 +317,11 @@ show_radios_state(struct device *dev,
return sprintf(buf, "killed\n");
}
static DEVICE_ATTR(max_brightness, 0444, show_max_brightness, ignore_store);
static DEVICE_ATTR(brightness_changed, 0444, show_brightness_changed,
ignore_store);
static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
static DEVICE_ATTR(lid, 0444, show_lid_state, ignore_store);
static DEVICE_ATTR(dock, 0444, show_dock_state, ignore_store);
static DEVICE_ATTR(radios, 0444, show_radios_state, ignore_store);
static DEVICE_ATTR_RO(lid);
static DEVICE_ATTR_RO(dock);
static DEVICE_ATTR_RO(radios);
static struct attribute *fujitsu_pf_attributes[] = {
&dev_attr_brightness_changed.attr,
&dev_attr_max_brightness.attr,
&dev_attr_lcd_level.attr,
&dev_attr_lid.attr,
&dev_attr_dock.attr,
&dev_attr_radios.attr,
......@@ -628,224 +338,438 @@ static struct platform_driver fujitsu_pf_driver = {
}
};
static void __init dmi_check_cb_common(const struct dmi_system_id *id)
/* ACPI device for LCD brightness control */
static const struct key_entry keymap_backlight[] = {
{ KE_KEY, true, { KEY_BRIGHTNESSUP } },
{ KE_KEY, false, { KEY_BRIGHTNESSDOWN } },
{ KE_END, 0 }
};
static int acpi_fujitsu_bl_input_setup(struct acpi_device *device)
{
pr_info("Identified laptop model '%s'\n", id->ident);
struct fujitsu_bl *fujitsu_bl = acpi_driver_data(device);
int ret;
fujitsu_bl->input = devm_input_allocate_device(&device->dev);
if (!fujitsu_bl->input)
return -ENOMEM;
snprintf(fujitsu_bl->phys, sizeof(fujitsu_bl->phys),
"%s/video/input0", acpi_device_hid(device));
fujitsu_bl->input->name = acpi_device_name(device);
fujitsu_bl->input->phys = fujitsu_bl->phys;
fujitsu_bl->input->id.bustype = BUS_HOST;
fujitsu_bl->input->id.product = 0x06;
ret = sparse_keymap_setup(fujitsu_bl->input, keymap_backlight, NULL);
if (ret)
return ret;
return input_register_device(fujitsu_bl->input);
}
static int __init dmi_check_cb_s6410(const struct dmi_system_id *id)
static int fujitsu_backlight_register(struct acpi_device *device)
{
dmi_check_cb_common(id);
fujitsu_bl->keycode1 = KEY_SCREENLOCK; /* "Lock" */
fujitsu_bl->keycode2 = KEY_HELP; /* "Mobility Center" */
return 1;
const struct backlight_properties props = {
.brightness = fujitsu_bl->brightness_level,
.max_brightness = fujitsu_bl->max_brightness - 1,
.type = BACKLIGHT_PLATFORM
};
struct backlight_device *bd;
bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop",
&device->dev, NULL,
&fujitsu_bl_ops, &props);
if (IS_ERR(bd))
return PTR_ERR(bd);
fujitsu_bl->bl_device = bd;
return 0;
}
static int __init dmi_check_cb_s6420(const struct dmi_system_id *id)
static int acpi_fujitsu_bl_add(struct acpi_device *device)
{
dmi_check_cb_common(id);
fujitsu_bl->keycode1 = KEY_SCREENLOCK; /* "Lock" */
fujitsu_bl->keycode2 = KEY_HELP; /* "Mobility Center" */
return 1;
int state = 0;
int error;
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
return -ENODEV;
if (!device)
return -EINVAL;
fujitsu_bl->acpi_handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_BL_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
device->driver_data = fujitsu_bl;
error = acpi_fujitsu_bl_input_setup(device);
if (error)
return error;
error = acpi_bus_update_power(fujitsu_bl->acpi_handle, &state);
if (error) {
pr_err("Error reading power state\n");
return error;
}
pr_info("ACPI: %s [%s] (%s)\n",
acpi_device_name(device), acpi_device_bid(device),
!device->power.state ? "on" : "off");
if (acpi_has_method(device->handle, METHOD_NAME__INI)) {
vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
if (ACPI_FAILURE
(acpi_evaluate_object
(device->handle, METHOD_NAME__INI, NULL, NULL)))
pr_err("_INI Method failed\n");
}
if (get_max_brightness() <= 0)
fujitsu_bl->max_brightness = FUJITSU_LCD_N_LEVELS;
get_lcd_level();
error = fujitsu_backlight_register(device);
if (error)
return error;
return 0;
}
/* Brightness notify */
static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event)
{
struct input_dev *input;
int oldb, newb;
input = fujitsu_bl->input;
if (event != ACPI_FUJITSU_NOTIFY_CODE1) {
vdbg_printk(FUJLAPTOP_DBG_WARN,
"unsupported event [0x%x]\n", event);
sparse_keymap_report_event(input, -1, 1, true);
return;
}
oldb = fujitsu_bl->brightness_level;
get_lcd_level();
newb = fujitsu_bl->brightness_level;
vdbg_printk(FUJLAPTOP_DBG_TRACE, "brightness button event [%i -> %i]\n",
oldb, newb);
if (oldb == newb)
return;
if (!disable_brightness_adjust)
set_lcd_level(newb);
sparse_keymap_report_event(input, oldb < newb, 1, true);
}
static int __init dmi_check_cb_p8010(const struct dmi_system_id *id)
/* ACPI device for hotkey handling */
static const struct key_entry keymap_default[] = {
{ KE_KEY, KEY1_CODE, { KEY_PROG1 } },
{ KE_KEY, KEY2_CODE, { KEY_PROG2 } },
{ KE_KEY, KEY3_CODE, { KEY_PROG3 } },
{ KE_KEY, KEY4_CODE, { KEY_PROG4 } },
{ KE_KEY, KEY5_CODE, { KEY_RFKILL } },
{ KE_KEY, BIT(26), { KEY_TOUCHPAD_TOGGLE } },
{ KE_END, 0 }
};
static const struct key_entry keymap_s64x0[] = {
{ KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } }, /* "Lock" */
{ KE_KEY, KEY2_CODE, { KEY_HELP } }, /* "Mobility Center */
{ KE_KEY, KEY3_CODE, { KEY_PROG3 } },
{ KE_KEY, KEY4_CODE, { KEY_PROG4 } },
{ KE_END, 0 }
};
static const struct key_entry keymap_p8010[] = {
{ KE_KEY, KEY1_CODE, { KEY_HELP } }, /* "Support" */
{ KE_KEY, KEY2_CODE, { KEY_PROG2 } },
{ KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } }, /* "Presentation" */
{ KE_KEY, KEY4_CODE, { KEY_WWW } }, /* "WWW" */
{ KE_END, 0 }
};
static const struct key_entry *keymap = keymap_default;
static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id)
{
dmi_check_cb_common(id);
fujitsu_bl->keycode1 = KEY_HELP; /* "Support" */
fujitsu_bl->keycode3 = KEY_SWITCHVIDEOMODE; /* "Presentation" */
fujitsu_bl->keycode4 = KEY_WWW; /* "Internet" */
pr_info("Identified laptop model '%s'\n", id->ident);
keymap = id->driver_data;
return 1;
}
static const struct dmi_system_id fujitsu_dmi_table[] __initconst = {
static const struct dmi_system_id fujitsu_laptop_dmi_table[] = {
{
.callback = fujitsu_laptop_dmi_keymap_override,
.ident = "Fujitsu Siemens S6410",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"),
},
.callback = dmi_check_cb_s6410},
.driver_data = (void *)keymap_s64x0
},
{
.callback = fujitsu_laptop_dmi_keymap_override,
.ident = "Fujitsu Siemens S6420",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"),
},
.callback = dmi_check_cb_s6420},
.driver_data = (void *)keymap_s64x0
},
{
.callback = fujitsu_laptop_dmi_keymap_override,
.ident = "Fujitsu LifeBook P8010",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"),
},
.callback = dmi_check_cb_p8010},
.driver_data = (void *)keymap_p8010
},
{}
};
/* ACPI device for LCD brightness control */
static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device)
{
struct fujitsu_laptop *fujitsu_laptop = acpi_driver_data(device);
int ret;
static int acpi_fujitsu_bl_add(struct acpi_device *device)
fujitsu_laptop->input = devm_input_allocate_device(&device->dev);
if (!fujitsu_laptop->input)
return -ENOMEM;
snprintf(fujitsu_laptop->phys, sizeof(fujitsu_laptop->phys),
"%s/video/input0", acpi_device_hid(device));
fujitsu_laptop->input->name = acpi_device_name(device);
fujitsu_laptop->input->phys = fujitsu_laptop->phys;
fujitsu_laptop->input->id.bustype = BUS_HOST;
fujitsu_laptop->input->id.product = 0x06;
dmi_check_system(fujitsu_laptop_dmi_table);
ret = sparse_keymap_setup(fujitsu_laptop->input, keymap, NULL);
if (ret)
return ret;
return input_register_device(fujitsu_laptop->input);
}
static int fujitsu_laptop_platform_add(void)
{
int ret;
fujitsu_laptop->pf_device = platform_device_alloc("fujitsu-laptop", -1);
if (!fujitsu_laptop->pf_device)
return -ENOMEM;
ret = platform_device_add(fujitsu_laptop->pf_device);
if (ret)
goto err_put_platform_device;
ret = sysfs_create_group(&fujitsu_laptop->pf_device->dev.kobj,
&fujitsu_pf_attribute_group);
if (ret)
goto err_del_platform_device;
return 0;
err_del_platform_device:
platform_device_del(fujitsu_laptop->pf_device);
err_put_platform_device:
platform_device_put(fujitsu_laptop->pf_device);
return ret;
}
static void fujitsu_laptop_platform_remove(void)
{
sysfs_remove_group(&fujitsu_laptop->pf_device->dev.kobj,
&fujitsu_pf_attribute_group);
platform_device_unregister(fujitsu_laptop->pf_device);
}
static int logolamp_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
int state = 0;
struct input_dev *input;
int error;
int poweron = FUNC_LED_ON, always = FUNC_LED_ON;
int ret;
if (!device)
return -EINVAL;
if (brightness < LED_HALF)
poweron = FUNC_LED_OFF;
fujitsu_bl->acpi_handle = device->handle;
sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_BL_DEVICE_NAME);
sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
device->driver_data = fujitsu_bl;
if (brightness < LED_FULL)
always = FUNC_LED_OFF;
fujitsu_bl->input = input = input_allocate_device();
if (!input) {
error = -ENOMEM;
goto err_stop;
}
ret = call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron);
if (ret < 0)
return ret;
snprintf(fujitsu_bl->phys, sizeof(fujitsu_bl->phys),
"%s/video/input0", acpi_device_hid(device));
return call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always);
}
input->name = acpi_device_name(device);
input->phys = fujitsu_bl->phys;
input->id.bustype = BUS_HOST;
input->id.product = 0x06;
input->dev.parent = &device->dev;
input->evbit[0] = BIT(EV_KEY);
set_bit(KEY_BRIGHTNESSUP, input->keybit);
set_bit(KEY_BRIGHTNESSDOWN, input->keybit);
set_bit(KEY_UNKNOWN, input->keybit);
error = input_register_device(input);
if (error)
goto err_free_input_dev;
static enum led_brightness logolamp_get(struct led_classdev *cdev)
{
int ret;
error = acpi_bus_update_power(fujitsu_bl->acpi_handle, &state);
if (error) {
pr_err("Error reading power state\n");
goto err_unregister_input_dev;
}
ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
if (ret == FUNC_LED_ON)
return LED_FULL;
pr_info("ACPI: %s [%s] (%s)\n",
acpi_device_name(device), acpi_device_bid(device),
!device->power.state ? "on" : "off");
ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
if (ret == FUNC_LED_ON)
return LED_HALF;
fujitsu_bl->dev = device;
return LED_OFF;
}
if (acpi_has_method(device->handle, METHOD_NAME__INI)) {
vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
if (ACPI_FAILURE
(acpi_evaluate_object
(device->handle, METHOD_NAME__INI, NULL, NULL)))
pr_err("_INI Method failed\n");
}
static struct led_classdev logolamp_led = {
.name = "fujitsu::logolamp",
.brightness_set_blocking = logolamp_set,
.brightness_get = logolamp_get
};
if (use_alt_lcd_levels == -1) {
if (acpi_has_method(NULL, "\\_SB.PCI0.LPCB.FJEX.SBL2"))
use_alt_lcd_levels = 1;
static int kblamps_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
if (brightness >= LED_FULL)
return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
FUNC_LED_ON);
else
use_alt_lcd_levels = 0;
vdbg_printk(FUJLAPTOP_DBG_TRACE, "auto-detected usealt as %i\n",
use_alt_lcd_levels);
}
/* do config (detect defaults) */
use_alt_lcd_levels = use_alt_lcd_levels == 1 ? 1 : 0;
disable_brightness_adjust = disable_brightness_adjust == 1 ? 1 : 0;
vdbg_printk(FUJLAPTOP_DBG_INFO,
"config: [alt interface: %d], [adjust disable: %d]\n",
use_alt_lcd_levels, disable_brightness_adjust);
return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
FUNC_LED_OFF);
}
if (get_max_brightness() <= 0)
fujitsu_bl->max_brightness = FUJITSU_LCD_N_LEVELS;
get_lcd_level();
static enum led_brightness kblamps_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
return 0;
if (call_fext_func(FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
brightness = LED_FULL;
err_unregister_input_dev:
input_unregister_device(input);
input = NULL;
err_free_input_dev:
input_free_device(input);
err_stop:
return error;
return brightness;
}
static int acpi_fujitsu_bl_remove(struct acpi_device *device)
static struct led_classdev kblamps_led = {
.name = "fujitsu::kblamps",
.brightness_set_blocking = kblamps_set,
.brightness_get = kblamps_get
};
static int radio_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct fujitsu_bl *fujitsu_bl = acpi_driver_data(device);
struct input_dev *input = fujitsu_bl->input;
if (brightness >= LED_FULL)
return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON,
RADIO_LED_ON);
else
return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, 0x0);
}
input_unregister_device(input);
static enum led_brightness radio_led_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
fujitsu_bl->acpi_handle = NULL;
if (call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON)
brightness = LED_FULL;
return 0;
return brightness;
}
/* Brightness notify */
static struct led_classdev radio_led = {
.name = "fujitsu::radio_led",
.brightness_set_blocking = radio_led_set,
.brightness_get = radio_led_get,
.default_trigger = "rfkill-any"
};
static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event)
static int eco_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct input_dev *input;
int keycode;
int oldb, newb;
int curr;
input = fujitsu_bl->input;
curr = call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0);
if (brightness >= LED_FULL)
return call_fext_func(FUNC_LEDS, 0x1, ECO_LED,
curr | ECO_LED_ON);
else
return call_fext_func(FUNC_LEDS, 0x1, ECO_LED,
curr & ~ECO_LED_ON);
}
switch (event) {
case ACPI_FUJITSU_NOTIFY_CODE1:
keycode = 0;
oldb = fujitsu_bl->brightness_level;
get_lcd_level();
newb = fujitsu_bl->brightness_level;
static enum led_brightness eco_led_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
vdbg_printk(FUJLAPTOP_DBG_TRACE,
"brightness button event [%i -> %i (%i)]\n",
oldb, newb, fujitsu_bl->brightness_changed);
if (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON)
brightness = LED_FULL;
if (oldb < newb) {
if (disable_brightness_adjust != 1) {
if (use_alt_lcd_levels)
set_lcd_level_alt(newb);
else
set_lcd_level(newb);
}
keycode = KEY_BRIGHTNESSUP;
} else if (oldb > newb) {
if (disable_brightness_adjust != 1) {
if (use_alt_lcd_levels)
set_lcd_level_alt(newb);
else
set_lcd_level(newb);
return brightness;
}
static struct led_classdev eco_led = {
.name = "fujitsu::eco_led",
.brightness_set_blocking = eco_led_set,
.brightness_get = eco_led_get
};
static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
{
int result;
if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
result = devm_led_classdev_register(&device->dev,
&logolamp_led);
if (result)
return result;
}
keycode = KEY_BRIGHTNESSDOWN;
if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
(call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
result = devm_led_classdev_register(&device->dev, &kblamps_led);
if (result)
return result;
}
break;
default:
keycode = KEY_UNKNOWN;
vdbg_printk(FUJLAPTOP_DBG_WARN,
"unsupported event [0x%x]\n", event);
break;
/*
* BTNI bit 24 seems to indicate the presence of a radio toggle
* button in place of a slide switch, and all such machines appear
* to also have an RF LED. Therefore use bit 24 as an indicator
* that an RF LED is present.
*/
if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) {
result = devm_led_classdev_register(&device->dev, &radio_led);
if (result)
return result;
}
if (keycode != 0) {
input_report_key(input, keycode, 1);
input_sync(input);
input_report_key(input, keycode, 0);
input_sync(input);
/* Support for eco led is not always signaled in bit corresponding
* to the bit used to control the led. According to the DSDT table,
* bit 14 seems to indicate presence of said led as well.
* Confirm by testing the status.
*/
if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) &&
(call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) {
result = devm_led_classdev_register(&device->dev, &eco_led);
if (result)
return result;
}
}
/* ACPI device for hotkey handling */
return 0;
}
static int acpi_fujitsu_laptop_add(struct acpi_device *device)
{
int result = 0;
int state = 0;
struct input_dev *input;
int error;
int i;
......@@ -867,38 +791,14 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
goto err_stop;
}
fujitsu_laptop->input = input = input_allocate_device();
if (!input) {
error = -ENOMEM;
goto err_free_fifo;
}
snprintf(fujitsu_laptop->phys, sizeof(fujitsu_laptop->phys),
"%s/video/input0", acpi_device_hid(device));
input->name = acpi_device_name(device);
input->phys = fujitsu_laptop->phys;
input->id.bustype = BUS_HOST;
input->id.product = 0x06;
input->dev.parent = &device->dev;
set_bit(EV_KEY, input->evbit);
set_bit(fujitsu_bl->keycode1, input->keybit);
set_bit(fujitsu_bl->keycode2, input->keybit);
set_bit(fujitsu_bl->keycode3, input->keybit);
set_bit(fujitsu_bl->keycode4, input->keybit);
set_bit(fujitsu_bl->keycode5, input->keybit);
set_bit(KEY_TOUCHPAD_TOGGLE, input->keybit);
set_bit(KEY_UNKNOWN, input->keybit);
error = input_register_device(input);
error = acpi_fujitsu_laptop_input_setup(device);
if (error)
goto err_free_input_dev;
goto err_free_fifo;
error = acpi_bus_update_power(fujitsu_laptop->acpi_handle, &state);
if (error) {
pr_err("Error reading power state\n");
goto err_unregister_input_dev;
goto err_free_fifo;
}
pr_info("ACPI: %s [%s] (%s)\n",
......@@ -936,72 +836,25 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
/* Suspect this is a keymap of the application panel, print it */
pr_info("BTNI: [0x%x]\n", call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0));
#if IS_ENABLED(CONFIG_LEDS_CLASS)
if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
result = led_classdev_register(&fujitsu_bl->pf_device->dev,
&logolamp_led);
if (result == 0) {
fujitsu_laptop->logolamp_registered = 1;
} else {
pr_err("Could not register LED handler for logo lamp, error %i\n",
result);
}
}
if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
(call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
result = led_classdev_register(&fujitsu_bl->pf_device->dev,
&kblamps_led);
if (result == 0) {
fujitsu_laptop->kblamps_registered = 1;
} else {
pr_err("Could not register LED handler for keyboard lamps, error %i\n",
result);
}
/* Sync backlight power status */
if (fujitsu_bl->bl_device &&
acpi_video_get_backlight_type() == acpi_backlight_vendor) {
if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN;
else
fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK;
}
/*
* BTNI bit 24 seems to indicate the presence of a radio toggle
* button in place of a slide switch, and all such machines appear
* to also have an RF LED. Therefore use bit 24 as an indicator
* that an RF LED is present.
*/
if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) {
result = led_classdev_register(&fujitsu_bl->pf_device->dev,
&radio_led);
if (result == 0) {
fujitsu_laptop->radio_led_registered = 1;
} else {
pr_err("Could not register LED handler for radio LED, error %i\n",
result);
}
}
error = acpi_fujitsu_laptop_leds_register(device);
if (error)
goto err_free_fifo;
/* Support for eco led is not always signaled in bit corresponding
* to the bit used to control the led. According to the DSDT table,
* bit 14 seems to indicate presence of said led as well.
* Confirm by testing the status.
*/
if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) &&
(call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) {
result = led_classdev_register(&fujitsu_bl->pf_device->dev,
&eco_led);
if (result == 0) {
fujitsu_laptop->eco_led_registered = 1;
} else {
pr_err("Could not register LED handler for eco LED, error %i\n",
result);
}
}
#endif
error = fujitsu_laptop_platform_add();
if (error)
goto err_free_fifo;
return result;
return 0;
err_unregister_input_dev:
input_unregister_device(input);
input = NULL;
err_free_input_dev:
input_free_device(input);
err_free_fifo:
kfifo_free(&fujitsu_laptop->fifo);
err_stop:
......@@ -1011,86 +864,62 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
static int acpi_fujitsu_laptop_remove(struct acpi_device *device)
{
struct fujitsu_laptop *fujitsu_laptop = acpi_driver_data(device);
struct input_dev *input = fujitsu_laptop->input;
#if IS_ENABLED(CONFIG_LEDS_CLASS)
if (fujitsu_laptop->logolamp_registered)
led_classdev_unregister(&logolamp_led);
if (fujitsu_laptop->kblamps_registered)
led_classdev_unregister(&kblamps_led);
if (fujitsu_laptop->radio_led_registered)
led_classdev_unregister(&radio_led);
if (fujitsu_laptop->eco_led_registered)
led_classdev_unregister(&eco_led);
#endif
input_unregister_device(input);
fujitsu_laptop_platform_remove();
kfifo_free(&fujitsu_laptop->fifo);
fujitsu_laptop->acpi_handle = NULL;
return 0;
}
static void acpi_fujitsu_laptop_press(int keycode)
static void acpi_fujitsu_laptop_press(int scancode)
{
struct input_dev *input = fujitsu_laptop->input;
int status;
status = kfifo_in_locked(&fujitsu_laptop->fifo,
(unsigned char *)&keycode, sizeof(keycode),
(unsigned char *)&scancode, sizeof(scancode),
&fujitsu_laptop->fifo_lock);
if (status != sizeof(keycode)) {
if (status != sizeof(scancode)) {
vdbg_printk(FUJLAPTOP_DBG_WARN,
"Could not push keycode [0x%x]\n", keycode);
"Could not push scancode [0x%x]\n", scancode);
return;
}
input_report_key(input, keycode, 1);
input_sync(input);
sparse_keymap_report_event(input, scancode, 1, false);
vdbg_printk(FUJLAPTOP_DBG_TRACE,
"Push keycode into ringbuffer [%d]\n", keycode);
"Push scancode into ringbuffer [0x%x]\n", scancode);
}
static void acpi_fujitsu_laptop_release(void)
{
struct input_dev *input = fujitsu_laptop->input;
int keycode, status;
int scancode, status;
while (true) {
status = kfifo_out_locked(&fujitsu_laptop->fifo,
(unsigned char *)&keycode,
sizeof(keycode),
(unsigned char *)&scancode,
sizeof(scancode),
&fujitsu_laptop->fifo_lock);
if (status != sizeof(keycode))
if (status != sizeof(scancode))
return;
input_report_key(input, keycode, 0);
input_sync(input);
sparse_keymap_report_event(input, scancode, 0, false);
vdbg_printk(FUJLAPTOP_DBG_TRACE,
"Pop keycode from ringbuffer [%d]\n", keycode);
"Pop scancode from ringbuffer [0x%x]\n", scancode);
}
}
static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event)
{
struct input_dev *input;
int keycode;
unsigned int irb = 1;
int i;
int scancode, i = 0;
unsigned int irb;
input = fujitsu_laptop->input;
if (event != ACPI_FUJITSU_NOTIFY_CODE1) {
keycode = KEY_UNKNOWN;
vdbg_printk(FUJLAPTOP_DBG_WARN,
"Unsupported event [0x%x]\n", event);
input_report_key(input, keycode, 1);
input_sync(input);
input_report_key(input, keycode, 0);
input_sync(input);
sparse_keymap_report_event(input, -1, 1, true);
return;
}
......@@ -1098,40 +927,16 @@ static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event)
fujitsu_laptop->flags_state =
call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0);
i = 0;
while ((irb =
call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0
&& (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) {
switch (irb & 0x4ff) {
case KEY1_CODE:
keycode = fujitsu_bl->keycode1;
break;
case KEY2_CODE:
keycode = fujitsu_bl->keycode2;
break;
case KEY3_CODE:
keycode = fujitsu_bl->keycode3;
break;
case KEY4_CODE:
keycode = fujitsu_bl->keycode4;
break;
case KEY5_CODE:
keycode = fujitsu_bl->keycode5;
break;
case 0:
keycode = 0;
break;
default:
while ((irb = call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 &&
i++ < MAX_HOTKEY_RINGBUFFER_SIZE) {
scancode = irb & 0x4ff;
if (sparse_keymap_entry_from_scancode(input, scancode))
acpi_fujitsu_laptop_press(scancode);
else if (scancode == 0)
acpi_fujitsu_laptop_release();
else
vdbg_printk(FUJLAPTOP_DBG_WARN,
"Unknown GIRB result [%x]\n", irb);
keycode = -1;
break;
}
if (keycode > 0)
acpi_fujitsu_laptop_press(keycode);
else if (keycode == 0)
acpi_fujitsu_laptop_release();
}
/* On some models (first seen on the Skylake-based Lifebook
......@@ -1139,14 +944,8 @@ static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event)
* handled in software; its state is queried using FUNC_FLAGS
*/
if ((fujitsu_laptop->flags_supported & BIT(26)) &&
(call_fext_func(FUNC_FLAGS, 0x1, 0x0, 0x0) & BIT(26))) {
keycode = KEY_TOUCHPAD_TOGGLE;
input_report_key(input, keycode, 1);
input_sync(input);
input_report_key(input, keycode, 0);
input_sync(input);
}
(call_fext_func(FUNC_FLAGS, 0x1, 0x0, 0x0) & BIT(26)))
sparse_keymap_report_event(input, BIT(26), 1, true);
}
/* Initialization */
......@@ -1162,7 +961,6 @@ static struct acpi_driver acpi_fujitsu_bl_driver = {
.ids = fujitsu_bl_device_ids,
.ops = {
.add = acpi_fujitsu_bl_add,
.remove = acpi_fujitsu_bl_remove,
.notify = acpi_fujitsu_bl_notify,
},
};
......@@ -1192,7 +990,7 @@ MODULE_DEVICE_TABLE(acpi, fujitsu_ids);
static int __init fujitsu_init(void)
{
int ret, max_brightness;
int ret;
if (acpi_disabled)
return -ENODEV;
......@@ -1200,100 +998,40 @@ static int __init fujitsu_init(void)
fujitsu_bl = kzalloc(sizeof(struct fujitsu_bl), GFP_KERNEL);
if (!fujitsu_bl)
return -ENOMEM;
fujitsu_bl->keycode1 = KEY_PROG1;
fujitsu_bl->keycode2 = KEY_PROG2;
fujitsu_bl->keycode3 = KEY_PROG3;
fujitsu_bl->keycode4 = KEY_PROG4;
fujitsu_bl->keycode5 = KEY_RFKILL;
dmi_check_system(fujitsu_dmi_table);
ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver);
if (ret)
goto fail_acpi;
goto err_free_fujitsu_bl;
/* Register platform stuff */
fujitsu_bl->pf_device = platform_device_alloc("fujitsu-laptop", -1);
if (!fujitsu_bl->pf_device) {
ret = -ENOMEM;
goto fail_platform_driver;
}
ret = platform_device_add(fujitsu_bl->pf_device);
if (ret)
goto fail_platform_device1;
ret =
sysfs_create_group(&fujitsu_bl->pf_device->dev.kobj,
&fujitsu_pf_attribute_group);
if (ret)
goto fail_platform_device2;
/* Register backlight stuff */
if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
struct backlight_properties props;
memset(&props, 0, sizeof(struct backlight_properties));
max_brightness = fujitsu_bl->max_brightness;
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = max_brightness - 1;
fujitsu_bl->bl_device = backlight_device_register("fujitsu-laptop",
NULL, NULL,
&fujitsu_bl_ops,
&props);
if (IS_ERR(fujitsu_bl->bl_device)) {
ret = PTR_ERR(fujitsu_bl->bl_device);
fujitsu_bl->bl_device = NULL;
goto fail_sysfs_group;
}
fujitsu_bl->bl_device->props.brightness = fujitsu_bl->brightness_level;
}
ret = platform_driver_register(&fujitsu_pf_driver);
if (ret)
goto fail_backlight;
goto err_unregister_acpi;
/* Register laptop driver */
fujitsu_laptop = kzalloc(sizeof(struct fujitsu_laptop), GFP_KERNEL);
if (!fujitsu_laptop) {
ret = -ENOMEM;
goto fail_laptop;
goto err_unregister_platform_driver;
}
ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver);
if (ret)
goto fail_laptop1;
/* Sync backlight power status (needs FUJ02E3 device, hence deferred) */
if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN;
else
fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK;
}
goto err_free_fujitsu_laptop;
pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n");
return 0;
fail_laptop1:
err_free_fujitsu_laptop:
kfree(fujitsu_laptop);
fail_laptop:
err_unregister_platform_driver:
platform_driver_unregister(&fujitsu_pf_driver);
fail_backlight:
backlight_device_unregister(fujitsu_bl->bl_device);
fail_sysfs_group:
sysfs_remove_group(&fujitsu_bl->pf_device->dev.kobj,
&fujitsu_pf_attribute_group);
fail_platform_device2:
platform_device_del(fujitsu_bl->pf_device);
fail_platform_device1:
platform_device_put(fujitsu_bl->pf_device);
fail_platform_driver:
err_unregister_acpi:
acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
fail_acpi:
err_free_fujitsu_bl:
kfree(fujitsu_bl);
return ret;
......@@ -1307,13 +1045,6 @@ static void __exit fujitsu_cleanup(void)
platform_driver_unregister(&fujitsu_pf_driver);
backlight_device_unregister(fujitsu_bl->bl_device);
sysfs_remove_group(&fujitsu_bl->pf_device->dev.kobj,
&fujitsu_pf_attribute_group);
platform_device_unregister(fujitsu_bl->pf_device);
acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
kfree(fujitsu_bl);
......@@ -1324,11 +1055,10 @@ static void __exit fujitsu_cleanup(void)
module_init(fujitsu_init);
module_exit(fujitsu_cleanup);
module_param(use_alt_lcd_levels, uint, 0644);
MODULE_PARM_DESC(use_alt_lcd_levels,
"Use alternative interface for lcd_levels (needed for Lifebook s6410).");
module_param(disable_brightness_adjust, uint, 0644);
MODULE_PARM_DESC(disable_brightness_adjust, "Disable brightness adjustment .");
module_param(use_alt_lcd_levels, int, 0644);
MODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)");
module_param(disable_brightness_adjust, bool, 0644);
MODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment");
#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
module_param_named(debug, dbg_level, uint, 0644);
MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
......
/*
* hp-wireless button for Windows 8
* Airplane mode button for HP & Xiaomi laptops
*
* Copyright (C) 2014 Alex Hung <alex.hung@canonical.com>
* Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -29,11 +29,13 @@
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex Hung");
MODULE_ALIAS("acpi*:HPQ6001:*");
MODULE_ALIAS("acpi*:WSTADEF:*");
static struct input_dev *hpwl_input_dev;
static const struct acpi_device_id hpwl_ids[] = {
{"HPQ6001", 0},
{"WSTADEF", 0},
{"", 0},
};
......@@ -108,23 +110,4 @@ static struct acpi_driver hpwl_driver = {
},
};
static int __init hpwl_init(void)
{
int err;
pr_info("Initializing HPQ6001 module\n");
err = acpi_bus_register_driver(&hpwl_driver);
if (err)
pr_err("Unable to register HP wireless control driver.\n");
return err;
}
static void __exit hpwl_exit(void)
{
pr_info("Exiting HPQ6001 module\n");
acpi_bus_unregister_driver(&hpwl_driver);
}
module_init(hpwl_init);
module_exit(hpwl_exit);
module_acpi_driver(hpwl_driver);
......@@ -48,35 +48,23 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
#define HPWMI_DISPLAY_QUERY 0x1
#define HPWMI_HDDTEMP_QUERY 0x2
#define HPWMI_ALS_QUERY 0x3
#define HPWMI_HARDWARE_QUERY 0x4
#define HPWMI_WIRELESS_QUERY 0x5
#define HPWMI_BIOS_QUERY 0x9
#define HPWMI_FEATURE_QUERY 0xb
#define HPWMI_HOTKEY_QUERY 0xc
#define HPWMI_FEATURE2_QUERY 0xd
#define HPWMI_WIRELESS2_QUERY 0x1b
#define HPWMI_POSTCODEERROR_QUERY 0x2a
enum hp_wmi_radio {
HPWMI_WIFI = 0,
HPWMI_BLUETOOTH = 1,
HPWMI_WWAN = 2,
HPWMI_GPS = 3,
HPWMI_WIFI = 0x0,
HPWMI_BLUETOOTH = 0x1,
HPWMI_WWAN = 0x2,
HPWMI_GPS = 0x3,
};
enum hp_wmi_event_ids {
HPWMI_DOCK_EVENT = 1,
HPWMI_PARK_HDD = 2,
HPWMI_SMART_ADAPTER = 3,
HPWMI_BEZEL_BUTTON = 4,
HPWMI_WIRELESS = 5,
HPWMI_CPU_BATTERY_THROTTLE = 6,
HPWMI_LOCK_SWITCH = 7,
HPWMI_LID_SWITCH = 8,
HPWMI_SCREEN_ROTATION = 9,
HPWMI_DOCK_EVENT = 0x01,
HPWMI_PARK_HDD = 0x02,
HPWMI_SMART_ADAPTER = 0x03,
HPWMI_BEZEL_BUTTON = 0x04,
HPWMI_WIRELESS = 0x05,
HPWMI_CPU_BATTERY_THROTTLE = 0x06,
HPWMI_LOCK_SWITCH = 0x07,
HPWMI_LID_SWITCH = 0x08,
HPWMI_SCREEN_ROTATION = 0x09,
HPWMI_COOLSENSE_SYSTEM_MOBILE = 0x0A,
HPWMI_COOLSENSE_SYSTEM_HOT = 0x0B,
HPWMI_PROXIMITY_SENSOR = 0x0C,
......@@ -93,6 +81,39 @@ struct bios_args {
u32 data;
};
enum hp_wmi_commandtype {
HPWMI_DISPLAY_QUERY = 0x01,
HPWMI_HDDTEMP_QUERY = 0x02,
HPWMI_ALS_QUERY = 0x03,
HPWMI_HARDWARE_QUERY = 0x04,
HPWMI_WIRELESS_QUERY = 0x05,
HPWMI_BATTERY_QUERY = 0x07,
HPWMI_BIOS_QUERY = 0x09,
HPWMI_FEATURE_QUERY = 0x0b,
HPWMI_HOTKEY_QUERY = 0x0c,
HPWMI_FEATURE2_QUERY = 0x0d,
HPWMI_WIRELESS2_QUERY = 0x1b,
HPWMI_POSTCODEERROR_QUERY = 0x2a,
};
enum hp_wmi_command {
HPWMI_READ = 0x01,
HPWMI_WRITE = 0x02,
HPWMI_ODM = 0x03,
};
enum hp_wmi_hardware_mask {
HPWMI_DOCK_MASK = 0x01,
HPWMI_TABLET_MASK = 0x04,
};
#define BIOS_ARGS_INIT(write, ctype, size) \
(struct bios_args) { .signature = 0x55434553, \
.command = (write) ? 0x2 : 0x1, \
.commandtype = (ctype), \
.datasize = (size), \
.data = 0 }
struct bios_return {
u32 sigpass;
u32 return_code;
......@@ -170,8 +191,8 @@ static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES];
/*
* hp_wmi_perform_query
*
* query: The commandtype -> What should be queried
* write: The command -> 0 read, 1 write, 3 ODM specific
* query: The commandtype (enum hp_wmi_commandtype)
* write: The command (enum hp_wmi_command)
* buffer: Buffer used as input and/or output
* insize: Size of input buffer
* outsize: Size of output buffer
......@@ -182,27 +203,27 @@ static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES];
* -EINVAL if the output buffer size exceeds buffersize
*
* Note: The buffersize must at least be the maximum of the input and output
* size. E.g. Battery info query (0x7) is defined to have 1 byte input
* size. E.g. Battery info query is defined to have 1 byte input
* and 128 byte output. The caller would do:
* buffer = kzalloc(128, GFP_KERNEL);
* ret = hp_wmi_perform_query(0x7, 0, buffer, 1, 128)
* ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ, buffer, 1, 128)
*/
static int hp_wmi_perform_query(int query, int write, void *buffer,
int insize, int outsize)
static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
void *buffer, int insize, int outsize)
{
struct bios_return *bios_return;
int actual_outsize;
union acpi_object *obj;
struct bios_args args = {
.signature = 0x55434553,
.command = write ? 0x2 : 0x1,
.command = command,
.commandtype = query,
.datasize = insize,
.data = 0,
};
struct acpi_buffer input = { sizeof(struct bios_args), &args };
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
u32 rc;
int ret = 0;
if (WARN_ON(insize > sizeof(args.data)))
return -EINVAL;
......@@ -214,91 +235,61 @@ static int hp_wmi_perform_query(int query, int write, void *buffer,
if (!obj)
return -EINVAL;
else if (obj->type != ACPI_TYPE_BUFFER) {
kfree(obj);
return -EINVAL;
if (obj->type != ACPI_TYPE_BUFFER) {
ret = -EINVAL;
goto out_free;
}
bios_return = (struct bios_return *)obj->buffer.pointer;
rc = bios_return->return_code;
ret = bios_return->return_code;
if (rc) {
if (rc != HPWMI_RET_UNKNOWN_CMDTYPE)
pr_warn("query 0x%x returned error 0x%x\n", query, rc);
kfree(obj);
return rc;
if (ret) {
if (ret != HPWMI_RET_UNKNOWN_CMDTYPE)
pr_warn("query 0x%x returned error 0x%x\n", query, ret);
goto out_free;
}
if (!outsize) {
/* ignore output data */
kfree(obj);
return 0;
}
/* Ignore output data of zero size */
if (!outsize)
goto out_free;
actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
memset(buffer + actual_outsize, 0, outsize - actual_outsize);
out_free:
kfree(obj);
return 0;
return ret;
}
static int hp_wmi_display_state(void)
static int hp_wmi_read_int(int query)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state,
sizeof(state), sizeof(state));
if (ret)
return -EINVAL;
return state;
}
int val = 0, ret;
static int hp_wmi_hddtemp_state(void)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, &state,
sizeof(state), sizeof(state));
if (ret)
return -EINVAL;
return state;
}
ret = hp_wmi_perform_query(query, HPWMI_READ, &val,
sizeof(val), sizeof(val));
static int hp_wmi_als_state(void)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, &state,
sizeof(state), sizeof(state));
if (ret)
return -EINVAL;
return state;
return ret < 0 ? ret : -EINVAL;
return val;
}
static int hp_wmi_dock_state(void)
static int hp_wmi_hw_state(int mask)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state,
sizeof(state), sizeof(state));
int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY);
if (ret)
return -EINVAL;
if (state < 0)
return state;
return state & 0x1;
}
static int hp_wmi_tablet_state(void)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state,
sizeof(state), sizeof(state));
if (ret)
return ret;
return (state & 0x4) ? 1 : 0;
}
static int __init hp_wmi_bios_2008_later(void)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, 0, &state,
int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state,
sizeof(state), sizeof(state));
if (!ret)
return 1;
......@@ -309,7 +300,7 @@ static int __init hp_wmi_bios_2008_later(void)
static int __init hp_wmi_bios_2009_later(void)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, 0, &state,
int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state,
sizeof(state), sizeof(state));
if (!ret)
return 1;
......@@ -320,11 +311,10 @@ static int __init hp_wmi_bios_2009_later(void)
static int __init hp_wmi_enable_hotkeys(void)
{
int value = 0x6e;
int ret = hp_wmi_perform_query(HPWMI_BIOS_QUERY, 1, &value,
int ret = hp_wmi_perform_query(HPWMI_BIOS_QUERY, HPWMI_WRITE, &value,
sizeof(value), 0);
if (ret)
return -EINVAL;
return 0;
return ret <= 0 ? ret : -EINVAL;
}
static int hp_wmi_set_block(void *data, bool blocked)
......@@ -333,11 +323,10 @@ static int hp_wmi_set_block(void *data, bool blocked)
int query = BIT(r + 8) | ((!blocked) << r);
int ret;
ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1,
ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE,
&query, sizeof(query), 0);
if (ret)
return -EINVAL;
return 0;
return ret <= 0 ? ret : -EINVAL;
}
static const struct rfkill_ops hp_wmi_rfkill_ops = {
......@@ -346,47 +335,38 @@ static const struct rfkill_ops hp_wmi_rfkill_ops = {
static bool hp_wmi_get_sw_state(enum hp_wmi_radio r)
{
int wireless = 0;
int mask;
hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0,
&wireless, sizeof(wireless),
sizeof(wireless));
/* TBD: Pass error */
int mask = 0x200 << (r * 8);
mask = 0x200 << (r * 8);
int wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY);
if (wireless & mask)
return false;
else
return true;
/* TBD: Pass error */
WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY");
return !(wireless & mask);
}
static bool hp_wmi_get_hw_state(enum hp_wmi_radio r)
{
int wireless = 0;
int mask;
hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0,
&wireless, sizeof(wireless),
sizeof(wireless));
/* TBD: Pass error */
int mask = 0x800 << (r * 8);
int wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY);
mask = 0x800 << (r * 8);
/* TBD: Pass error */
WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY");
if (wireless & mask)
return false;
else
return true;
return !(wireless & mask);
}
static int hp_wmi_rfkill2_set_block(void *data, bool blocked)
{
int rfkill_id = (int)(long)data;
char buffer[4] = { 0x01, 0x00, rfkill_id, !blocked };
int ret;
if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1,
buffer, sizeof(buffer), 0))
return -EINVAL;
return 0;
ret = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_WRITE,
buffer, sizeof(buffer), 0);
return ret <= 0 ? ret : -EINVAL;
}
static const struct rfkill_ops hp_wmi_rfkill2_ops = {
......@@ -395,10 +375,10 @@ static const struct rfkill_ops hp_wmi_rfkill2_ops = {
static int hp_wmi_rfkill2_refresh(void)
{
int err, i;
struct bios_rfkill2_state state;
int err, i;
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state,
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state,
0, sizeof(state));
if (err)
return err;
......@@ -422,119 +402,113 @@ static int hp_wmi_rfkill2_refresh(void)
return 0;
}
static int hp_wmi_post_code_state(void)
{
int state = 0;
int ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 0, &state,
sizeof(state), sizeof(state));
if (ret)
return -EINVAL;
return state;
}
static ssize_t show_display(struct device *dev, struct device_attribute *attr,
static ssize_t display_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int value = hp_wmi_display_state();
int value = hp_wmi_read_int(HPWMI_DISPLAY_QUERY);
if (value < 0)
return -EINVAL;
return value;
return sprintf(buf, "%d\n", value);
}
static ssize_t show_hddtemp(struct device *dev, struct device_attribute *attr,
static ssize_t hddtemp_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int value = hp_wmi_hddtemp_state();
int value = hp_wmi_read_int(HPWMI_HDDTEMP_QUERY);
if (value < 0)
return -EINVAL;
return value;
return sprintf(buf, "%d\n", value);
}
static ssize_t show_als(struct device *dev, struct device_attribute *attr,
static ssize_t als_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int value = hp_wmi_als_state();
int value = hp_wmi_read_int(HPWMI_ALS_QUERY);
if (value < 0)
return -EINVAL;
return value;
return sprintf(buf, "%d\n", value);
}
static ssize_t show_dock(struct device *dev, struct device_attribute *attr,
static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int value = hp_wmi_dock_state();
int value = hp_wmi_hw_state(HPWMI_DOCK_MASK);
if (value < 0)
return -EINVAL;
return value;
return sprintf(buf, "%d\n", value);
}
static ssize_t show_tablet(struct device *dev, struct device_attribute *attr,
static ssize_t tablet_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int value = hp_wmi_tablet_state();
int value = hp_wmi_hw_state(HPWMI_TABLET_MASK);
if (value < 0)
return -EINVAL;
return value;
return sprintf(buf, "%d\n", value);
}
static ssize_t show_postcode(struct device *dev, struct device_attribute *attr,
static ssize_t postcode_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
/* Get the POST error code of previous boot failure. */
int value = hp_wmi_post_code_state();
int value = hp_wmi_read_int(HPWMI_POSTCODEERROR_QUERY);
if (value < 0)
return -EINVAL;
return value;
return sprintf(buf, "0x%x\n", value);
}
static ssize_t set_als(struct device *dev, struct device_attribute *attr,
static ssize_t als_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
u32 tmp = simple_strtoul(buf, NULL, 10);
int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, &tmp,
int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp,
sizeof(tmp), sizeof(tmp));
if (ret)
return -EINVAL;
return ret < 0 ? ret : -EINVAL;
return count;
}
static ssize_t set_postcode(struct device *dev, struct device_attribute *attr,
static ssize_t postcode_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
long unsigned int tmp2;
int ret;
u32 tmp;
long unsigned int tmp2;
ret = kstrtoul(buf, 10, &tmp2);
if (ret || tmp2 != 1)
return -EINVAL;
if (!ret && tmp2 != 1)
ret = -EINVAL;
if (ret)
goto out;
/* Clear the POST error code. It is kept until until cleared. */
tmp = (u32) tmp2;
ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 1, &tmp,
ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp,
sizeof(tmp), sizeof(tmp));
out:
if (ret)
return -EINVAL;
return ret < 0 ? ret : -EINVAL;
return count;
}
static DEVICE_ATTR(display, S_IRUGO, show_display, NULL);
static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL);
static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als);
static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL);
static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL);
static DEVICE_ATTR(postcode, S_IRUGO | S_IWUSR, show_postcode, set_postcode);
static DEVICE_ATTR_RO(display);
static DEVICE_ATTR_RO(hddtemp);
static DEVICE_ATTR_RW(als);
static DEVICE_ATTR_RO(dock);
static DEVICE_ATTR_RO(tablet);
static DEVICE_ATTR_RW(postcode);
static void hp_wmi_notify(u32 value, void *context)
{
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
u32 event_id, event_data;
int key_code = 0, ret;
u32 *location;
union acpi_object *obj;
acpi_status status;
u32 *location;
int key_code;
status = wmi_get_event_data(value, &response);
if (status != AE_OK) {
......@@ -572,10 +546,12 @@ static void hp_wmi_notify(u32 value, void *context)
switch (event_id) {
case HPWMI_DOCK_EVENT:
if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit))
input_report_switch(hp_wmi_input_dev, SW_DOCK,
hp_wmi_dock_state());
hp_wmi_hw_state(HPWMI_DOCK_MASK));
if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit))
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
hp_wmi_tablet_state());
hp_wmi_hw_state(HPWMI_TABLET_MASK));
input_sync(hp_wmi_input_dev);
break;
case HPWMI_PARK_HDD:
......@@ -583,11 +559,8 @@ static void hp_wmi_notify(u32 value, void *context)
case HPWMI_SMART_ADAPTER:
break;
case HPWMI_BEZEL_BUTTON:
ret = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0,
&key_code,
sizeof(key_code),
sizeof(key_code));
if (ret)
key_code = hp_wmi_read_int(HPWMI_HOTKEY_QUERY);
if (key_code < 0)
break;
if (!sparse_keymap_report_event(hp_wmi_input_dev,
......@@ -643,7 +616,7 @@ static void hp_wmi_notify(u32 value, void *context)
static int __init hp_wmi_input_setup(void)
{
acpi_status status;
int err;
int err, val;
hp_wmi_input_dev = input_allocate_device();
if (!hp_wmi_input_dev)
......@@ -654,17 +627,26 @@ static int __init hp_wmi_input_setup(void)
hp_wmi_input_dev->id.bustype = BUS_HOST;
__set_bit(EV_SW, hp_wmi_input_dev->evbit);
/* Dock */
val = hp_wmi_hw_state(HPWMI_DOCK_MASK);
if (!(val < 0)) {
__set_bit(SW_DOCK, hp_wmi_input_dev->swbit);
input_report_switch(hp_wmi_input_dev, SW_DOCK, val);
}
/* Tablet mode */
val = hp_wmi_hw_state(HPWMI_TABLET_MASK);
if (!(val < 0)) {
__set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit);
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val);
}
err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL);
if (err)
goto err_free_dev;
/* Set initial hardware state */
input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state());
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
hp_wmi_tablet_state());
input_sync(hp_wmi_input_dev);
if (!hp_wmi_bios_2009_later() && hp_wmi_bios_2008_later())
......@@ -673,7 +655,7 @@ static int __init hp_wmi_input_setup(void)
status = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL);
if (ACPI_FAILURE(status)) {
err = -EIO;
goto err_free_keymap;
goto err_free_dev;
}
err = input_register_device(hp_wmi_input_dev);
......@@ -684,8 +666,6 @@ static int __init hp_wmi_input_setup(void)
err_uninstall_notifier:
wmi_remove_notify_handler(HPWMI_EVENT_GUID);
err_free_keymap:
sparse_keymap_free(hp_wmi_input_dev);
err_free_dev:
input_free_device(hp_wmi_input_dev);
return err;
......@@ -694,7 +674,6 @@ static int __init hp_wmi_input_setup(void)
static void hp_wmi_input_destroy(void)
{
wmi_remove_notify_handler(HPWMI_EVENT_GUID);
sparse_keymap_free(hp_wmi_input_dev);
input_unregister_device(hp_wmi_input_dev);
}
......@@ -710,15 +689,13 @@ static void cleanup_sysfs(struct platform_device *device)
static int __init hp_wmi_rfkill_setup(struct platform_device *device)
{
int err;
int wireless = 0;
int err, wireless;
err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless,
sizeof(wireless), sizeof(wireless));
if (err)
return err;
wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY);
if (wireless < 0)
return wireless;
err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, &wireless,
err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE, &wireless,
sizeof(wireless), 0);
if (err)
return err;
......@@ -795,12 +772,13 @@ static int __init hp_wmi_rfkill_setup(struct platform_device *device)
static int __init hp_wmi_rfkill2_setup(struct platform_device *device)
{
int err, i;
struct bios_rfkill2_state state;
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state,
int err, i;
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state,
0, sizeof(state));
if (err)
return err;
return err < 0 ? err : -EINVAL;
if (state.count > HPWMI_MAX_RFKILL2_DEVICES) {
pr_warn("unable to parse 0x1b query output\n");
......@@ -950,10 +928,12 @@ static int hp_wmi_resume_handler(struct device *device)
* changed.
*/
if (hp_wmi_input_dev) {
if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit))
input_report_switch(hp_wmi_input_dev, SW_DOCK,
hp_wmi_dock_state());
hp_wmi_hw_state(HPWMI_DOCK_MASK));
if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit))
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
hp_wmi_tablet_state());
hp_wmi_hw_state(HPWMI_TABLET_MASK));
input_sync(hp_wmi_input_dev);
}
......@@ -991,9 +971,9 @@ static struct platform_driver hp_wmi_driver = {
static int __init hp_wmi_init(void)
{
int err;
int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID);
int err;
if (!bios_capable && !event_capable)
return -ENODEV;
......
......@@ -604,14 +604,12 @@ static int ideapad_input_init(struct ideapad_private *priv)
error = input_register_device(inputdev);
if (error) {
pr_err("Unable to register input device\n");
goto err_free_keymap;
goto err_free_dev;
}
priv->inputdev = inputdev;
return 0;
err_free_keymap:
sparse_keymap_free(inputdev);
err_free_dev:
input_free_device(inputdev);
return error;
......@@ -619,7 +617,6 @@ static int ideapad_input_init(struct ideapad_private *priv)
static void ideapad_input_exit(struct ideapad_private *priv)
{
sparse_keymap_free(priv->inputdev);
input_unregister_device(priv->inputdev);
priv->inputdev = NULL;
}
......@@ -871,6 +868,20 @@ static const struct dmi_system_id no_hw_rfkill_list[] = {
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"),
},
},
{
.ident = "Lenovo V310-15ISK",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15ISK"),
},
},
{
.ident = "Lenovo ideapad 310-15IKB",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IKB"),
},
},
{
.ident = "Lenovo ideapad Y700-15ACZ",
.matches = {
......
......@@ -77,14 +77,12 @@ struct intel_hid_priv {
struct input_dev *array;
};
static int intel_hid_set_enable(struct device *device, int enable)
static int intel_hid_set_enable(struct device *device, bool enable)
{
union acpi_object arg0 = { ACPI_TYPE_INTEGER };
struct acpi_object_list args = { 1, &arg0 };
acpi_status status;
arg0.integer.value = enable;
status = acpi_evaluate_object(ACPI_HANDLE(device), "HDSM", &args, NULL);
status = acpi_execute_simple_method(ACPI_HANDLE(device), "HDSM",
enable);
if (ACPI_FAILURE(status)) {
dev_warn(device, "failed to %sable hotkeys\n",
enable ? "en" : "dis");
......@@ -120,7 +118,7 @@ static void intel_button_array_enable(struct device *device, bool enable)
static int intel_hid_pl_suspend_handler(struct device *device)
{
intel_hid_set_enable(device, 0);
intel_hid_set_enable(device, false);
intel_button_array_enable(device, false);
return 0;
......@@ -128,7 +126,7 @@ static int intel_hid_pl_suspend_handler(struct device *device)
static int intel_hid_pl_resume_handler(struct device *device)
{
intel_hid_set_enable(device, 1);
intel_hid_set_enable(device, true);
intel_button_array_enable(device, true);
return 0;
......@@ -136,6 +134,7 @@ static int intel_hid_pl_resume_handler(struct device *device)
static const struct dev_pm_ops intel_hid_pl_pm_ops = {
.freeze = intel_hid_pl_suspend_handler,
.thaw = intel_hid_pl_resume_handler,
.restore = intel_hid_pl_resume_handler,
.suspend = intel_hid_pl_suspend_handler,
.resume = intel_hid_pl_resume_handler,
......@@ -146,28 +145,18 @@ static int intel_hid_input_setup(struct platform_device *device)
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
int ret;
priv->input_dev = input_allocate_device();
priv->input_dev = devm_input_allocate_device(&device->dev);
if (!priv->input_dev)
return -ENOMEM;
ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL);
if (ret)
goto err_free_device;
return ret;
priv->input_dev->dev.parent = &device->dev;
priv->input_dev->name = "Intel HID events";
priv->input_dev->id.bustype = BUS_HOST;
set_bit(KEY_RFKILL, priv->input_dev->keybit);
ret = input_register_device(priv->input_dev);
if (ret)
goto err_free_device;
return 0;
err_free_device:
input_free_device(priv->input_dev);
return ret;
return input_register_device(priv->input_dev);
}
static int intel_button_array_input_setup(struct platform_device *device)
......@@ -184,20 +173,12 @@ static int intel_button_array_input_setup(struct platform_device *device)
if (ret)
return ret;
priv->array->dev.parent = &device->dev;
priv->array->name = "Intel HID 5 button array";
priv->array->id.bustype = BUS_HOST;
return input_register_device(priv->array);
}
static void intel_hid_input_destroy(struct platform_device *device)
{
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
input_unregister_device(priv->input_dev);
}
static void notify_handler(acpi_handle handle, u32 event, void *context)
{
struct platform_device *device = context;
......@@ -272,12 +253,10 @@ static int intel_hid_probe(struct platform_device *device)
ACPI_DEVICE_NOTIFY,
notify_handler,
device);
if (ACPI_FAILURE(status)) {
err = -EBUSY;
goto err_remove_input;
}
if (ACPI_FAILURE(status))
return -EBUSY;
err = intel_hid_set_enable(&device->dev, 1);
err = intel_hid_set_enable(&device->dev, true);
if (err)
goto err_remove_notify;
......@@ -296,9 +275,6 @@ static int intel_hid_probe(struct platform_device *device)
err_remove_notify:
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
err_remove_input:
intel_hid_input_destroy(device);
return err;
}
......@@ -307,8 +283,7 @@ static int intel_hid_remove(struct platform_device *device)
acpi_handle handle = ACPI_HANDLE(&device->dev);
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
intel_hid_input_destroy(device);
intel_hid_set_enable(&device->dev, 0);
intel_hid_set_enable(&device->dev, false);
intel_button_array_enable(&device->dev, false);
/*
......
......@@ -37,6 +37,10 @@ static const struct acpi_device_id intel_vbtn_ids[] = {
static const struct key_entry intel_vbtn_keymap[] = {
{ KE_IGNORE, 0xC0, { KEY_POWER } }, /* power key press */
{ KE_KEY, 0xC1, { KEY_POWER } }, /* power key release */
{ KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */
{ KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */
{ KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */
{ KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */
{ KE_END },
};
......
/*
* Intel Cherry Trail ACPI INT33FE pseudo device driver
*
* Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Some Intel Cherry Trail based device which ship with Windows 10, have
* this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2
* resources, for 4 different chips attached to various i2c busses:
* 1. The Whiskey Cove pmic, which is also described by the INT34D3 ACPI device
* 2. Maxim MAX17047 Fuel Gauge Controller
* 3. FUSB302 USB Type-C Controller
* 4. PI3USB30532 USB switch
*
* So this driver is a stub / pseudo driver whose only purpose is to
* instantiate i2c-clients for chips 2 - 4, so that standard i2c drivers
* 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/slab.h>
#define EXPECTED_PTYPE 4
struct cht_int33fe_data {
struct i2c_client *max17047;
struct i2c_client *fusb302;
struct i2c_client *pi3usb30532;
};
static int cht_int33fe_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct i2c_board_info board_info;
struct cht_int33fe_data *data;
unsigned long long ptyp;
acpi_status status;
int fusb302_irq;
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;
/* The FUSB302 uses the irq at index 1 and is the only irq user */
fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1);
if (fusb302_irq < 0) {
if (fusb302_irq != -EPROBE_DEFER)
dev_err(dev, "Error getting FUSB302 irq\n");
return fusb302_irq;
}
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
memset(&board_info, 0, sizeof(board_info));
strlcpy(board_info.type, "max17047", I2C_NAME_SIZE);
data->max17047 = i2c_acpi_new_device(dev, 1, &board_info);
if (!data->max17047)
return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */
memset(&board_info, 0, sizeof(board_info));
strlcpy(board_info.type, "fusb302", I2C_NAME_SIZE);
board_info.irq = fusb302_irq;
data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info);
if (!data->fusb302)
goto out_unregister_max17047;
memset(&board_info, 0, sizeof(board_info));
strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE);
data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info);
if (!data->pi3usb30532)
goto out_unregister_fusb302;
i2c_set_clientdata(client, data);
return 0;
out_unregister_fusb302:
i2c_unregister_device(data->fusb302);
out_unregister_max17047:
i2c_unregister_device(data->max17047);
return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */
}
static int cht_int33fe_remove(struct i2c_client *i2c)
{
struct cht_int33fe_data *data = i2c_get_clientdata(i2c);
i2c_unregister_device(data->pi3usb30532);
i2c_unregister_device(data->fusb302);
i2c_unregister_device(data->max17047);
return 0;
}
static const struct i2c_device_id cht_int33fe_i2c_id[] = {
{ }
};
MODULE_DEVICE_TABLE(i2c, cht_int33fe_i2c_id);
static const struct acpi_device_id cht_int33fe_acpi_ids[] = {
{ "INT33FE", },
{ }
};
MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids);
static struct i2c_driver cht_int33fe_driver = {
.driver = {
.name = "Intel Cherry Trail ACPI INT33FE driver",
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
},
.probe_new = cht_int33fe_probe,
.remove = cht_int33fe_remove,
.id_table = cht_int33fe_i2c_id,
.disable_i2c_core_irq_mapping = true,
};
module_i2c_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");
......@@ -57,10 +57,6 @@
#define IPC_WRITE_BUFFER 0x80
#define IPC_READ_BUFFER 0x90
/* PMC Global Control Registers */
#define GCR_TELEM_DEEP_S0IX_OFFSET 0x1078
#define GCR_TELEM_SHLW_S0IX_OFFSET 0x1080
/* Residency with clock rate at 19.2MHz to usecs */
#define S0IX_RESIDENCY_IN_USECS(d, s) \
({ \
......@@ -82,7 +78,7 @@
/* exported resources from IFWI */
#define PLAT_RESOURCE_IPC_INDEX 0
#define PLAT_RESOURCE_IPC_SIZE 0x1000
#define PLAT_RESOURCE_GCR_OFFSET 0x1008
#define PLAT_RESOURCE_GCR_OFFSET 0x1000
#define PLAT_RESOURCE_GCR_SIZE 0x1000
#define PLAT_RESOURCE_BIOS_DATA_INDEX 1
#define PLAT_RESOURCE_BIOS_IFACE_INDEX 2
......@@ -112,6 +108,13 @@
#define TCO_PMC_OFFSET 0x8
#define TCO_PMC_SIZE 0x4
/* PMC register bit definitions */
/* PMC_CFG_REG bit masks */
#define PMC_CFG_NO_REBOOT_MASK (1 << 4)
#define PMC_CFG_NO_REBOOT_EN (1 << 4)
#define PMC_CFG_NO_REBOOT_DIS (0 << 4)
static struct intel_pmc_ipc_dev {
struct device *dev;
void __iomem *ipc_base;
......@@ -126,8 +129,7 @@ static struct intel_pmc_ipc_dev {
struct platform_device *tco_dev;
/* gcr */
resource_size_t gcr_base;
int gcr_size;
void __iomem *gcr_mem_base;
bool has_gcr_regs;
/* punit */
......@@ -196,7 +198,128 @@ static inline u32 ipc_data_readl(u32 offset)
static inline u64 gcr_data_readq(u32 offset)
{
return readq(ipcdev.ipc_base + offset);
return readq(ipcdev.gcr_mem_base + offset);
}
static inline int is_gcr_valid(u32 offset)
{
if (!ipcdev.has_gcr_regs)
return -EACCES;
if (offset > PLAT_RESOURCE_GCR_SIZE)
return -EINVAL;
return 0;
}
/**
* intel_pmc_gcr_read() - Read PMC GCR register
* @offset: offset of GCR register from GCR address base
* @data: data pointer for storing the register output
*
* Reads the PMC GCR register of given offset.
*
* Return: negative value on error or 0 on success.
*/
int intel_pmc_gcr_read(u32 offset, u32 *data)
{
int ret;
mutex_lock(&ipclock);
ret = is_gcr_valid(offset);
if (ret < 0) {
mutex_unlock(&ipclock);
return ret;
}
*data = readl(ipcdev.gcr_mem_base + offset);
mutex_unlock(&ipclock);
return 0;
}
EXPORT_SYMBOL_GPL(intel_pmc_gcr_read);
/**
* intel_pmc_gcr_write() - Write PMC GCR register
* @offset: offset of GCR register from GCR address base
* @data: register update value
*
* Writes the PMC GCR register of given offset with given
* value.
*
* Return: negative value on error or 0 on success.
*/
int intel_pmc_gcr_write(u32 offset, u32 data)
{
int ret;
mutex_lock(&ipclock);
ret = is_gcr_valid(offset);
if (ret < 0) {
mutex_unlock(&ipclock);
return ret;
}
writel(data, ipcdev.gcr_mem_base + offset);
mutex_unlock(&ipclock);
return 0;
}
EXPORT_SYMBOL_GPL(intel_pmc_gcr_write);
/**
* intel_pmc_gcr_update() - Update PMC GCR register bits
* @offset: offset of GCR register from GCR address base
* @mask: bit mask for update operation
* @val: update value
*
* Updates the bits of given GCR register as specified by
* @mask and @val.
*
* Return: negative value on error or 0 on success.
*/
int intel_pmc_gcr_update(u32 offset, u32 mask, u32 val)
{
u32 new_val;
int ret = 0;
mutex_lock(&ipclock);
ret = is_gcr_valid(offset);
if (ret < 0)
goto gcr_ipc_unlock;
new_val = readl(ipcdev.gcr_mem_base + offset);
new_val &= ~mask;
new_val |= val & mask;
writel(new_val, ipcdev.gcr_mem_base + offset);
new_val = readl(ipcdev.gcr_mem_base + offset);
/* check whether the bit update is successful */
if ((new_val & mask) != (val & mask)) {
ret = -EIO;
goto gcr_ipc_unlock;
}
gcr_ipc_unlock:
mutex_unlock(&ipclock);
return ret;
}
EXPORT_SYMBOL_GPL(intel_pmc_gcr_update);
static int update_no_reboot_bit(void *priv, bool set)
{
u32 value = set ? PMC_CFG_NO_REBOOT_EN : PMC_CFG_NO_REBOOT_DIS;
return intel_pmc_gcr_update(PMC_GCR_PMC_CFG_REG,
PMC_CFG_NO_REBOOT_MASK, value);
}
static int intel_pmc_ipc_check_status(void)
......@@ -516,15 +639,13 @@ static struct resource tco_res[] = {
{
.flags = IORESOURCE_IO,
},
/* GCS */
{
.flags = IORESOURCE_MEM,
},
};
static struct itco_wdt_platform_data tco_info = {
.name = "Apollo Lake SoC",
.version = 5,
.no_reboot_priv = &ipcdev,
.update_no_reboot_bit = update_no_reboot_bit,
};
#define TELEMETRY_RESOURCE_PUNIT_SSRAM 0
......@@ -581,10 +702,6 @@ static int ipc_create_tco_device(void)
res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET;
res->end = res->start + SMI_EN_SIZE - 1;
res = tco_res + TCO_RESOURCE_GCR_MEM;
res->start = ipcdev.gcr_base + TCO_PMC_OFFSET;
res->end = res->start + TCO_PMC_SIZE - 1;
pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
......@@ -746,8 +863,7 @@ static int ipc_plat_get_res(struct platform_device *pdev)
}
ipcdev.ipc_base = addr;
ipcdev.gcr_base = res->start + PLAT_RESOURCE_GCR_OFFSET;
ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE;
ipcdev.gcr_mem_base = addr + PLAT_RESOURCE_GCR_OFFSET;
dev_info(&pdev->dev, "ipc res: %pR\n", res);
ipcdev.telem_res_inval = 0;
......@@ -782,8 +898,8 @@ int intel_pmc_s0ix_counter_read(u64 *data)
if (!ipcdev.has_gcr_regs)
return -EACCES;
deep = gcr_data_readq(GCR_TELEM_DEEP_S0IX_OFFSET);
shlw = gcr_data_readq(GCR_TELEM_SHLW_S0IX_OFFSET);
deep = gcr_data_readq(PMC_GCR_TELEM_DEEP_S0IX_REG);
shlw = gcr_data_readq(PMC_GCR_TELEM_SHLW_S0IX_REG);
*data = S0IX_RESIDENCY_IN_USECS(deep, shlw);
......
......@@ -491,6 +491,69 @@ int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
}
EXPORT_SYMBOL(intel_scu_ipc_command);
#define IPC_SPTR 0x08
#define IPC_DPTR 0x0C
/**
* intel_scu_ipc_raw_command() - IPC command with data and pointers
* @cmd: IPC command code.
* @sub: IPC command sub type.
* @in: input data of this IPC command.
* @inlen: input data length in dwords.
* @out: output data of this IPC command.
* @outlen: output data length in dwords.
* @sptr: data writing to SPTR register.
* @dptr: data writing to DPTR register.
*
* Send an IPC command to SCU with input/output data and source/dest pointers.
*
* Return: an IPC error code or 0 on success.
*/
int intel_scu_ipc_raw_command(int cmd, int sub, u8 *in, int inlen,
u32 *out, int outlen, u32 dptr, u32 sptr)
{
struct intel_scu_ipc_dev *scu = &ipcdev;
int inbuflen = DIV_ROUND_UP(inlen, 4);
u32 inbuf[4];
int i, err;
/* Up to 16 bytes */
if (inbuflen > 4)
return -EINVAL;
mutex_lock(&ipclock);
if (scu->dev == NULL) {
mutex_unlock(&ipclock);
return -ENODEV;
}
writel(dptr, scu->ipc_base + IPC_DPTR);
writel(sptr, scu->ipc_base + IPC_SPTR);
/*
* SRAM controller doesn't support 8-bit writes, it only
* supports 32-bit writes, so we have to copy input data into
* the temporary buffer, and SCU FW will use the inlen to
* determine the actual input data length in the temporary
* buffer.
*/
memcpy(inbuf, in, inlen);
for (i = 0; i < inbuflen; i++)
ipc_data_writel(scu, inbuf[i], 4 * i);
ipc_command(scu, (inlen << 16) | (sub << 12) | cmd);
err = intel_scu_ipc_check_status(scu);
if (!err) {
for (i = 0; i < outlen; i++)
*out++ = ipc_data_readl(scu, 4 * i);
}
mutex_unlock(&ipclock);
return err;
}
EXPORT_SYMBOL_GPL(intel_scu_ipc_raw_command);
/* I2C commands */
#define IPC_I2C_WRITE 1 /* I2C Write command */
#define IPC_I2C_READ 2 /* I2C Read command */
......@@ -566,21 +629,17 @@ static irqreturn_t ioc(int irq, void *dev_id)
*/
static int ipc_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int platform; /* Platform type */
int err;
struct intel_scu_ipc_dev *scu = &ipcdev;
struct intel_scu_ipc_pdata_t *pdata;
platform = intel_mid_identify_cpu();
if (platform == 0)
return -ENODEV;
if (scu->dev) /* We support only one SCU */
return -EBUSY;
pdata = (struct intel_scu_ipc_pdata_t *)id->driver_data;
if (!pdata)
return -ENODEV;
scu->dev = &pdev->dev;
scu->irq_mode = pdata->irq_mode;
err = pcim_enable_device(pdev);
......@@ -593,39 +652,34 @@ static int ipc_probe(struct pci_dev *pdev, const struct pci_device_id *id)
init_completion(&scu->cmd_complete);
err = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_scu_ipc",
scu);
if (err)
return err;
scu->ipc_base = pcim_iomap_table(pdev)[0];
scu->i2c_base = ioremap_nocache(pdata->i2c_base, pdata->i2c_len);
if (!scu->i2c_base)
return -ENOMEM;
err = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_scu_ipc",
scu);
if (err)
return err;
/* Assign device at last */
scu->dev = &pdev->dev;
intel_scu_devices_create();
pci_set_drvdata(pdev, scu);
return 0;
}
#define SCU_DEVICE(id, pdata) {PCI_VDEVICE(INTEL, id), (kernel_ulong_t)&pdata}
static const struct pci_device_id pci_ids[] = {
{
PCI_VDEVICE(INTEL, PCI_DEVICE_ID_LINCROFT),
(kernel_ulong_t)&intel_scu_ipc_lincroft_pdata,
}, {
PCI_VDEVICE(INTEL, PCI_DEVICE_ID_PENWELL),
(kernel_ulong_t)&intel_scu_ipc_penwell_pdata,
}, {
PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CLOVERVIEW),
(kernel_ulong_t)&intel_scu_ipc_penwell_pdata,
}, {
PCI_VDEVICE(INTEL, PCI_DEVICE_ID_TANGIER),
(kernel_ulong_t)&intel_scu_ipc_tangier_pdata,
}, {
0,
}
SCU_DEVICE(PCI_DEVICE_ID_LINCROFT, intel_scu_ipc_lincroft_pdata),
SCU_DEVICE(PCI_DEVICE_ID_PENWELL, intel_scu_ipc_penwell_pdata),
SCU_DEVICE(PCI_DEVICE_ID_CLOVERVIEW, intel_scu_ipc_penwell_pdata),
SCU_DEVICE(PCI_DEVICE_ID_TANGIER, intel_scu_ipc_tangier_pdata),
{}
};
static struct pci_driver ipc_driver = {
......
......@@ -976,23 +976,15 @@ static int __init msi_laptop_input_setup(void)
err = input_register_device(msi_laptop_input_dev);
if (err)
goto err_free_keymap;
goto err_free_dev;
return 0;
err_free_keymap:
sparse_keymap_free(msi_laptop_input_dev);
err_free_dev:
input_free_device(msi_laptop_input_dev);
return err;
}
static void msi_laptop_input_destroy(void)
{
sparse_keymap_free(msi_laptop_input_dev);
input_unregister_device(msi_laptop_input_dev);
}
static int __init load_scm_model_init(struct platform_device *sdev)
{
u8 data;
......@@ -1037,7 +1029,7 @@ static int __init load_scm_model_init(struct platform_device *sdev)
return 0;
fail_filter:
msi_laptop_input_destroy();
input_unregister_device(msi_laptop_input_dev);
fail_input:
rfkill_cleanup();
......@@ -1158,7 +1150,7 @@ static void __exit msi_cleanup(void)
{
if (quirks->load_scm_model) {
i8042_remove_filter(msi_laptop_i8042_filter);
msi_laptop_input_destroy();
input_unregister_device(msi_laptop_input_dev);
cancel_delayed_work_sync(&msi_rfkill_dwork);
cancel_work_sync(&msi_rfkill_work);
rfkill_cleanup();
......
......@@ -281,14 +281,12 @@ static int __init msi_wmi_input_setup(void)
err = input_register_device(msi_wmi_input_dev);
if (err)
goto err_free_keymap;
goto err_free_dev;
last_pressed = 0;
return 0;
err_free_keymap:
sparse_keymap_free(msi_wmi_input_dev);
err_free_dev:
input_free_device(msi_wmi_input_dev);
return err;
......@@ -342,10 +340,8 @@ static int __init msi_wmi_init(void)
if (event_wmi)
wmi_remove_notify_handler(event_wmi->guid);
err_free_input:
if (event_wmi) {
sparse_keymap_free(msi_wmi_input_dev);
if (event_wmi)
input_unregister_device(msi_wmi_input_dev);
}
return err;
}
......@@ -353,7 +349,6 @@ static void __exit msi_wmi_exit(void)
{
if (event_wmi) {
wmi_remove_notify_handler(event_wmi->guid);
sparse_keymap_free(msi_wmi_input_dev);
input_unregister_device(msi_wmi_input_dev);
}
backlight_device_unregister(backlight);
......
......@@ -520,29 +520,17 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc)
if (error) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"Unable to register input device\n"));
goto err_free_keymap;
goto err_free_dev;
}
pcc->input_dev = input_dev;
return 0;
err_free_keymap:
sparse_keymap_free(input_dev);
err_free_dev:
input_free_device(input_dev);
return error;
}
static void acpi_pcc_destroy_input(struct pcc_acpi *pcc)
{
sparse_keymap_free(pcc->input_dev);
input_unregister_device(pcc->input_dev);
/*
* No need to input_free_device() since core input API refcounts
* and free()s the device.
*/
}
/* kernel module interface */
#ifdef CONFIG_PM_SLEEP
......@@ -640,7 +628,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
out_backlight:
backlight_device_unregister(pcc->backlight);
out_input:
acpi_pcc_destroy_input(pcc);
input_unregister_device(pcc->input_dev);
out_sinf:
kfree(pcc->sinf);
out_hotkey:
......@@ -660,7 +648,7 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device)
backlight_device_unregister(pcc->backlight);
acpi_pcc_destroy_input(pcc);
input_unregister_device(pcc->input_dev);
kfree(pcc->sinf);
kfree(pcc);
......
......@@ -22,10 +22,10 @@
struct silead_ts_dmi_data {
const char *acpi_name;
struct property_entry *properties;
const struct property_entry *properties;
};
static struct property_entry cube_iwork8_air_props[] = {
static const struct property_entry cube_iwork8_air_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1660),
PROPERTY_ENTRY_U32("touchscreen-size-y", 900),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
......@@ -39,7 +39,7 @@ static const struct silead_ts_dmi_data cube_iwork8_air_data = {
.properties = cube_iwork8_air_props,
};
static struct property_entry jumper_ezpad_mini3_props[] = {
static const struct property_entry jumper_ezpad_mini3_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1700),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1150),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
......@@ -53,6 +53,33 @@ static const struct silead_ts_dmi_data jumper_ezpad_mini3_data = {
.properties = jumper_ezpad_mini3_props,
};
static const struct property_entry dexp_ursus_7w_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 890),
PROPERTY_ENTRY_U32("touchscreen-size-y", 630),
PROPERTY_ENTRY_STRING("firmware-name", "gsl1686-dexp-ursus-7w.fw"),
PROPERTY_ENTRY_U32("silead,max-fingers", 10),
{ }
};
static const struct silead_ts_dmi_data dexp_ursus_7w_data = {
.acpi_name = "MSSL1680:00",
.properties = dexp_ursus_7w_props,
};
static const struct property_entry surftab_wintron70_st70416_6_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 884),
PROPERTY_ENTRY_U32("touchscreen-size-y", 632),
PROPERTY_ENTRY_STRING("firmware-name",
"gsl1686-surftab-wintron70-st70416-6.fw"),
PROPERTY_ENTRY_U32("silead,max-fingers", 10),
{ }
};
static const struct silead_ts_dmi_data surftab_wintron70_st70416_6_data = {
.acpi_name = "MSSL1680:00",
.properties = surftab_wintron70_st70416_6_props,
};
static const struct dmi_system_id silead_ts_dmi_table[] = {
{
/* CUBE iwork8 Air */
......@@ -72,24 +99,37 @@ static const struct dmi_system_id silead_ts_dmi_table[] = {
DMI_MATCH(DMI_BIOS_VERSION, "jumperx.T87.KFBNEEA"),
},
},
{
/* DEXP Ursus 7W */
.driver_data = (void *)&dexp_ursus_7w_data,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "7W"),
},
},
{
/* Trekstor Surftab Wintron 7.0 ST70416-6 */
.driver_data = (void *)&surftab_wintron70_st70416_6_data,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "ST70416-6"),
/* Exact match, different versions need different fw */
DMI_MATCH(DMI_BIOS_VERSION, "TREK.G.WI71C.JGBMRBA04"),
},
},
{ },
};
static void silead_ts_dmi_add_props(struct device *dev)
static const struct silead_ts_dmi_data *silead_ts_data;
static void silead_ts_dmi_add_props(struct i2c_client *client)
{
struct i2c_client *client = to_i2c_client(dev);
const struct dmi_system_id *dmi_id;
const struct silead_ts_dmi_data *ts_data;
struct device *dev = &client->dev;
int error;
dmi_id = dmi_first_match(silead_ts_dmi_table);
if (!dmi_id)
return;
ts_data = dmi_id->driver_data;
if (has_acpi_companion(dev) &&
!strncmp(ts_data->acpi_name, client->name, I2C_NAME_SIZE)) {
error = device_add_properties(dev, ts_data->properties);
!strncmp(silead_ts_data->acpi_name, client->name, I2C_NAME_SIZE)) {
error = device_add_properties(dev, silead_ts_data->properties);
if (error)
dev_err(dev, "failed to add properties: %d\n", error);
}
......@@ -99,10 +139,13 @@ static int silead_ts_dmi_notifier_call(struct notifier_block *nb,
unsigned long action, void *data)
{
struct device *dev = data;
struct i2c_client *client;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
silead_ts_dmi_add_props(dev);
client = i2c_verify_client(dev);
if (client)
silead_ts_dmi_add_props(client);
break;
default:
......@@ -118,8 +161,15 @@ static struct notifier_block silead_ts_dmi_notifier = {
static int __init silead_ts_dmi_init(void)
{
const struct dmi_system_id *dmi_id;
int error;
dmi_id = dmi_first_match(silead_ts_dmi_table);
if (!dmi_id)
return 0; /* Not an error */
silead_ts_data = dmi_id->driver_data;
error = bus_register_notifier(&i2c_bus_type, &silead_ts_dmi_notifier);
if (error)
pr_err("%s: failed to register i2c bus notifier: %d\n",
......
......@@ -1922,7 +1922,9 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */
TP_ACPI_HOTKEYSCAN_UNK7,
TP_ACPI_HOTKEYSCAN_UNK8,
TP_ACPI_HOTKEYSCAN_MUTE2,
/* Adaptive keyboard keycodes */
TP_ACPI_HOTKEYSCAN_ADAPTIVE_START,
TP_ACPI_HOTKEYSCAN_MUTE2 = TP_ACPI_HOTKEYSCAN_ADAPTIVE_START,
TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO,
TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL,
TP_ACPI_HOTKEYSCAN_CLOUD,
......@@ -1943,6 +1945,15 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */
TP_ACPI_HOTKEYSCAN_CAMERA_MODE,
TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY,
/* Lenovo extended keymap, starting at 0x1300 */
TP_ACPI_HOTKEYSCAN_EXTENDED_START,
/* first new observed key (star, favorites) is 0x1311 */
TP_ACPI_HOTKEYSCAN_STAR = 69,
TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL2,
TP_ACPI_HOTKEYSCAN_UNK25,
TP_ACPI_HOTKEYSCAN_BLUETOOTH,
TP_ACPI_HOTKEYSCAN_KEYBOARD,
/* Hotkey keymap size */
TPACPI_HOTKEY_MAP_LEN
};
......@@ -3250,6 +3261,15 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
/* No assignment, used for newer Lenovo models */
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN
},
/* Generic keymap for Lenovo ThinkPads */
......@@ -3335,6 +3355,29 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
KEY_RESERVED, /* Microphone cancellation */
KEY_RESERVED, /* Camera mode */
KEY_RESERVED, /* Rotate display, 0x116 */
/*
* These are found in 2017 models (e.g. T470s, X270).
* The lowest known value is 0x311, which according to
* the manual should launch a user defined favorite
* application.
*
* The offset for these is TP_ACPI_HOTKEYSCAN_EXTENDED_START,
* corresponding to 0x34.
*/
/* (assignments unknown, please report if found) */
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
KEY_UNKNOWN,
KEY_FAVORITES, /* Favorite app, 0x311 */
KEY_RESERVED, /* Clipping tool */
KEY_RESERVED,
KEY_BLUETOOTH, /* Bluetooth */
KEY_KEYBOARD /* Keyboard, 0x315 */
},
};
......@@ -3656,7 +3699,6 @@ static const int adaptive_keyboard_modes[] = {
#define DFR_CHANGE_ROW 0x101
#define DFR_SHOW_QUICKVIEW_ROW 0x102
#define FIRST_ADAPTIVE_KEY 0x103
#define ADAPTIVE_KEY_OFFSET 0x020
/* press Fn key a while second, it will switch to Function Mode. Then
* release Fn key, previous mode be restored.
......@@ -3746,13 +3788,15 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode)
default:
if (scancode < FIRST_ADAPTIVE_KEY ||
scancode >= FIRST_ADAPTIVE_KEY + TPACPI_HOTKEY_MAP_LEN -
ADAPTIVE_KEY_OFFSET) {
scancode >= FIRST_ADAPTIVE_KEY +
TP_ACPI_HOTKEYSCAN_EXTENDED_START -
TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) {
pr_info("Unhandled adaptive keyboard key: 0x%x\n",
scancode);
return false;
}
keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + ADAPTIVE_KEY_OFFSET];
keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY +
TP_ACPI_HOTKEYSCAN_ADAPTIVE_START];
if (keycode != KEY_RESERVED) {
mutex_lock(&tpacpi_inputdev_send_mutex);
......@@ -3777,8 +3821,16 @@ static bool hotkey_notify_hotkey(const u32 hkey,
*send_acpi_ev = true;
*ignore_acpi_ev = false;
/*
* Original events are in the 0x10XX range, the adaptive keyboard
* found in 2014 X1 Carbon emits events are of 0x11XX. In 2017
* models, additional keys are emitted through 0x13XX.
*/
switch ((hkey >> 8) & 0xf) {
case 0:
if (scancode > 0 &&
scancode <= TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) {
/* HKEY event 0x1001 is scancode 0x00 */
if (scancode > 0 && scancode <= TPACPI_HOTKEY_MAP_LEN) {
scancode--;
if (!(hotkey_source_mask & (1 << scancode))) {
tpacpi_input_send_key_masked(scancode);
......@@ -3787,9 +3839,26 @@ static bool hotkey_notify_hotkey(const u32 hkey,
*ignore_acpi_ev = true;
}
return true;
} else {
}
break;
case 1:
return adaptive_keyboard_hotkey_notify_hotkey(scancode);
case 3:
/* Extended keycodes start at 0x300 and our offset into the map
* TP_ACPI_HOTKEYSCAN_EXTENDED_START. The calculated scancode
* will be positive, but might not be in the correct range.
*/
scancode -= (0x300 - TP_ACPI_HOTKEYSCAN_EXTENDED_START);
if (scancode >= TP_ACPI_HOTKEYSCAN_EXTENDED_START &&
scancode < TPACPI_HOTKEY_MAP_LEN) {
tpacpi_input_send_key(scancode);
return true;
}
break;
}
return false;
}
......
......@@ -113,14 +113,12 @@ static int acpi_topstar_init_hkey(struct topstar_hkey *hkey)
error = input_register_device(input);
if (error) {
pr_err("Unable to register input device\n");
goto err_free_keymap;
goto err_free_dev;
}
hkey->inputdev = input;
return 0;
err_free_keymap:
sparse_keymap_free(input);
err_free_dev:
input_free_device(input);
return error;
......@@ -157,7 +155,6 @@ static int acpi_topstar_remove(struct acpi_device *device)
acpi_topstar_fncx_switch(device, false);
sparse_keymap_free(tps_hkey->inputdev);
input_unregister_device(tps_hkey->inputdev);
kfree(tps_hkey);
......
......@@ -96,7 +96,7 @@ static int __init toshiba_wmi_input_setup(void)
toshiba_wmi_notify, NULL);
if (ACPI_FAILURE(status)) {
err = -EIO;
goto err_free_keymap;
goto err_free_dev;
}
err = input_register_device(toshiba_wmi_input_dev);
......@@ -107,8 +107,6 @@ static int __init toshiba_wmi_input_setup(void)
err_remove_notifier:
wmi_remove_notify_handler(WMI_EVENT_GUID);
err_free_keymap:
sparse_keymap_free(toshiba_wmi_input_dev);
err_free_dev:
input_free_device(toshiba_wmi_input_dev);
return err;
......@@ -117,7 +115,6 @@ static int __init toshiba_wmi_input_setup(void)
static void toshiba_wmi_input_destroy(void)
{
wmi_remove_notify_handler(WMI_EVENT_GUID);
sparse_keymap_free(toshiba_wmi_input_dev);
input_unregister_device(toshiba_wmi_input_dev);
}
......
......@@ -2849,7 +2849,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
error = i8042_install_filter(toshiba_acpi_i8042_filter);
if (error) {
pr_err("Error installing key filter\n");
goto err_free_keymap;
goto err_free_dev;
}
dev->ntfy_supported = 1;
......@@ -2880,8 +2880,6 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
err_remove_filter:
if (dev->ntfy_supported)
i8042_remove_filter(toshiba_acpi_i8042_filter);
err_free_keymap:
sparse_keymap_free(dev->hotkey_dev);
err_free_dev:
input_free_device(dev->hotkey_dev);
dev->hotkey_dev = NULL;
......@@ -3018,10 +3016,8 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
cancel_work_sync(&dev->hotkey_work);
}
if (dev->hotkey_dev) {
if (dev->hotkey_dev)
input_unregister_device(dev->hotkey_dev);
sparse_keymap_free(dev->hotkey_dev);
}
backlight_device_unregister(dev->backlight_dev);
......
......@@ -106,6 +106,10 @@ struct iTCO_wdt_private {
struct pci_dev *pci_dev;
/* whether or not the watchdog has been suspended */
bool suspended;
/* no reboot API private data */
void *no_reboot_priv;
/* no reboot update function pointer */
int (*update_no_reboot_bit)(void *p, bool set);
};
/* module parameters */
......@@ -170,46 +174,68 @@ static inline u32 no_reboot_bit(struct iTCO_wdt_private *p)
return enable_bit;
}
static void iTCO_wdt_set_NO_REBOOT_bit(struct iTCO_wdt_private *p)
static int update_no_reboot_bit_def(void *priv, bool set)
{
u32 val32;
return 0;
}
static int update_no_reboot_bit_pci(void *priv, bool set)
{
struct iTCO_wdt_private *p = priv;
u32 val32 = 0, newval32 = 0;
/* Set the NO_REBOOT bit: this disables reboots */
if (p->iTCO_version >= 2) {
val32 = readl(p->gcs_pmc);
val32 |= no_reboot_bit(p);
writel(val32, p->gcs_pmc);
} else if (p->iTCO_version == 1) {
pci_read_config_dword(p->pci_dev, 0xd4, &val32);
if (set)
val32 |= no_reboot_bit(p);
else
val32 &= ~no_reboot_bit(p);
pci_write_config_dword(p->pci_dev, 0xd4, val32);
}
pci_read_config_dword(p->pci_dev, 0xd4, &newval32);
/* make sure the update is successful */
if (val32 != newval32)
return -EIO;
return 0;
}
static int iTCO_wdt_unset_NO_REBOOT_bit(struct iTCO_wdt_private *p)
static int update_no_reboot_bit_mem(void *priv, bool set)
{
u32 enable_bit = no_reboot_bit(p);
u32 val32 = 0;
struct iTCO_wdt_private *p = priv;
u32 val32 = 0, newval32 = 0;
/* Unset the NO_REBOOT bit: this enables reboots */
if (p->iTCO_version >= 2) {
val32 = readl(p->gcs_pmc);
val32 &= ~enable_bit;
if (set)
val32 |= no_reboot_bit(p);
else
val32 &= ~no_reboot_bit(p);
writel(val32, p->gcs_pmc);
newval32 = readl(p->gcs_pmc);
val32 = readl(p->gcs_pmc);
} else if (p->iTCO_version == 1) {
pci_read_config_dword(p->pci_dev, 0xd4, &val32);
val32 &= ~enable_bit;
pci_write_config_dword(p->pci_dev, 0xd4, val32);
/* make sure the update is successful */
if (val32 != newval32)
return -EIO;
pci_read_config_dword(p->pci_dev, 0xd4, &val32);
return 0;
}
static void iTCO_wdt_no_reboot_bit_setup(struct iTCO_wdt_private *p,
struct itco_wdt_platform_data *pdata)
{
if (pdata->update_no_reboot_bit) {
p->update_no_reboot_bit = pdata->update_no_reboot_bit;
p->no_reboot_priv = pdata->no_reboot_priv;
return;
}
if (val32 & enable_bit)
return -EIO;
if (p->iTCO_version >= 2)
p->update_no_reboot_bit = update_no_reboot_bit_mem;
else if (p->iTCO_version == 1)
p->update_no_reboot_bit = update_no_reboot_bit_pci;
else
p->update_no_reboot_bit = update_no_reboot_bit_def;
return 0;
p->no_reboot_priv = p;
}
static int iTCO_wdt_start(struct watchdog_device *wd_dev)
......@@ -222,7 +248,7 @@ static int iTCO_wdt_start(struct watchdog_device *wd_dev)
iTCO_vendor_pre_start(p->smi_res, wd_dev->timeout);
/* disable chipset's NO_REBOOT bit */
if (iTCO_wdt_unset_NO_REBOOT_bit(p)) {
if (p->update_no_reboot_bit(p->no_reboot_priv, false)) {
spin_unlock(&p->io_lock);
pr_err("failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n");
return -EIO;
......@@ -263,7 +289,7 @@ static int iTCO_wdt_stop(struct watchdog_device *wd_dev)
val = inw(TCO1_CNT(p));
/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
iTCO_wdt_set_NO_REBOOT_bit(p);
p->update_no_reboot_bit(p->no_reboot_priv, true);
spin_unlock(&p->io_lock);
......@@ -428,11 +454,13 @@ static int iTCO_wdt_probe(struct platform_device *pdev)
p->iTCO_version = pdata->version;
p->pci_dev = to_pci_dev(dev->parent);
iTCO_wdt_no_reboot_bit_setup(p, pdata);
/*
* Get the Memory-Mapped GCS or PMC register, we need it for the
* NO_REBOOT flag (TCO v2 and v3).
*/
if (p->iTCO_version >= 2) {
if (p->iTCO_version >= 2 && !pdata->update_no_reboot_bit) {
p->gcs_pmc_res = platform_get_resource(pdev,
IORESOURCE_MEM,
ICH_RES_MEM_GCS_PMC);
......@@ -442,14 +470,14 @@ static int iTCO_wdt_probe(struct platform_device *pdev)
}
/* Check chipset's NO_REBOOT bit */
if (iTCO_wdt_unset_NO_REBOOT_bit(p) &&
if (p->update_no_reboot_bit(p->no_reboot_priv, false) &&
iTCO_vendor_check_noreboot_on()) {
pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n");
return -ENODEV; /* Cannot reset NO_REBOOT bit */
}
/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
iTCO_wdt_set_NO_REBOOT_bit(p);
p->update_no_reboot_bit(p->no_reboot_priv, true);
/* The TCO logic uses the TCO_EN bit in the SMI_EN register */
if (!devm_request_region(dev, p->smi_res->start,
......
......@@ -14,6 +14,10 @@
struct itco_wdt_platform_data {
char name[32];
unsigned int version;
/* private data to be passed to update_no_reboot_bit API */
void *no_reboot_priv;
/* pointer for platform specific no reboot update function */
int (*update_no_reboot_bit)(void *priv, bool set);
};
#endif /* _ITCO_WDT_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment