Commit 899fbc33 authored by Linus Torvalds's avatar Linus Torvalds

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

Pull x86 platform driver updates from Andy Shevchenko:

 - The driver for Silead touchscreen configurations has been renamed
   from silead_dmi to touchscreen_dmi since it starts supporting other
   touchscreens which require some DMI quirks

   It also gets expanded to cover cases for Chuwi Vi10, ONDA V891W,
   Connect Tablet 9, Onda V820w, and Cube KNote i1101 tablets.

 - Another bunch of changes is related to Mellanox platform code to
   allow user space to communicate with Mellanox for system control and
   monitoring purposes. The driver notifies user on hotplug device
   signal receiving.

 - ASUS WMI drivers recognize lid flip action on UX360, and correctly
   toggles airplane mode LED. In addition the keyboard backlight toggle
   gets support.

 - ThinkPad ACPI driver enables support for calculator key (on at least
   P52). It also has been fixed to support three characters model
   designators, which are used for modern laptops. Earlier the battery,
   marked as BAT1, on ThinkPad laptops has not been configured properly,
   which is fixed. On the opposite the multi-battery configurations now
   probed correctly.

 - Dell SMBIOS driver starts working on some Dell servers which do not
   support token interface. The regression with backlight detection has
   also been fixed. In order to support dock mode on some laptops, Intel
   virtual button driver has been fixed. The last but not least is the
   fix to Intel HID driver due to changes in Dell systems that prevented
   to use power button.

* tag 'platform-drivers-x86-v4.19-1' of git://git.infradead.org/linux-platform-drivers-x86: (47 commits)
  platform/x86: acer-wmi: Silence "unsupported" message a bit
  platform/x86: intel_punit_ipc: fix build errors
  platform/x86: ideapad: Add Y520-15IKBM and Y720-15IKBM to no_hw_rfkill
  platform/x86: asus-nb-wmi: Add keymap entry for lid flip action on UX360
  platform/x86: acer-wmi: refactor function has_cap
  platform/x86: thinkpad_acpi: Fix multi-battery bug
  platform/x86: thinkpad_acpi: extend battery quirk coverage
  platform/x86: touchscreen_dmi: Add info for the Cube KNote i1101 tablet
  platform/x86: mlx-platform: Fix copy-paste error in mlxplat_init()
  platform/x86: mlx-platform: Remove unused define
  platform/x86: mlx-platform: Change mlxreg-io configuration for MSN274x systems
  Documentation/ABI: Add new attribute for mlxreg-io sysfs interfaces
  platform/x86: mlx-platform: Allow mlxreg-io driver activation for more systems
  platform/x86: mlx-platform: Add ASIC hotplug device configuration
  platform/mellanox: mlxreg-hotplug: Add hotplug hwmon uevent notification
  platform/mellanox: mlxreg-hotplug: Improve mechanism of ASIC health discovery
  platform/x86: mlx-platform: Add mlxreg-fan platform driver activation
  platform/x86: dell-laptop: Fix backlight detection
  platform/x86: toshiba_acpi: Fix defined but not used build warnings
  platform/x86: thinkpad_acpi: Support battery quirk
  ...
parents 2edd73a4 9bd5196e
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/
asic_health
Date: June 2018
KernelVersion: 4.19
Contact: Vadim Pasternak <vadimpmellanox.com>
Description: This file shows ASIC health status. The possible values are:
0 - health failed, 2 - health OK, 3 - ASIC in booting state.
The files are read only.
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/
cpld1_version
cpld2_version
Date: June 2018
KernelVersion: 4.19
Contact: Vadim Pasternak <vadimpmellanox.com>
Description: These files show with which CPLD versions have been burned
on carrier and switch boards.
The files are read only.
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/select_iio
Date: June 2018
KernelVersion: 4.19
Contact: Vadim Pasternak <vadimpmellanox.com>
Description: This file allows iio devices selection.
Attribute select_iio can be written with 0 or with 1. It
selects which one of iio devices can be accessed.
The file is read/write.
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/psu1_on
/sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/psu2_on
/sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/pwr_cycle
/sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/pwr_down
Date: June 2018
KernelVersion: 4.19
Contact: Vadim Pasternak <vadimpmellanox.com>
Description: These files allow asserting system power cycling, switching
power supply units on and off and system's main power domain
shutdown.
Expected behavior:
When pwr_cycle is written 1: auxiliary power domain will go
down and after short period (about 1 second) up.
When psu1_on or psu2_on is written 1, related unit will be
disconnected from the power source, when written 0 - connected.
If both are written 1 - power supplies main power domain will
go down.
When pwr_down is written 1, system's main power domain will go
down.
The files are write only.
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/
reset_aux_pwr_or_ref
reset_asic_thermal
reset_hotswap_or_halt
reset_hotswap_or_wd
reset_fw_reset
reset_long_pb
reset_main_pwr_fail
reset_short_pb
reset_sw_reset
Date: June 2018
KernelVersion: 4.19
Contact: Vadim Pasternak <vadimpmellanox.com>
Description: These files show the system reset cause, as following: power
auxiliary outage or power refresh, ASIC thermal shutdown, halt,
hotswap, watchdog, firmware reset, long press power button,
short press power button, software reset. Value 1 in file means
this is reset cause, 0 - otherwise. Only one of the above
causes could be 1 at the same time, representing only last
reset cause.
The files are read only.
......@@ -13207,7 +13207,7 @@ L: linux-input@vger.kernel.org
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/input/touchscreen/silead.c
F: drivers/platform/x86/silead_dmi.c
F: drivers/platform/x86/touchscreen_dmi.c
SILICON MOTION SM712 FRAME BUFFER DRIVER
M: Sudip Mukherjee <sudipm.mukherjee@gmail.com>
......
......@@ -23,4 +23,15 @@ config MLXREG_HOTPLUG
This driver handles hot-plug events for the power suppliers, power
cables and fans on the wide range Mellanox IB and Ethernet systems.
config MLXREG_IO
tristate "Mellanox platform register access driver support"
depends on REGMAP
depends on HWMON
help
This driver allows access to Mellanox programmable device register
space through sysfs interface. The sets of registers for sysfs access
are defined per system type bases and include the registers related
to system resets operation, system reset causes monitoring and some
kinds of mux selection.
endif # MELLANOX_PLATFORM
......@@ -4,3 +4,4 @@
# Mellanox Platform-Specific Drivers
#
obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o
obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o
......@@ -50,9 +50,8 @@
#define MLXREG_HOTPLUG_MASK_OFF 2
#define MLXREG_HOTPLUG_AGGR_MASK_OFF 1
/* ASIC health parameters. */
#define MLXREG_HOTPLUG_HEALTH_MASK 0x02
#define MLXREG_HOTPLUG_RST_CNTR 3
/* ASIC good health mask. */
#define MLXREG_HOTPLUG_GOOD_HEALTH_MASK 0x02
#define MLXREG_HOTPLUG_ATTRS_MAX 24
#define MLXREG_HOTPLUG_NOT_ASSERT 3
......@@ -103,6 +102,9 @@ static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv,
{
struct mlxreg_core_hotplug_platform_data *pdata;
/* Notify user by sending hwmon uevent. */
kobject_uevent(&priv->hwmon->kobj, KOBJ_CHANGE);
/*
* Return if adapter number is negative. It could be in case hotplug
* event is not associated with hotplug device.
......@@ -134,8 +136,13 @@ static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv,
return 0;
}
static void mlxreg_hotplug_device_destroy(struct mlxreg_core_data *data)
static void
mlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data *priv,
struct mlxreg_core_data *data)
{
/* Notify user by sending hwmon uevent. */
kobject_uevent(&priv->hwmon->kobj, KOBJ_CHANGE);
if (data->hpdev.client) {
i2c_unregister_device(data->hpdev.client);
data->hpdev.client = NULL;
......@@ -278,14 +285,14 @@ mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv,
data = item->data + bit;
if (regval & BIT(bit)) {
if (item->inversed)
mlxreg_hotplug_device_destroy(data);
mlxreg_hotplug_device_destroy(priv, data);
else
mlxreg_hotplug_device_create(priv, data);
} else {
if (item->inversed)
mlxreg_hotplug_device_create(priv, data);
else
mlxreg_hotplug_device_destroy(data);
mlxreg_hotplug_device_destroy(priv, data);
}
}
......@@ -325,21 +332,40 @@ mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv,
goto out;
regval &= data->mask;
item->cache = regval;
if (regval == MLXREG_HOTPLUG_HEALTH_MASK) {
if ((data->health_cntr++ == MLXREG_HOTPLUG_RST_CNTR) ||
!priv->after_probe) {
if (item->cache == regval)
goto ack_event;
/*
* ASIC health indication is provided through two bits. Bits
* value 0x2 indicates that ASIC reached the good health, value
* 0x0 indicates ASIC the bad health or dormant state and value
* 0x3 indicates the booting state. During ASIC reset it should
* pass the following states: dormant -> booting -> good.
*/
if (regval == MLXREG_HOTPLUG_GOOD_HEALTH_MASK) {
if (!data->attached) {
/*
* ASIC is in steady state. Connect associated
* device, if configured.
*/
mlxreg_hotplug_device_create(priv, data);
data->attached = true;
}
} else {
if (data->attached) {
mlxreg_hotplug_device_destroy(data);
/*
* ASIC health is failed after ASIC has been
* in steady state. Disconnect associated
* device, if it has been connected.
*/
mlxreg_hotplug_device_destroy(priv, data);
data->attached = false;
data->health_cntr = 0;
}
}
item->cache = regval;
ack_event:
/* Acknowledge event. */
ret = regmap_write(priv->regmap, data->reg +
MLXREG_HOTPLUG_EVENT_OFF, 0);
......@@ -551,7 +577,7 @@ static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv)
/* Remove all the attached devices in group. */
count = item->count;
for (j = 0; j < count; j++, data++)
mlxreg_hotplug_device_destroy(data);
mlxreg_hotplug_device_destroy(priv, data);
}
}
......@@ -616,10 +642,6 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev)
disable_irq(priv->irq);
spin_lock_init(&priv->lock);
INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler);
/* Perform initial interrupts setup. */
mlxreg_hotplug_set_irq(priv);
priv->after_probe = true;
dev_set_drvdata(&pdev->dev, priv);
err = mlxreg_hotplug_attr_init(priv);
......@@ -637,6 +659,10 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev)
return PTR_ERR(priv->hwmon);
}
/* Perform initial interrupts setup. */
mlxreg_hotplug_set_irq(priv);
priv->after_probe = true;
return 0;
}
......
// SPDX-License-Identifier: GPL-2.0+
/*
* Mellanox register access driver
*
* Copyright (C) 2018 Mellanox Technologies
* Copyright (C) 2018 Vadim Pasternak <vadimp@mellanox.com>
*/
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_data/mlxreg.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
/* Attribute parameters. */
#define MLXREG_IO_ATT_SIZE 10
#define MLXREG_IO_ATT_NUM 48
/**
* struct mlxreg_io_priv_data - driver's private data:
*
* @pdev: platform device;
* @pdata: platform data;
* @hwmon: hwmon device;
* @mlxreg_io_attr: sysfs attributes array;
* @mlxreg_io_dev_attr: sysfs sensor device attribute array;
* @group: sysfs attribute group;
* @groups: list of sysfs attribute group for hwmon registration;
*/
struct mlxreg_io_priv_data {
struct platform_device *pdev;
struct mlxreg_core_platform_data *pdata;
struct device *hwmon;
struct attribute *mlxreg_io_attr[MLXREG_IO_ATT_NUM + 1];
struct sensor_device_attribute mlxreg_io_dev_attr[MLXREG_IO_ATT_NUM];
struct attribute_group group;
const struct attribute_group *groups[2];
};
static int
mlxreg_io_get_reg(void *regmap, struct mlxreg_core_data *data, u32 in_val,
bool rw_flag, u32 *regval)
{
int ret;
ret = regmap_read(regmap, data->reg, regval);
if (ret)
goto access_error;
/*
* There are three kinds of attributes: single bit, full register's
* bits and bit sequence. For the first kind field mask indicates which
* bits are not related and field bit is set zero. For the second kind
* field mask is set to zero and field bit is set with all bits one.
* No special handling for such kind of attributes - pass value as is.
* For the third kind, field mask indicates which bits are related and
* field bit is set to the first bit number (from 1 to 32) is the bit
* sequence.
*/
if (!data->bit) {
/* Single bit. */
if (rw_flag) {
/* For show: expose effective bit value as 0 or 1. */
*regval = !!(*regval & ~data->mask);
} else {
/* For store: set effective bit value. */
*regval &= data->mask;
if (in_val)
*regval |= ~data->mask;
}
} else if (data->mask) {
/* Bit sequence. */
if (rw_flag) {
/* For show: mask and shift right. */
*regval = ror32(*regval & data->mask, (data->bit - 1));
} else {
/* For store: shift to the position and mask. */
in_val = rol32(in_val, data->bit - 1) & data->mask;
/* Clear relevant bits and set them to new value. */
*regval = (*regval & ~data->mask) | in_val;
}
}
access_error:
return ret;
}
static ssize_t
mlxreg_io_attr_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev);
int index = to_sensor_dev_attr(attr)->index;
struct mlxreg_core_data *data = priv->pdata->data + index;
u32 regval = 0;
int ret;
ret = mlxreg_io_get_reg(priv->pdata->regmap, data, 0, true, &regval);
if (ret)
goto access_error;
return sprintf(buf, "%u\n", regval);
access_error:
return ret;
}
static ssize_t
mlxreg_io_attr_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev);
int index = to_sensor_dev_attr(attr)->index;
struct mlxreg_core_data *data = priv->pdata->data + index;
u32 input_val, regval;
int ret;
if (len > MLXREG_IO_ATT_SIZE)
return -EINVAL;
/* Convert buffer to input value. */
ret = kstrtou32(buf, len, &input_val);
if (ret)
return ret;
ret = mlxreg_io_get_reg(priv->pdata->regmap, data, input_val, false,
&regval);
if (ret)
goto access_error;
ret = regmap_write(priv->pdata->regmap, data->reg, regval);
if (ret)
goto access_error;
return len;
access_error:
dev_err(&priv->pdev->dev, "Bus access error\n");
return ret;
}
static struct device_attribute mlxreg_io_devattr_rw = {
.show = mlxreg_io_attr_show,
.store = mlxreg_io_attr_store,
};
static int mlxreg_io_attr_init(struct mlxreg_io_priv_data *priv)
{
int i;
priv->group.attrs = devm_kcalloc(&priv->pdev->dev,
priv->pdata->counter,
sizeof(struct attribute *),
GFP_KERNEL);
if (!priv->group.attrs)
return -ENOMEM;
for (i = 0; i < priv->pdata->counter; i++) {
priv->mlxreg_io_attr[i] =
&priv->mlxreg_io_dev_attr[i].dev_attr.attr;
memcpy(&priv->mlxreg_io_dev_attr[i].dev_attr,
&mlxreg_io_devattr_rw, sizeof(struct device_attribute));
/* Set attribute name as a label. */
priv->mlxreg_io_attr[i]->name =
devm_kasprintf(&priv->pdev->dev, GFP_KERNEL,
priv->pdata->data[i].label);
if (!priv->mlxreg_io_attr[i]->name) {
dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n",
i + 1);
return -ENOMEM;
}
priv->mlxreg_io_dev_attr[i].dev_attr.attr.mode =
priv->pdata->data[i].mode;
priv->mlxreg_io_dev_attr[i].dev_attr.attr.name =
priv->mlxreg_io_attr[i]->name;
priv->mlxreg_io_dev_attr[i].index = i;
sysfs_attr_init(&priv->mlxreg_io_dev_attr[i].dev_attr.attr);
}
priv->group.attrs = priv->mlxreg_io_attr;
priv->groups[0] = &priv->group;
priv->groups[1] = NULL;
return 0;
}
static int mlxreg_io_probe(struct platform_device *pdev)
{
struct mlxreg_io_priv_data *priv;
int err;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->pdata = dev_get_platdata(&pdev->dev);
if (!priv->pdata) {
dev_err(&pdev->dev, "Failed to get platform data.\n");
return -EINVAL;
}
priv->pdev = pdev;
err = mlxreg_io_attr_init(priv);
if (err) {
dev_err(&priv->pdev->dev, "Failed to allocate attributes: %d\n",
err);
return err;
}
priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev,
"mlxreg_io",
priv,
priv->groups);
if (IS_ERR(priv->hwmon)) {
dev_err(&pdev->dev, "Failed to register hwmon device %ld\n",
PTR_ERR(priv->hwmon));
return PTR_ERR(priv->hwmon);
}
dev_set_drvdata(&pdev->dev, priv);
return 0;
}
static struct platform_driver mlxreg_io_driver = {
.driver = {
.name = "mlxreg-io",
},
.probe = mlxreg_io_probe,
};
module_platform_driver(mlxreg_io_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
MODULE_DESCRIPTION("Mellanox regmap I/O access driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mlxreg-io");
......@@ -1196,16 +1196,16 @@ config INTEL_TURBO_MAX_3
This driver is only required when the system is not using Hardware
P-States (HWP). In HWP mode, priority can be read from ACPI tables.
config SILEAD_DMI
bool "Tablets with Silead touchscreens"
config TOUCHSCREEN_DMI
bool "DMI based touchscreen configuration info"
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
the touchscreen properly, as OEMs expected the data to be baked
into the tablet model specific version of the driver shipped
with the OS-image for the device. This option supplies the missing
information. Enable this for x86 tablets with Silead touchscreens.
Certain ACPI based tablets with e.g. Silead or Chipone touchscreens
do not have enough data in ACPI tables for the touchscreen driver to
handle the touchscreen properly, as OEMs expect the data to be baked
into the tablet model specific version of the driver shipped with the
the OS-image for the device. This option supplies the missing info.
Enable this for x86 tablets with Silead or Chipone touchscreens.
config INTEL_CHTDC_TI_PWRBTN
tristate "Intel Cherry Trail Dollar Cove TI power button driver"
......
......@@ -78,7 +78,7 @@ obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o
obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o
obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o
obj-$(CONFIG_SILEAD_DMI) += silead_dmi.o
obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
......
......@@ -672,10 +672,7 @@ static void __init find_quirks(void)
static bool has_cap(u32 cap)
{
if ((interface->capability & cap) != 0)
return 1;
return 0;
return interface->capability & cap;
}
/*
......@@ -2216,7 +2213,7 @@ static int __init acer_wmi_init(void)
if (wmi_has_guid(AMW0_GUID1) &&
!dmi_check_system(amw0_whitelist) &&
quirks == &quirk_unknown) {
pr_err("Unsupported machine has AMW0_GUID1, unable to load\n");
pr_debug("Unsupported machine has AMW0_GUID1, unable to load\n");
return -ENODEV;
}
......
......@@ -496,6 +496,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
{ KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
{ KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
{ KE_IGNORE, 0xC6, }, /* Ambient Light Sensor notification */
{ KE_KEY, 0xFA, { KEY_PROG2 } }, /* Lid flip action */
{ KE_END, 0},
};
......
......@@ -52,13 +52,12 @@ static const struct acpi_device_id device_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, device_ids);
static u64 asus_wireless_method(acpi_handle handle, const char *method,
int param)
static acpi_status asus_wireless_method(acpi_handle handle, const char *method,
int param, u64 *ret)
{
struct acpi_object_list p;
union acpi_object obj;
acpi_status s;
u64 ret;
acpi_handle_debug(handle, "Evaluating method %s, parameter %#x\n",
method, param);
......@@ -67,24 +66,27 @@ static u64 asus_wireless_method(acpi_handle handle, const char *method,
p.count = 1;
p.pointer = &obj;
s = acpi_evaluate_integer(handle, (acpi_string) method, &p, &ret);
s = acpi_evaluate_integer(handle, (acpi_string) method, &p, ret);
if (ACPI_FAILURE(s))
acpi_handle_err(handle,
"Failed to eval method %s, param %#x (%d)\n",
method, param, s);
acpi_handle_debug(handle, "%s returned %#llx\n", method, ret);
return ret;
else
acpi_handle_debug(handle, "%s returned %#llx\n", method, *ret);
return s;
}
static enum led_brightness led_state_get(struct led_classdev *led)
{
struct asus_wireless_data *data;
int s;
acpi_status s;
u64 ret;
data = container_of(led, struct asus_wireless_data, led);
s = asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
data->hswc_params->status);
if (s == data->hswc_params->on)
data->hswc_params->status, &ret);
if (ACPI_SUCCESS(s) && ret == data->hswc_params->on)
return LED_FULL;
return LED_OFF;
}
......@@ -92,10 +94,11 @@ static enum led_brightness led_state_get(struct led_classdev *led)
static void led_state_update(struct work_struct *work)
{
struct asus_wireless_data *data;
u64 ret;
data = container_of(work, struct asus_wireless_data, led_work);
asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
data->led_state);
data->led_state, &ret);
}
static void led_state_set(struct led_classdev *led, enum led_brightness value)
......@@ -167,6 +170,7 @@ static int asus_wireless_add(struct acpi_device *adev)
data->led.brightness_get = led_state_get;
data->led.flags = LED_CORE_SUSPENDRESUME;
data->led.max_brightness = 1;
data->led.default_trigger = "rfkill-none";
err = devm_led_classdev_register(&adev->dev, &data->led);
if (err)
destroy_workqueue(data->wq);
......
......@@ -67,6 +67,7 @@ MODULE_LICENSE("GPL");
#define NOTIFY_BRNDOWN_MAX 0x2e
#define NOTIFY_KBD_BRTUP 0xc4
#define NOTIFY_KBD_BRTDWN 0xc5
#define NOTIFY_KBD_BRTTOGGLE 0xc7
/* WMI Methods */
#define ASUS_WMI_METHODID_SPEC 0x43455053 /* BIOS SPECification */
......@@ -470,6 +471,7 @@ static void kbd_led_update(struct work_struct *work)
ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
led_classdev_notify_brightness_hw_changed(&asus->kbd_led, asus->kbd_led_wk);
}
static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
......@@ -500,15 +502,16 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
return retval;
}
static void kbd_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
{
struct asus_wmi *asus;
int max_level;
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
max_level = asus->kbd_led.max_brightness;
if (value > asus->kbd_led.max_brightness)
value = asus->kbd_led.max_brightness;
if (value > max_level)
value = max_level;
else if (value < 0)
value = 0;
......@@ -516,6 +519,12 @@ static void kbd_led_set(struct led_classdev *led_cdev,
queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
static void kbd_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
do_kbd_led_set(led_cdev, value);
}
static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
{
struct asus_wmi *asus;
......@@ -666,6 +675,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
asus->kbd_led_wk = led_val;
asus->kbd_led.name = "asus::kbd_backlight";
asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
asus->kbd_led.brightness_set = kbd_led_set;
asus->kbd_led.brightness_get = kbd_led_get;
asus->kbd_led.max_brightness = 3;
......@@ -1754,6 +1764,22 @@ static void asus_wmi_notify(u32 value, void *context)
}
}
if (code == NOTIFY_KBD_BRTUP) {
do_kbd_led_set(&asus->kbd_led, asus->kbd_led_wk + 1);
goto exit;
}
if (code == NOTIFY_KBD_BRTDWN) {
do_kbd_led_set(&asus->kbd_led, asus->kbd_led_wk - 1);
goto exit;
}
if (code == NOTIFY_KBD_BRTTOGGLE) {
if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
do_kbd_led_set(&asus->kbd_led, 0);
else
do_kbd_led_set(&asus->kbd_led, asus->kbd_led_wk + 1);
goto exit;
}
if (is_display_toggle(code) &&
asus->driver->quirks->no_display_toggle)
goto exit;
......
......@@ -212,6 +212,12 @@ int dell_smbios_call_filter(struct device *d,
if ((buffer->cmd_class == CLASS_TOKEN_READ ||
buffer->cmd_class == CLASS_TOKEN_WRITE) &&
buffer->cmd_select < 3) {
/* tokens enabled ? */
if (!da_tokens) {
dev_dbg(d, "no token support on this system\n");
return -EINVAL;
}
/* find the matching token ID */
for (i = 0; i < da_num_tokens; i++) {
if (da_tokens[i].location != buffer->input[0])
......@@ -315,6 +321,9 @@ struct calling_interface_token *dell_smbios_find_token(int tokenid)
{
int i;
if (!da_tokens)
return NULL;
for (i = 0; i < da_num_tokens; i++) {
if (da_tokens[i].tokenID == tokenid)
return &da_tokens[i];
......@@ -565,11 +574,6 @@ static int __init dell_smbios_init(void)
dmi_walk(find_tokens, NULL);
if (!da_tokens) {
pr_info("Unable to find dmi tokens\n");
return -ENODEV;
}
ret = platform_driver_register(&platform_driver);
if (ret)
goto fail_platform_driver;
......@@ -583,13 +587,6 @@ static int __init dell_smbios_init(void)
if (ret)
goto fail_platform_device_add;
/* duplicate tokens will cause problems building sysfs files */
zero_duplicates(&platform_device->dev);
ret = build_tokens_sysfs(platform_device);
if (ret)
goto fail_create_group;
/* register backends */
wmi = init_dell_smbios_wmi();
if (wmi)
......@@ -600,7 +597,16 @@ static int __init dell_smbios_init(void)
if (wmi && smm) {
pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n",
wmi, smm);
goto fail_sysfs;
goto fail_create_group;
}
if (da_tokens) {
/* duplicate tokens will cause problems building sysfs files */
zero_duplicates(&platform_device->dev);
ret = build_tokens_sysfs(platform_device);
if (ret)
goto fail_sysfs;
}
return 0;
......@@ -628,7 +634,8 @@ static void __exit dell_smbios_exit(void)
exit_dell_smbios_smm();
mutex_lock(&smbios_mutex);
if (platform_device) {
free_group(platform_device);
if (da_tokens)
free_group(platform_device);
platform_device_unregister(platform_device);
platform_driver_unregister(&platform_driver);
}
......
......@@ -24,7 +24,7 @@
static int da_command_address;
static int da_command_code;
static struct calling_interface_buffer *buffer;
struct platform_device *platform_device;
static struct platform_device *platform_device;
static DEFINE_MUTEX(smm_mutex);
static const struct dmi_system_id dell_device_table[] __initconst = {
......@@ -82,7 +82,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy)
}
}
int dell_smbios_smm_call(struct calling_interface_buffer *input)
static int dell_smbios_smm_call(struct calling_interface_buffer *input)
{
struct smi_cmd command;
size_t size;
......
......@@ -82,7 +82,7 @@ static int run_smbios_call(struct wmi_device *wdev)
return 0;
}
int dell_smbios_wmi_call(struct calling_interface_buffer *buffer)
static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer)
{
struct wmi_smbios_priv *priv;
size_t difference;
......
......@@ -1133,10 +1133,17 @@ static const struct dmi_system_id no_hw_rfkill_list[] = {
},
},
{
.ident = "Lenovo Legion Y520-15IKBN",
.ident = "Lenovo Legion Y520-15IKB",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKBN"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKB"),
},
},
{
.ident = "Lenovo Y520-15IKBM",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKBM"),
},
},
{
......@@ -1153,6 +1160,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = {
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKBN"),
},
},
{
.ident = "Lenovo Y720-15IKBM",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKBM"),
},
},
{
.ident = "Lenovo Yoga 2 11 / 13 / Pro",
.matches = {
......
......@@ -96,13 +96,140 @@ struct intel_hid_priv {
bool wakeup_mode;
};
static int intel_hid_set_enable(struct device *device, bool enable)
#define HID_EVENT_FILTER_UUID "eeec56b3-4442-408f-a792-4edd4d758054"
enum intel_hid_dsm_fn_codes {
INTEL_HID_DSM_FN_INVALID,
INTEL_HID_DSM_BTNL_FN,
INTEL_HID_DSM_HDMM_FN,
INTEL_HID_DSM_HDSM_FN,
INTEL_HID_DSM_HDEM_FN,
INTEL_HID_DSM_BTNS_FN,
INTEL_HID_DSM_BTNE_FN,
INTEL_HID_DSM_HEBC_V1_FN,
INTEL_HID_DSM_VGBS_FN,
INTEL_HID_DSM_HEBC_V2_FN,
INTEL_HID_DSM_FN_MAX
};
static const char *intel_hid_dsm_fn_to_method[INTEL_HID_DSM_FN_MAX] = {
NULL,
"BTNL",
"HDMM",
"HDSM",
"HDEM",
"BTNS",
"BTNE",
"HEBC",
"VGBS",
"HEBC"
};
static unsigned long long intel_hid_dsm_fn_mask;
static guid_t intel_dsm_guid;
static bool intel_hid_execute_method(acpi_handle handle,
enum intel_hid_dsm_fn_codes fn_index,
unsigned long long arg)
{
union acpi_object *obj, argv4, req;
acpi_status status;
char *method_name;
status = acpi_execute_simple_method(ACPI_HANDLE(device), "HDSM",
enable);
if (ACPI_FAILURE(status)) {
if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
fn_index >= INTEL_HID_DSM_FN_MAX)
return false;
method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
if (!(intel_hid_dsm_fn_mask & fn_index))
goto skip_dsm_exec;
/* All methods expects a package with one integer element */
req.type = ACPI_TYPE_INTEGER;
req.integer.value = arg;
argv4.type = ACPI_TYPE_PACKAGE;
argv4.package.count = 1;
argv4.package.elements = &req;
obj = acpi_evaluate_dsm(handle, &intel_dsm_guid, 1, fn_index, &argv4);
if (obj) {
acpi_handle_debug(handle, "Exec DSM Fn code: %d[%s] success\n",
fn_index, method_name);
ACPI_FREE(obj);
return true;
}
skip_dsm_exec:
status = acpi_execute_simple_method(handle, method_name, arg);
if (ACPI_SUCCESS(status))
return true;
return false;
}
static bool intel_hid_evaluate_method(acpi_handle handle,
enum intel_hid_dsm_fn_codes fn_index,
unsigned long long *result)
{
union acpi_object *obj;
acpi_status status;
char *method_name;
if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
fn_index >= INTEL_HID_DSM_FN_MAX)
return false;
method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
if (!(intel_hid_dsm_fn_mask & fn_index))
goto skip_dsm_eval;
obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid,
1, fn_index,
NULL, ACPI_TYPE_INTEGER);
if (obj) {
*result = obj->integer.value;
acpi_handle_debug(handle,
"Eval DSM Fn code: %d[%s] results: 0x%llx\n",
fn_index, method_name, *result);
ACPI_FREE(obj);
return true;
}
skip_dsm_eval:
status = acpi_evaluate_integer(handle, method_name, NULL, result);
if (ACPI_SUCCESS(status))
return true;
return false;
}
static void intel_hid_init_dsm(acpi_handle handle)
{
union acpi_object *obj;
guid_parse(HID_EVENT_FILTER_UUID, &intel_dsm_guid);
obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL,
ACPI_TYPE_BUFFER);
if (obj) {
intel_hid_dsm_fn_mask = *obj->buffer.pointer;
ACPI_FREE(obj);
}
acpi_handle_debug(handle, "intel_hid_dsm_fn_mask = %llx\n",
intel_hid_dsm_fn_mask);
}
static int intel_hid_set_enable(struct device *device, bool enable)
{
acpi_handle handle = ACPI_HANDLE(device);
/* Enable|disable features - power button is always enabled */
if (!intel_hid_execute_method(handle, INTEL_HID_DSM_HDSM_FN,
enable)) {
dev_warn(device, "failed to %sable hotkeys\n",
enable ? "en" : "dis");
return -EIO;
......@@ -129,9 +256,8 @@ static void intel_button_array_enable(struct device *device, bool enable)
}
/* Enable|disable features - power button is always enabled */
status = acpi_execute_simple_method(handle, "BTNE",
enable ? button_cap : 1);
if (ACPI_FAILURE(status))
if (!intel_hid_execute_method(handle, INTEL_HID_DSM_BTNE_FN,
enable ? button_cap : 1))
dev_warn(device, "failed to set button capability\n");
}
......@@ -217,7 +343,6 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
struct platform_device *device = context;
struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
unsigned long long ev_index;
acpi_status status;
if (priv->wakeup_mode) {
/*
......@@ -269,8 +394,8 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)
return;
}
status = acpi_evaluate_integer(handle, "HDEM", NULL, &ev_index);
if (ACPI_FAILURE(status)) {
if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDEM_FN,
&ev_index)) {
dev_warn(&device->dev, "failed to get event index\n");
return;
}
......@@ -284,17 +409,24 @@ static bool button_array_present(struct platform_device *device)
{
acpi_handle handle = ACPI_HANDLE(&device->dev);
unsigned long long event_cap;
acpi_status status;
bool supported = false;
status = acpi_evaluate_integer(handle, "HEBC", NULL, &event_cap);
if (ACPI_SUCCESS(status) && (event_cap & 0x20000))
supported = true;
if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V2_FN,
&event_cap)) {
/* Check presence of 5 button array or v2 power button */
if (event_cap & 0x60000)
return true;
}
if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V1_FN,
&event_cap)) {
if (event_cap & 0x20000)
return true;
}
if (dmi_check_system(button_array_table))
supported = true;
return true;
return supported;
return false;
}
static int intel_hid_probe(struct platform_device *device)
......@@ -305,8 +437,9 @@ static int intel_hid_probe(struct platform_device *device)
acpi_status status;
int err;
status = acpi_evaluate_integer(handle, "HDMM", NULL, &mode);
if (ACPI_FAILURE(status)) {
intel_hid_init_dsm(handle);
if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) {
dev_warn(&device->dev, "failed to read mode\n");
return -ENODEV;
}
......@@ -352,13 +485,16 @@ static int intel_hid_probe(struct platform_device *device)
goto err_remove_notify;
if (priv->array) {
unsigned long long dummy;
intel_button_array_enable(&device->dev, true);
/* Call button load method to enable HID power button */
status = acpi_evaluate_object(handle, "BTNL", NULL, NULL);
if (ACPI_FAILURE(status))
if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_BTNL_FN,
&dummy)) {
dev_warn(&device->dev,
"failed to enable HID power button\n");
}
}
device_init_wakeup(&device->dev, true);
......
......@@ -17,6 +17,7 @@
/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
#define TABLET_MODE_FLAG 0x40
#define DOCK_MODE_FLAG 0x80
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AceLan Kao");
......@@ -38,6 +39,8 @@ static const struct key_entry intel_vbtn_keymap[] = {
{ KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */
{ KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */
{ KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */
{ KE_SW, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */
{ KE_SW, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */
{ KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */
{ KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */
{ KE_END },
......@@ -121,6 +124,8 @@ static void detect_tablet_mode(struct platform_device *device)
m = !(obj->integer.value & TABLET_MODE_FLAG);
input_report_switch(priv->input_dev, SW_TABLET_MODE, m);
m = (obj->integer.value & DOCK_MODE_FLAG) ? 1 : 0;
input_report_switch(priv->input_dev, SW_DOCK, m);
out:
kfree(vgbs_output.pointer);
}
......
......@@ -858,10 +858,7 @@ static u16 read_mgtv(struct ips_driver *ips)
static u16 read_ptv(struct ips_driver *ips)
{
u16 val, slope, offset;
slope = (ips->pta_val & PTA_SLOPE_MASK) >> PTA_SLOPE_SHIFT;
offset = ips->pta_val & PTA_OFFSET_MASK;
u16 val;
val = thm_readw(THM_PTV) & PTV_MASK;
......
......@@ -196,9 +196,67 @@ static const struct pmc_bit_map cnp_pfear_map[] = {
{}
};
static const struct pmc_bit_map cnp_slps0_dbg0_map[] = {
{"AUDIO_D3", BIT(0)},
{"OTG_D3", BIT(1)},
{"XHCI_D3", BIT(2)},
{"LPIO_D3", BIT(3)},
{"SDX_D3", BIT(4)},
{"SATA_D3", BIT(5)},
{"UFS0_D3", BIT(6)},
{"UFS1_D3", BIT(7)},
{"EMMC_D3", BIT(8)},
{}
};
static const struct pmc_bit_map cnp_slps0_dbg1_map[] = {
{"SDIO_PLL_OFF", BIT(0)},
{"USB2_PLL_OFF", BIT(1)},
{"AUDIO_PLL_OFF", BIT(2)},
{"OC_PLL_OFF", BIT(3)},
{"MAIN_PLL_OFF", BIT(4)},
{"XOSC_OFF", BIT(5)},
{"LPC_CLKS_GATED", BIT(6)},
{"PCIE_CLKREQS_IDLE", BIT(7)},
{"AUDIO_ROSC_OFF", BIT(8)},
{"HPET_XOSC_CLK_REQ", BIT(9)},
{"PMC_ROSC_SLOW_CLK", BIT(10)},
{"AON2_ROSC_GATED", BIT(11)},
{"CLKACKS_DEASSERTED", BIT(12)},
{}
};
static const struct pmc_bit_map cnp_slps0_dbg2_map[] = {
{"MPHY_CORE_GATED", BIT(0)},
{"CSME_GATED", BIT(1)},
{"USB2_SUS_GATED", BIT(2)},
{"DYN_FLEX_IO_IDLE", BIT(3)},
{"GBE_NO_LINK", BIT(4)},
{"THERM_SEN_DISABLED", BIT(5)},
{"PCIE_LOW_POWER", BIT(6)},
{"ISH_VNNAON_REQ_ACT", BIT(7)},
{"ISH_VNN_REQ_ACT", BIT(8)},
{"CNV_VNNAON_REQ_ACT", BIT(9)},
{"CNV_VNN_REQ_ACT", BIT(10)},
{"NPK_VNNON_REQ_ACT", BIT(11)},
{"PMSYNC_STATE_IDLE", BIT(12)},
{"ALST_GT_THRES", BIT(13)},
{"PMC_ARC_PG_READY", BIT(14)},
{}
};
static const struct pmc_bit_map *cnp_slps0_dbg_maps[] = {
cnp_slps0_dbg0_map,
cnp_slps0_dbg1_map,
cnp_slps0_dbg2_map,
NULL,
};
static const struct pmc_reg_map cnp_reg_map = {
.pfear_sts = cnp_pfear_map,
.slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
.slps0_dbg_maps = cnp_slps0_dbg_maps,
.slps0_dbg_offset = CNP_PMC_SLPS0_DBG_OFFSET,
.ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
.regmap_length = CNP_PMC_MMIO_REG_LEN,
.ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
......@@ -252,6 +310,8 @@ static int pmc_core_check_read_lock_bit(void)
}
#if IS_ENABLED(CONFIG_DEBUG_FS)
static bool slps0_dbg_latch;
static void pmc_core_display_map(struct seq_file *s, int index,
u8 pf_reg, const struct pmc_bit_map *pf_map)
{
......@@ -481,6 +541,57 @@ static const struct file_operations pmc_core_ltr_ignore_ops = {
.release = single_release,
};
static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset)
{
const struct pmc_reg_map *map = pmcdev->map;
u32 fd;
mutex_lock(&pmcdev->lock);
if (!reset && !slps0_dbg_latch)
goto out_unlock;
fd = pmc_core_reg_read(pmcdev, map->slps0_dbg_offset);
if (reset)
fd &= ~CNP_PMC_LATCH_SLPS0_EVENTS;
else
fd |= CNP_PMC_LATCH_SLPS0_EVENTS;
pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd);
slps0_dbg_latch = 0;
out_unlock:
mutex_unlock(&pmcdev->lock);
}
static int pmc_core_slps0_dbg_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map **maps = pmcdev->map->slps0_dbg_maps;
const struct pmc_bit_map *map;
int offset;
u32 data;
pmc_core_slps0_dbg_latch(pmcdev, false);
offset = pmcdev->map->slps0_dbg_offset;
while (*maps) {
map = *maps;
data = pmc_core_reg_read(pmcdev, offset);
offset += 4;
while (map->name) {
seq_printf(s, "SLP_S0_DBG: %-32s\tState: %s\n",
map->name,
data & map->bit_mask ?
"Yes" : "No");
++map;
}
++maps;
}
pmc_core_slps0_dbg_latch(pmcdev, true);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_slps0_dbg);
static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
{
debugfs_remove_recursive(pmcdev->dbgfs_dir);
......@@ -514,6 +625,15 @@ static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
0444, dir, pmcdev,
&pmc_core_mphy_pg_ops);
if (pmcdev->map->slps0_dbg_maps) {
debugfs_create_file("slp_s0_debug_status", 0444,
dir, pmcdev,
&pmc_core_slps0_dbg_fops);
debugfs_create_bool("slp_s0_dbg_latch", 0644,
dir, &slps0_dbg_latch);
}
return 0;
}
#else
......
......@@ -127,12 +127,14 @@ enum ppfear_regs {
#define CNP_PMC_SLP_S0_RES_COUNTER_OFFSET 0x193C
#define CNP_PMC_LTR_IGNORE_OFFSET 0x1B0C
#define CNP_PMC_PM_CFG_OFFSET 0x1818
#define CNP_PMC_SLPS0_DBG_OFFSET 0x10B4
/* Cannonlake: PGD PFET Enable Ack Status Register(s) start */
#define CNP_PMC_HOST_PPFEAR0A 0x1D90
#define CNP_PMC_MMIO_REG_LEN 0x2000
#define CNP_PPFEAR_NUM_ENTRIES 8
#define CNP_PMC_READ_DISABLE_BIT 22
#define CNP_PMC_LATCH_SLPS0_EVENTS BIT(31)
struct pmc_bit_map {
const char *name;
......@@ -145,6 +147,7 @@ struct pmc_bit_map {
* @pfear_sts: Maps name of IP block to PPFEAR* bit
* @mphy_sts: Maps name of MPHY lane to MPHY status lane status bit
* @pll_sts: Maps name of PLL to corresponding bit status
* @slps0_dbg_maps: Array of SLP_S0_DBG* registers containing debug info
* @slp_s0_offset: PWRMBASE offset to read SLP_S0 residency
* @ltr_ignore_offset: PWRMBASE offset to read/write LTR ignore bit
* @regmap_length: Length of memory to map from PWRMBASE address to access
......@@ -153,6 +156,7 @@ struct pmc_bit_map {
* PPFEAR
* @pm_cfg_offset: PWRMBASE offset to PM_CFG register
* @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE
* @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG*
*
* Each PCH has unique set of register offsets and bit indexes. This structure
* captures them to have a common implementation.
......@@ -161,6 +165,7 @@ struct pmc_reg_map {
const struct pmc_bit_map *pfear_sts;
const struct pmc_bit_map *mphy_sts;
const struct pmc_bit_map *pll_sts;
const struct pmc_bit_map **slps0_dbg_maps;
const u32 slp_s0_offset;
const u32 ltr_ignore_offset;
const int regmap_length;
......@@ -168,6 +173,7 @@ struct pmc_reg_map {
const int ppfear_buckets;
const u32 pm_cfg_offset;
const int pm_read_disable_bit;
const u32 slps0_dbg_offset;
};
/**
......
......@@ -18,6 +18,7 @@
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <asm/intel_punit_ipc.h>
......
This diff is collapsed.
......@@ -336,6 +336,7 @@ static struct {
u32 second_fan:1;
u32 beep_needs_two_args:1;
u32 mixer_no_level_control:1;
u32 battery_force_primary:1;
u32 input_device_registered:1;
u32 platform_drv_registered:1;
u32 platform_drv_attrs_registered:1;
......@@ -344,7 +345,6 @@ static struct {
u32 sensors_pdev_attrs_registered:1;
u32 hotkey_poll_active:1;
u32 has_adaptive_kbd:1;
u32 battery:1;
} tp_features;
static struct {
......@@ -359,9 +359,9 @@ struct thinkpad_id_data {
char *bios_version_str; /* Something like 1ZET51WW (1.03z) */
char *ec_version_str; /* Something like 1ZHT51WW-1.04a */
u16 bios_model; /* 1Y = 0x5931, 0 = unknown */
u16 ec_model;
u16 bios_release; /* 1ZETK1WW = 0x314b, 0 = unknown */
u32 bios_model; /* 1Y = 0x3159, 0 = unknown */
u32 ec_model;
u16 bios_release; /* 1ZETK1WW = 0x4b31, 0 = unknown */
u16 ec_release;
char *model_str; /* ThinkPad T43 */
......@@ -445,17 +445,20 @@ do { \
/*
* Quirk handling helpers
*
* ThinkPad IDs and versions seen in the field so far
* are two-characters from the set [0-9A-Z], i.e. base 36.
* ThinkPad IDs and versions seen in the field so far are
* two or three characters from the set [0-9A-Z], i.e. base 36.
*
* We use values well outside that range as specials.
*/
#define TPACPI_MATCH_ANY 0xffffU
#define TPACPI_MATCH_ANY 0xffffffffU
#define TPACPI_MATCH_ANY_VERSION 0xffffU
#define TPACPI_MATCH_UNKNOWN 0U
/* TPID('1', 'Y') == 0x5931 */
#define TPID(__c1, __c2) (((__c2) << 8) | (__c1))
/* TPID('1', 'Y') == 0x3159 */
#define TPID(__c1, __c2) (((__c1) << 8) | (__c2))
#define TPID3(__c1, __c2, __c3) (((__c1) << 16) | ((__c2) << 8) | (__c3))
#define TPVER TPID
#define TPACPI_Q_IBM(__id1, __id2, __quirk) \
{ .vendor = PCI_VENDOR_ID_IBM, \
......@@ -469,6 +472,12 @@ do { \
.ec = TPACPI_MATCH_ANY, \
.quirks = (__quirk) }
#define TPACPI_Q_LNV3(__id1, __id2, __id3, __quirk) \
{ .vendor = PCI_VENDOR_ID_LENOVO, \
.bios = TPID3(__id1, __id2, __id3), \
.ec = TPACPI_MATCH_ANY, \
.quirks = (__quirk) }
#define TPACPI_QEC_LNV(__id1, __id2, __quirk) \
{ .vendor = PCI_VENDOR_ID_LENOVO, \
.bios = TPACPI_MATCH_ANY, \
......@@ -477,8 +486,8 @@ do { \
struct tpacpi_quirk {
unsigned int vendor;
u16 bios;
u16 ec;
u32 bios;
u32 ec;
unsigned long quirks;
};
......@@ -1648,16 +1657,16 @@ static void tpacpi_remove_driver_attributes(struct device_driver *drv)
{ .vendor = (__v), \
.bios = TPID(__id1, __id2), \
.ec = TPACPI_MATCH_ANY, \
.quirks = TPACPI_MATCH_ANY << 16 \
| (__bv1) << 8 | (__bv2) }
.quirks = TPACPI_MATCH_ANY_VERSION << 16 \
| TPVER(__bv1, __bv2) }
#define TPV_Q_X(__v, __bid1, __bid2, __bv1, __bv2, \
__eid, __ev1, __ev2) \
{ .vendor = (__v), \
.bios = TPID(__bid1, __bid2), \
.ec = __eid, \
.quirks = (__ev1) << 24 | (__ev2) << 16 \
| (__bv1) << 8 | (__bv2) }
.quirks = TPVER(__ev1, __ev2) << 16 \
| TPVER(__bv1, __bv2) }
#define TPV_QI0(__id1, __id2, __bv1, __bv2) \
TPV_Q(PCI_VENDOR_ID_IBM, __id1, __id2, __bv1, __bv2)
......@@ -1799,7 +1808,7 @@ static void __init tpacpi_check_outdated_fw(void)
/* note that unknown versions are set to 0x0000 and we use that */
if ((bios_version > thinkpad_id.bios_release) ||
(ec_version > thinkpad_id.ec_release &&
ec_version != TPACPI_MATCH_ANY)) {
ec_version != TPACPI_MATCH_ANY_VERSION)) {
/*
* The changelogs would let us track down the exact
* reason, but it is just too much of a pain to track
......@@ -1929,7 +1938,7 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */
/* 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_CALCULATOR,
TP_ACPI_HOTKEYSCAN_BLUETOOTH,
TP_ACPI_HOTKEYSCAN_KEYBOARD,
......@@ -3450,7 +3459,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
KEY_FAVORITES, /* Favorite app, 0x311 */
KEY_RESERVED, /* Clipping tool */
KEY_RESERVED,
KEY_CALC, /* Calculator (above numpad, P52) */
KEY_BLUETOOTH, /* Bluetooth */
KEY_KEYBOARD /* Keyboard, 0x315 */
},
......@@ -9366,7 +9375,9 @@ static int tpacpi_battery_probe(int battery)
{
int ret = 0;
memset(&battery_info, 0, sizeof(struct tpacpi_battery_driver_data));
memset(&battery_info.batteries[battery], 0,
sizeof(battery_info.batteries[battery]));
/*
* 1) Get the current start threshold
* 2) Check for support
......@@ -9421,7 +9432,8 @@ static int tpacpi_battery_probe(int battery)
static int tpacpi_battery_get_id(const char *battery_name)
{
if (strcmp(battery_name, "BAT0") == 0)
if (strcmp(battery_name, "BAT0") == 0 ||
tp_features.battery_force_primary)
return BAT_PRIMARY;
if (strcmp(battery_name, "BAT1") == 0)
return BAT_SECONDARY;
......@@ -9597,8 +9609,26 @@ static struct acpi_battery_hook battery_hook = {
/* Subdriver init/exit */
static const struct tpacpi_quirk battery_quirk_table[] __initconst = {
/*
* Individual addressing is broken on models that expose the
* primary battery as BAT1.
*/
TPACPI_Q_LNV('J', '7', true), /* B5400 */
TPACPI_Q_LNV('J', 'I', true), /* Thinkpad 11e */
TPACPI_Q_LNV3('R', '0', 'B', true), /* Thinkpad 11e gen 3 */
TPACPI_Q_LNV3('R', '0', 'C', true), /* Thinkpad 13 */
TPACPI_Q_LNV3('R', '0', 'J', true), /* Thinkpad 13 gen 2 */
};
static int __init tpacpi_battery_init(struct ibm_init_struct *ibm)
{
memset(&battery_info, 0, sizeof(battery_info));
tp_features.battery_force_primary = tpacpi_check_quirks(
battery_quirk_table,
ARRAY_SIZE(battery_quirk_table));
battery_hook_register(&battery_hook);
return 0;
}
......@@ -9809,36 +9839,37 @@ static int __init ibm_init(struct ibm_init_struct *iibm)
/* Probing */
static bool __pure __init tpacpi_is_fw_digit(const char c)
static char __init tpacpi_parse_fw_id(const char * const s,
u32 *model, u16 *release)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z');
}
int i;
if (!s || strlen(s) < 8)
goto invalid;
for (i = 0; i < 8; i++)
if (!((s[i] >= '0' && s[i] <= '9') ||
(s[i] >= 'A' && s[i] <= 'Z')))
goto invalid;
static bool __pure __init tpacpi_is_valid_fw_id(const char * const s,
const char t)
{
/*
* Most models: xxyTkkWW (#.##c)
* Ancient 570/600 and -SL lacks (#.##c)
*/
if (s && strlen(s) >= 8 &&
tpacpi_is_fw_digit(s[0]) &&
tpacpi_is_fw_digit(s[1]) &&
s[2] == t &&
(s[3] == 'T' || s[3] == 'N') &&
tpacpi_is_fw_digit(s[4]) &&
tpacpi_is_fw_digit(s[5]))
return true;
if (s[3] == 'T' || s[3] == 'N') {
*model = TPID(s[0], s[1]);
*release = TPVER(s[4], s[5]);
return s[2];
/* New models: xxxyTkkW (#.##c); T550 and some others */
return s && strlen(s) >= 8 &&
tpacpi_is_fw_digit(s[0]) &&
tpacpi_is_fw_digit(s[1]) &&
tpacpi_is_fw_digit(s[2]) &&
s[3] == t &&
(s[4] == 'T' || s[4] == 'N') &&
tpacpi_is_fw_digit(s[5]) &&
tpacpi_is_fw_digit(s[6]);
} else if (s[4] == 'T' || s[4] == 'N') {
*model = TPID3(s[0], s[1], s[2]);
*release = TPVER(s[5], s[6]);
return s[3];
}
invalid:
return '\0';
}
/* returns 0 - probe ok, or < 0 - probe error.
......@@ -9850,6 +9881,7 @@ static int __must_check __init get_thinkpad_model_data(
const struct dmi_device *dev = NULL;
char ec_fw_string[18];
char const *s;
char t;
if (!tp)
return -EINVAL;
......@@ -9869,15 +9901,11 @@ static int __must_check __init get_thinkpad_model_data(
return -ENOMEM;
/* Really ancient ThinkPad 240X will fail this, which is fine */
if (!(tpacpi_is_valid_fw_id(tp->bios_version_str, 'E') ||
tpacpi_is_valid_fw_id(tp->bios_version_str, 'C')))
t = tpacpi_parse_fw_id(tp->bios_version_str,
&tp->bios_model, &tp->bios_release);
if (t != 'E' && t != 'C')
return 0;
tp->bios_model = tp->bios_version_str[0]
| (tp->bios_version_str[1] << 8);
tp->bios_release = (tp->bios_version_str[4] << 8)
| tp->bios_version_str[5];
/*
* ThinkPad T23 or newer, A31 or newer, R50e or newer,
* X32 or newer, all Z series; Some models must have an
......@@ -9896,12 +9924,9 @@ static int __must_check __init get_thinkpad_model_data(
if (!tp->ec_version_str)
return -ENOMEM;
if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) {
tp->ec_model = ec_fw_string[0]
| (ec_fw_string[1] << 8);
tp->ec_release = (ec_fw_string[4] << 8)
| ec_fw_string[5];
} else {
t = tpacpi_parse_fw_id(ec_fw_string,
&tp->ec_model, &tp->ec_release);
if (t != 'H') {
pr_notice("ThinkPad firmware release %s doesn't match the known patterns\n",
ec_fw_string);
pr_notice("please report this to %s\n",
......
......@@ -34,6 +34,7 @@
#define TOSHIBA_ACPI_VERSION "0.24"
#define PROC_INTERFACE_VERSION 1
#include <linux/compiler.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
......@@ -1682,7 +1683,7 @@ static const struct file_operations keys_proc_fops = {
.write = keys_proc_write,
};
static int version_proc_show(struct seq_file *m, void *v)
static int __maybe_unused version_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "driver: %s\n", TOSHIBA_ACPI_VERSION);
seq_printf(m, "proc_interface: %d\n", PROC_INTERFACE_VERSION);
......@@ -1836,6 +1837,7 @@ static ssize_t kbd_backlight_mode_store(struct device *dev,
return ret;
toshiba->kbd_mode = mode;
toshiba_acpi->kbd_mode = mode;
/*
* Some laptop models with the second generation backlit
......@@ -1852,7 +1854,7 @@ static ssize_t kbd_backlight_mode_store(struct device *dev,
* event via genetlink.
*/
if (toshiba->kbd_type == 2 &&
!toshiba_acpi->kbd_event_generated)
!toshiba->kbd_event_generated)
schedule_work(&kbd_bl_work);
}
......@@ -2413,16 +2415,21 @@ static const struct attribute_group toshiba_attr_group = {
static void toshiba_acpi_kbd_bl_work(struct work_struct *work)
{
struct acpi_device *acpi_dev = toshiba_acpi->acpi_dev;
/* Update the sysfs entries */
if (sysfs_update_group(&acpi_dev->dev.kobj,
if (sysfs_update_group(&toshiba_acpi->acpi_dev->dev.kobj,
&toshiba_attr_group))
pr_err("Unable to update sysfs entries\n");
/* Notify LED subsystem about keyboard backlight change */
if (toshiba_acpi->kbd_type == 2 &&
toshiba_acpi->kbd_mode != SCI_KBD_MODE_AUTO)
led_classdev_notify_brightness_hw_changed(&toshiba_acpi->kbd_led,
(toshiba_acpi->kbd_mode == SCI_KBD_MODE_ON) ?
LED_FULL : LED_OFF);
/* Emulate the keyboard backlight event */
acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
dev_name(&acpi_dev->dev),
acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class,
dev_name(&toshiba_acpi->acpi_dev->dev),
0x92, 0);
}
......@@ -3119,9 +3126,12 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
/*
* Only register the LED if KBD illumination is supported
* and the keyboard backlight operation mode is set to FN-Z
* or we detect a second gen keyboard backlight
*/
if (dev->kbd_illum_supported && dev->kbd_mode == SCI_KBD_MODE_FNZ) {
if (dev->kbd_illum_supported &&
(dev->kbd_mode == SCI_KBD_MODE_FNZ || dev->kbd_type == 2)) {
dev->kbd_led.name = "toshiba::kbd_backlight";
dev->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
dev->kbd_led.max_brightness = 1;
dev->kbd_led.brightness_set = toshiba_kbd_backlight_set;
dev->kbd_led.brightness_get = toshiba_kbd_backlight_get;
......@@ -3237,11 +3247,16 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
pr_info("SATA power event received %x\n", event);
break;
case 0x92: /* Keyboard backlight mode changed */
toshiba_acpi->kbd_event_generated = true;
dev->kbd_event_generated = true;
/* Update sysfs entries */
if (sysfs_update_group(&acpi_dev->dev.kobj,
&toshiba_attr_group))
pr_err("Unable to update sysfs entries\n");
/* Notify LED subsystem about keyboard backlight change */
if (dev->kbd_type == 2 && dev->kbd_mode != SCI_KBD_MODE_AUTO)
led_classdev_notify_brightness_hw_changed(&dev->kbd_led,
(dev->kbd_mode == SCI_KBD_MODE_ON) ?
LED_FULL : LED_OFF);
break;
case 0x85: /* Unknown */
case 0x8d: /* Unknown */
......
......@@ -895,7 +895,6 @@ static int wmi_dev_probe(struct device *dev)
struct wmi_driver *wdriver =
container_of(dev->driver, struct wmi_driver, driver);
int ret = 0;
int count;
char *buf;
if (ACPI_FAILURE(wmi_method_enable(wblock, 1)))
......@@ -917,9 +916,8 @@ static int wmi_dev_probe(struct device *dev)
goto probe_failure;
}
count = get_order(wblock->req_buf_size);
wblock->handler_data = (void *)__get_free_pages(GFP_KERNEL,
count);
wblock->handler_data = kmalloc(wblock->req_buf_size,
GFP_KERNEL);
if (!wblock->handler_data) {
ret = -ENOMEM;
goto probe_failure;
......@@ -964,8 +962,7 @@ static int wmi_dev_remove(struct device *dev)
if (wdriver->filter_callback) {
misc_deregister(&wblock->char_dev);
kfree(wblock->char_dev.name);
free_pages((unsigned long)wblock->handler_data,
get_order(wblock->req_buf_size));
kfree(wblock->handler_data);
}
if (wdriver->remove)
......
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