summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/lg-laptop.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/lg-laptop.c')
-rw-r--r--drivers/platform/x86/lg-laptop.c194
1 files changed, 162 insertions, 32 deletions
diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c
index 9c7857842caf..f92e89c75db9 100644
--- a/drivers/platform/x86/lg-laptop.c
+++ b/drivers/platform/x86/lg-laptop.c
@@ -8,6 +8,10 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
@@ -15,6 +19,7 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/string_choices.h>
#include <linux/types.h>
#include <acpi/battery.h>
@@ -31,6 +36,27 @@ MODULE_AUTHOR("Matan Ziv-Av");
MODULE_DESCRIPTION("LG WMI Hotkey Driver");
MODULE_LICENSE("GPL");
+static bool fw_debug;
+module_param(fw_debug, bool, 0);
+MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");
+
+#define LG_ADDRESS_SPACE_ID 0x8F
+
+#define LG_ADDRESS_SPACE_DEBUG_FLAG_ADR 0x00
+#define LG_ADDRESS_SPACE_HD_AUDIO_POWER_ADDR 0x01
+#define LG_ADDRESS_SPACE_FAN_MODE_ADR 0x03
+
+#define LG_ADDRESS_SPACE_DTTM_FLAG_ADR 0x20
+#define LG_ADDRESS_SPACE_CPU_TEMP_ADR 0x21
+#define LG_ADDRESS_SPACE_CPU_TRIP_LOW_ADR 0x22
+#define LG_ADDRESS_SPACE_CPU_TRIP_HIGH_ADR 0x23
+#define LG_ADDRESS_SPACE_MB_TEMP_ADR 0x24
+#define LG_ADDRESS_SPACE_MB_TRIP_LOW_ADR 0x25
+#define LG_ADDRESS_SPACE_MB_TRIP_HIGH_ADR 0x26
+
+#define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8
+#define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8
+
#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"
#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2"
#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"
@@ -52,6 +78,9 @@ MODULE_LICENSE("GPL");
#define WMBB_USB_CHARGE 0x10B
#define WMBB_BATT_LIMIT 0x10C
+#define FAN_MODE_LOWER GENMASK(1, 0)
+#define FAN_MODE_UPPER GENMASK(5, 4)
+
#define PLATFORM_NAME "lg-laptop"
MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);
@@ -182,21 +211,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u
return (union acpi_object *)buffer.pointer;
}
-static void wmi_notify(u32 value, void *context)
+static void wmi_notify(union acpi_object *obj, void *context)
{
- struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
- union acpi_object *obj;
- acpi_status status;
long data = (long)context;
pr_debug("event guid %li\n", data);
- status = wmi_get_event_data(value, &response);
- if (ACPI_FAILURE(status)) {
- pr_err("Bad event status 0x%x\n", status);
- return;
- }
-
- obj = (union acpi_object *)response.pointer;
if (!obj)
return;
@@ -218,7 +237,6 @@ static void wmi_notify(u32 value, void *context)
pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type,
obj->integer.value);
- kfree(response.pointer);
}
static void wmi_input_setup(void)
@@ -262,29 +280,19 @@ static ssize_t fan_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buffer, size_t count)
{
- bool value;
+ unsigned long value;
union acpi_object *r;
- u32 m;
int ret;
- ret = kstrtobool(buffer, &value);
+ ret = kstrtoul(buffer, 10, &value);
if (ret)
return ret;
+ if (value >= 3)
+ return -EINVAL;
- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);
- if (!r)
- return -EIO;
-
- if (r->type != ACPI_TYPE_INTEGER) {
- kfree(r);
- return -EIO;
- }
-
- m = r->integer.value;
- kfree(r);
- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, (m & 0xffffff0f) | (value << 4));
- kfree(r);
- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, (m & 0xfffffff0) | value);
+ r = lg_wmab(dev, WM_FAN_MODE, WM_SET,
+ FIELD_PREP(FAN_MODE_LOWER, value) |
+ FIELD_PREP(FAN_MODE_UPPER, value));
kfree(r);
return count;
@@ -293,7 +301,7 @@ static ssize_t fan_mode_store(struct device *dev,
static ssize_t fan_mode_show(struct device *dev,
struct device_attribute *attr, char *buffer)
{
- unsigned int status;
+ unsigned int mode;
union acpi_object *r;
r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);
@@ -305,10 +313,10 @@ static ssize_t fan_mode_show(struct device *dev,
return -EIO;
}
- status = r->integer.value & 0x01;
+ mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value);
kfree(r);
- return sysfs_emit(buffer, "%d\n", status);
+ return sysfs_emit(buffer, "%d\n", mode);
}
static ssize_t usb_charge_store(struct device *dev,
@@ -646,6 +654,116 @@ static struct platform_driver pf_driver = {
}
};
+static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address,
+ size_t size, u64 value)
+{
+ u8 byte;
+
+ /* Ignore any debug messages */
+ if (address >= LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR &&
+ address <= LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR)
+ return AE_OK;
+
+ if (size != sizeof(byte))
+ return AE_BAD_PARAMETER;
+
+ byte = value & 0xFF;
+
+ switch (address) {
+ case LG_ADDRESS_SPACE_HD_AUDIO_POWER_ADDR:
+ /*
+ * The HD audio power field is not affected by the DTTM flag,
+ * so we have to manually check fw_debug.
+ */
+ if (fw_debug)
+ dev_dbg(dev, "HD audio power %s\n", str_enabled_disabled(byte));
+
+ return AE_OK;
+ case LG_ADDRESS_SPACE_FAN_MODE_ADR:
+ /*
+ * The fan mode field is not affected by the DTTM flag, so we
+ * have to manually check fw_debug.
+ */
+ if (fw_debug)
+ dev_dbg(dev, "Fan mode set to mode %u\n", byte);
+
+ return AE_OK;
+ case LG_ADDRESS_SPACE_CPU_TEMP_ADR:
+ dev_dbg(dev, "CPU temperature is %u °C\n", byte);
+ return AE_OK;
+ case LG_ADDRESS_SPACE_CPU_TRIP_LOW_ADR:
+ dev_dbg(dev, "CPU lower trip point set to %u °C\n", byte);
+ return AE_OK;
+ case LG_ADDRESS_SPACE_CPU_TRIP_HIGH_ADR:
+ dev_dbg(dev, "CPU higher trip point set to %u °C\n", byte);
+ return AE_OK;
+ case LG_ADDRESS_SPACE_MB_TEMP_ADR:
+ dev_dbg(dev, "Motherboard temperature is %u °C\n", byte);
+ return AE_OK;
+ case LG_ADDRESS_SPACE_MB_TRIP_LOW_ADR:
+ dev_dbg(dev, "Motherboard lower trip point set to %u °C\n", byte);
+ return AE_OK;
+ case LG_ADDRESS_SPACE_MB_TRIP_HIGH_ADR:
+ dev_dbg(dev, "Motherboard higher trip point set to %u °C\n", byte);
+ return AE_OK;
+ default:
+ dev_notice_ratelimited(dev, "Ignoring write to unknown opregion address %llu\n",
+ address);
+ return AE_OK;
+ }
+}
+
+static acpi_status lg_laptop_address_space_read(struct device *dev, acpi_physical_address address,
+ size_t size, u64 *value)
+{
+ if (size != 1)
+ return AE_BAD_PARAMETER;
+
+ switch (address) {
+ case LG_ADDRESS_SPACE_DEBUG_FLAG_ADR:
+ /* Debug messages are already printed using the standard ACPI Debug object */
+ *value = 0x00;
+ return AE_OK;
+ case LG_ADDRESS_SPACE_DTTM_FLAG_ADR:
+ *value = fw_debug;
+ return AE_OK;
+ default:
+ dev_notice_ratelimited(dev, "Attempt to read unknown opregion address %llu\n",
+ address);
+ return AE_BAD_PARAMETER;
+ }
+}
+
+static acpi_status lg_laptop_address_space_handler(u32 function, acpi_physical_address address,
+ u32 bits, u64 *value, void *handler_context,
+ void *region_context)
+{
+ struct device *dev = handler_context;
+ size_t size;
+
+ if (bits % BITS_PER_BYTE)
+ return AE_BAD_PARAMETER;
+
+ size = bits / BITS_PER_BYTE;
+
+ switch (function) {
+ case ACPI_READ:
+ return lg_laptop_address_space_read(dev, address, size, value);
+ case ACPI_WRITE:
+ return lg_laptop_address_space_write(dev, address, size, *value);
+ default:
+ return AE_BAD_PARAMETER;
+ }
+}
+
+static void lg_laptop_remove_address_space_handler(void *data)
+{
+ struct acpi_device *device = data;
+
+ acpi_remove_address_space_handler(device->handle, LG_ADDRESS_SPACE_ID,
+ &lg_laptop_address_space_handler);
+}
+
static int acpi_add(struct acpi_device *device)
{
struct platform_device_info pdev_info = {
@@ -653,6 +771,7 @@ static int acpi_add(struct acpi_device *device)
.name = PLATFORM_NAME,
.id = PLATFORM_DEVID_NONE,
};
+ acpi_status status;
int ret;
const char *product;
int year = 2017;
@@ -660,6 +779,17 @@ static int acpi_add(struct acpi_device *device)
if (pf_device)
return 0;
+ status = acpi_install_address_space_handler(device->handle, LG_ADDRESS_SPACE_ID,
+ &lg_laptop_address_space_handler,
+ NULL, &device->dev);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ ret = devm_add_action_or_reset(&device->dev, lg_laptop_remove_address_space_handler,
+ device);
+ if (ret < 0)
+ return ret;
+
ret = platform_driver_register(&pf_driver);
if (ret)
return ret;