diff options
Diffstat (limited to 'drivers/platform')
217 files changed, 14387 insertions, 3743 deletions
diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index 10f905d7d6bf..e32e01b2a9bd 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -90,4 +90,17 @@ config EC_LENOVO_THINKPAD_T14S Say M or Y here to include this support. +config EC_QCOM_HAMOA + tristate "Embedded Controller driver for Qualcomm Hamoa/Glymur reference devices" + depends on ARCH_QCOM || COMPILE_TEST + depends on I2C + depends on THERMAL || THERMAL=n + help + Say M or Y here to enable the Embedded Controller driver for Qualcomm + Snapdragon-based Hamoa/Glymur reference devices. The driver handles fan + control, temperature sensors, access to EC state changes and supports + reporting suspend entry/exit to the EC. + + This driver currently supports Hamoa/Purwa/Glymur reference devices. + endif # ARM64_PLATFORM_DEVICES diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile index 60c131cff6a1..7681be4a46e9 100644 --- a/drivers/platform/arm64/Makefile +++ b/drivers/platform/arm64/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o +obj-$(CONFIG_EC_QCOM_HAMOA) += qcom-hamoa-ec.o diff --git a/drivers/platform/arm64/acer-aspire1-ec.c b/drivers/platform/arm64/acer-aspire1-ec.c index 438532a047e6..08d0b155a197 100644 --- a/drivers/platform/arm64/acer-aspire1-ec.c +++ b/drivers/platform/arm64/acer-aspire1-ec.c @@ -532,7 +532,7 @@ static int aspire_ec_resume(struct device *dev) } static const struct i2c_device_id aspire_ec_id[] = { - { "aspire1-ec", }, + { .name = "aspire1-ec" }, { } }; MODULE_DEVICE_TABLE(i2c, aspire_ec_id); diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c index a83ddc20b5a3..80a8ba8b8dda 100644 --- a/drivers/platform/arm64/huawei-gaokun-ec.c +++ b/drivers/platform/arm64/huawei-gaokun-ec.c @@ -795,7 +795,7 @@ static int gaokun_ec_probe(struct i2c_client *client) } static const struct i2c_device_id gaokun_ec_id[] = { - { "gaokun-ec", }, + { .name = "gaokun-ec" }, { } }; MODULE_DEVICE_TABLE(i2c, gaokun_ec_id); diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c index 5590302a5694..e7acb66b77f2 100644 --- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c +++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c @@ -637,8 +637,8 @@ static const struct of_device_id t14s_ec_of_match[] = { MODULE_DEVICE_TABLE(of, t14s_ec_of_match); static const struct i2c_device_id t14s_ec_i2c_id_table[] = { - { "thinkpad-t14s-ec", }, - {} + { .name = "thinkpad-t14s-ec" }, + { } }; MODULE_DEVICE_TABLE(i2c, t14s_ec_i2c_id_table); diff --git a/drivers/platform/arm64/lenovo-yoga-c630.c b/drivers/platform/arm64/lenovo-yoga-c630.c index 75060c842b24..a8600a977fbc 100644 --- a/drivers/platform/arm64/lenovo-yoga-c630.c +++ b/drivers/platform/arm64/lenovo-yoga-c630.c @@ -238,8 +238,8 @@ static const struct of_device_id yoga_c630_ec_of_match[] = { MODULE_DEVICE_TABLE(of, yoga_c630_ec_of_match); static const struct i2c_device_id yoga_c630_ec_i2c_id_table[] = { - { "yoga-c630-ec", }, - {} + { .name = "yoga-c630-ec" }, + { } }; MODULE_DEVICE_TABLE(i2c, yoga_c630_ec_i2c_id_table); diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c new file mode 100644 index 000000000000..4d2ad042a7f8 --- /dev/null +++ b/drivers/platform/arm64/qcom-hamoa-ec.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Maya Matuszczyk <maccraft123mc@gmail.com> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#define EC_SCI_EVT_READ_CMD 0x05 +#define EC_FW_VERSION_CMD 0x0e +#define EC_MODERN_STANDBY_CMD 0x23 +#define EC_FAN_DBG_CONTROL_CMD 0x30 +#define EC_SCI_EVT_CONTROL_CMD 0x35 +#define EC_THERMAL_CAP_CMD 0x42 + +#define EC_FW_VERSION_RESP_LEN 4 +#define EC_THERMAL_CAP_RESP_LEN 3 +#define EC_FAN_DEBUG_CMD_LEN 6 +#define EC_FAN_SPEED_DATA_SIZE 4 + +#define EC_MODERN_STANDBY_ENTER 0x01 +#define EC_MODERN_STANDBY_EXIT 0x00 + +#define EC_FAN_DEBUG_MODE_OFF 0 +#define EC_FAN_DEBUG_MODE_ON BIT(0) +#define EC_FAN_ON BIT(1) +#define EC_FAN_DEBUG_TYPE_PWM BIT(2) +#define EC_MAX_FAN_CNT 2 +#define EC_FAN_NAME_SIZE 20 +#define EC_FAN_MAX_PWM 255 + +enum qcom_ec_sci_events { + EC_FAN1_STATUS_CHANGE_EVT = 0x30, + EC_FAN2_STATUS_CHANGE_EVT, + EC_FAN1_SPEED_CHANGE_EVT, + EC_FAN2_SPEED_CHANGE_EVT, + EC_NEW_LUT_SET_EVT, + EC_FAN_PROFILE_SWITCH_EVT, + EC_THERMISTOR_1_THRESHOLD_CROSS_EVT, + EC_THERMISTOR_2_THRESHOLD_CROSS_EVT, + EC_THERMISTOR_3_THRESHOLD_CROSS_EVT, + /* Reserved: 0x39 - 0x3c/0x3f */ + EC_RECOVERED_FROM_RESET_EVT = 0x3d, +}; + +struct qcom_ec_version { + u8 main_version; + u8 sub_version; + u8 test_version; +}; + +struct qcom_ec_thermal_cap { +#define EC_THERMAL_FAN_CNT(x) (FIELD_GET(GENMASK(1, 0), (x))) +#define EC_THERMAL_FAN_TYPE(x) (FIELD_GET(GENMASK(4, 2), (x))) +#define EC_THERMAL_THERMISTOR_MASK(x) (FIELD_GET(GENMASK(7, 0), (x))) + u8 fan_cnt; + u8 fan_type; + u8 thermistor_mask; +}; + +struct qcom_ec_cooling_dev { + struct thermal_cooling_device *cdev; + struct device *parent_dev; + u8 fan_id; + u8 state; +}; + +struct qcom_ec { + struct qcom_ec_cooling_dev *ec_cdev; + struct qcom_ec_thermal_cap thermal_cap; + struct qcom_ec_version version; + struct i2c_client *client; +}; + +static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(ec->client, cmd, resp_len, resp); + if (ret < 0) + return ret; + else if (ret == 0 || ret == 0xff) + return -EOPNOTSUPP; + else if (ret != resp_len) + return -EIO; + + if (resp[0] != resp_len - 1) + return -EINVAL; + + return 0; +} + +/* + * EC Device Firmware Version: + * + * Read Response: + * ---------------------------------------------------------------------- + * | Offset | Name | Description | + * ---------------------------------------------------------------------- + * | 0x00 | Byte count | Number of bytes in response | + * | | | (excluding byte count) | + * ---------------------------------------------------------------------- + * | 0x01 | Test-version | Test-version of EC firmware | + * ---------------------------------------------------------------------- + * | 0x02 | Sub-version | Sub-version of EC firmware | + * ---------------------------------------------------------------------- + * | 0x03 | Main-version | Main-version of EC firmware | + * ---------------------------------------------------------------------- + * + */ +static int qcom_ec_read_fw_version(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct qcom_ec *ec = i2c_get_clientdata(client); + struct qcom_ec_version *version = &ec->version; + u8 resp[EC_FW_VERSION_RESP_LEN]; + int ret; + + ret = qcom_ec_read(ec, EC_FW_VERSION_CMD, EC_FW_VERSION_RESP_LEN, resp); + if (ret < 0) + return ret; + + version->main_version = resp[3]; + version->sub_version = resp[2]; + version->test_version = resp[1]; + + dev_dbg(dev, "EC Version %d.%d.%d\n", + version->main_version, version->sub_version, version->test_version); + + return 0; +} + +/* + * EC Device Thermal Capabilities: + * + * Read Response: + * ------------------------------------------------------------------------------ + * | Offset | Name | Description | + * ------------------------------------------------------------------------------ + * | 0x00 | Byte count | Number of bytes in response | + * | | | (excluding byte count) | + * ------------------------------------------------------------------------------ + * | 0x02 (LSB) | EC Thermal | Bit 0-1: Number of fans | + * | 0x03 | Capabilities | Bit 2-4: Type of fan | + * | | | Bit 5-6: Reserved | + * | | | Bit 7: Data Valid/Invalid | + * | | | (Valid - 1, Invalid - 0) | + * | | | Bit 8-15: Thermistor 0 - 7 presence | + * | | | (1 present, 0 absent) | + * ------------------------------------------------------------------------------ + * + */ +static int qcom_ec_thermal_capabilities(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct qcom_ec *ec = i2c_get_clientdata(client); + struct qcom_ec_thermal_cap *cap = &ec->thermal_cap; + u8 resp[EC_THERMAL_CAP_RESP_LEN]; + int ret; + + ret = qcom_ec_read(ec, EC_THERMAL_CAP_CMD, EC_THERMAL_CAP_RESP_LEN, resp); + if (ret < 0) + return ret; + + cap->fan_cnt = min(EC_MAX_FAN_CNT, EC_THERMAL_FAN_CNT(resp[1])); + cap->fan_type = EC_THERMAL_FAN_TYPE(resp[1]); + cap->thermistor_mask = EC_THERMAL_THERMISTOR_MASK(resp[2]); + + dev_dbg(dev, "Fan count: %d Fan Type: %d Thermistor Mask: %x\n", + cap->fan_cnt, cap->fan_type, cap->thermistor_mask); + + return 0; +} + +static irqreturn_t qcom_ec_irq(int irq, void *data) +{ + struct qcom_ec *ec = data; + struct device *dev = &ec->client->dev; + int val; + + val = i2c_smbus_read_byte_data(ec->client, EC_SCI_EVT_READ_CMD); + if (val < 0) { + dev_err_ratelimited(dev, "Failed to read EC SCI Event: %d\n", val); + return IRQ_HANDLED; + } + + switch (val) { + case EC_FAN1_STATUS_CHANGE_EVT: + dev_dbg_ratelimited(dev, "Fan1 status changed\n"); + break; + case EC_FAN2_STATUS_CHANGE_EVT: + dev_dbg_ratelimited(dev, "Fan2 status changed\n"); + break; + case EC_FAN1_SPEED_CHANGE_EVT: + dev_dbg_ratelimited(dev, "Fan1 speed crossed low/high trip point\n"); + break; + case EC_FAN2_SPEED_CHANGE_EVT: + dev_dbg_ratelimited(dev, "Fan2 speed crossed low/high trip point\n"); + break; + case EC_NEW_LUT_SET_EVT: + dev_dbg_ratelimited(dev, "New LUT set\n"); + break; + case EC_FAN_PROFILE_SWITCH_EVT: + dev_dbg_ratelimited(dev, "FAN Profile switched\n"); + break; + case EC_THERMISTOR_1_THRESHOLD_CROSS_EVT: + dev_dbg_ratelimited(dev, "Thermistor 1 threshold crossed\n"); + break; + case EC_THERMISTOR_2_THRESHOLD_CROSS_EVT: + dev_dbg_ratelimited(dev, "Thermistor 2 threshold crossed\n"); + break; + case EC_THERMISTOR_3_THRESHOLD_CROSS_EVT: + dev_dbg_ratelimited(dev, "Thermistor 3 threshold crossed\n"); + break; + case EC_RECOVERED_FROM_RESET_EVT: + dev_dbg_ratelimited(dev, "EC recovered from reset\n"); + break; + default: + dev_notice_ratelimited(dev, "Unknown EC event: %d\n", val); + break; + } + + return IRQ_HANDLED; +} + +static int qcom_ec_sci_evt_control(struct device *dev, bool enable) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(client, EC_SCI_EVT_CONTROL_CMD, enable ? 1 : 0); +} + +static int qcom_ec_fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) +{ + *state = EC_FAN_MAX_PWM; + + return 0; +} + +static int qcom_ec_fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) +{ + struct qcom_ec_cooling_dev *ec_cdev = cdev->devdata; + + *state = ec_cdev->state; + + return 0; +} + +/* + * Fan Debug control command: + * + * Command Payload: + * -------------------------------------------------------------------------------------- + * | Offset | Name | Description | + * -------------------------------------------------------------------------------------- + * | 0x00 | Command | Fan control command | + * -------------------------------------------------------------------------------------- + * | 0x01 | Fan ID | 0x1 : Fan 1 | + * | | | 0x2 : Fan 2 | + * -------------------------------------------------------------------------------------- + * | 0x02 | Byte count = 4| Size of data to set fan speed | + * -------------------------------------------------------------------------------------- + * | 0x03 | Mode | Bit 0: Debug Mode On/Off (0 - OFF, 1 - ON ) | + * | | | Bit 1: Fan On/Off (0 - Off, 1 - ON) | + * | | | Bit 2: Debug Type (0 - RPM, 1 - PWM) | + * -------------------------------------------------------------------------------------- + * | 0x04 (LSB) | Speed in RPM | RPM value, if mode selected is RPM | + * | 0x05 | | | + * -------------------------------------------------------------------------------------- + * | 0x06 | Speed in PWM | PWM value, if mode selected is PWM (0 - 255) | + * ______________________________________________________________________________________ + * + */ +static int qcom_ec_fan_debug_mode_off(struct qcom_ec_cooling_dev *ec_cdev) +{ + struct device *dev = ec_cdev->parent_dev; + struct i2c_client *client = to_i2c_client(dev); + u8 request[6] = { ec_cdev->fan_id, EC_FAN_SPEED_DATA_SIZE, + EC_FAN_DEBUG_MODE_OFF, 0, 0, 0 }; + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, EC_FAN_DBG_CONTROL_CMD, + sizeof(request), request); + if (ret) { + dev_err(dev, "Failed to turn off fan%d debug mode: %d\n", + ec_cdev->fan_id, ret); + } + + return ret; +} + +static int qcom_ec_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +{ + struct qcom_ec_cooling_dev *ec_cdev = cdev->devdata; + struct device *dev = ec_cdev->parent_dev; + struct i2c_client *client = to_i2c_client(dev); + u8 request[6] = { ec_cdev->fan_id, EC_FAN_SPEED_DATA_SIZE, + EC_FAN_DEBUG_MODE_ON | EC_FAN_ON | EC_FAN_DEBUG_TYPE_PWM, + 0, 0, state }; + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, EC_FAN_DBG_CONTROL_CMD, + sizeof(request), request); + if (ret) { + dev_err(dev, "Failed to set fan pwm: %d\n", ret); + return ret; + } + + ec_cdev->state = state; + + return 0; +} + +static const struct thermal_cooling_device_ops qcom_ec_thermal_ops = { + .get_max_state = qcom_ec_fan_get_max_state, + .get_cur_state = qcom_ec_fan_get_cur_state, + .set_cur_state = qcom_ec_fan_set_cur_state, +}; + +static int qcom_ec_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, + EC_MODERN_STANDBY_EXIT); +} + +static int qcom_ec_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, + EC_MODERN_STANDBY_ENTER); +} + +static int qcom_ec_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct qcom_ec *ec; + unsigned int i; + int ret; + + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); + if (!ec) + return -ENOMEM; + + ec->client = client; + + ret = devm_request_threaded_irq(dev, client->irq, NULL, qcom_ec_irq, + IRQF_ONESHOT, "qcom_ec", ec); + if (ret < 0) + return ret; + + i2c_set_clientdata(client, ec); + + ret = qcom_ec_read_fw_version(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read EC firmware version\n"); + + ret = qcom_ec_sci_evt_control(dev, true); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable SCI events\n"); + + ret = qcom_ec_thermal_capabilities(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read thermal capabilities\n"); + + if (ec->thermal_cap.fan_cnt == 0) { + dev_warn(dev, FW_BUG "Failed to get fan count, firmware update required\n"); + return 0; + } + + ec->ec_cdev = devm_kcalloc(dev, ec->thermal_cap.fan_cnt, sizeof(*ec->ec_cdev), GFP_KERNEL); + if (!ec->ec_cdev) + return -ENOMEM; + + for (i = 0; i < ec->thermal_cap.fan_cnt; i++) { + struct qcom_ec_cooling_dev *ec_cdev = &ec->ec_cdev[i]; + char name[EC_FAN_NAME_SIZE]; + + scnprintf(name, sizeof(name), "qcom_ec_fan_%u", i); + ec_cdev->fan_id = i + 1; + ec_cdev->parent_dev = dev; + + ec_cdev->cdev = devm_thermal_of_child_cooling_device_register(dev, NULL, name, ec_cdev, + &qcom_ec_thermal_ops); + if (IS_ERR(ec_cdev->cdev)) { + return dev_err_probe(dev, PTR_ERR(ec_cdev->cdev), + "Failed to register fan%d cooling device\n", i); + } + } + + return 0; +} + +static void qcom_ec_remove(struct i2c_client *client) +{ + struct qcom_ec *ec = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int ret; + + ret = qcom_ec_sci_evt_control(dev, false); + if (ret < 0) + dev_err(dev, "Failed to disable SCI events: %d\n", ret); + + for (int i = 0; i < ec->thermal_cap.fan_cnt; i++) { + struct qcom_ec_cooling_dev *ec_cdev = &ec->ec_cdev[i]; + + qcom_ec_fan_debug_mode_off(ec_cdev); + } +} + +static const struct of_device_id qcom_ec_of_match[] = { + { .compatible = "qcom,hamoa-crd-ec" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_ec_of_match); + +static const struct i2c_device_id qcom_ec_i2c_id_table[] = { + { "qcom-hamoa-ec", }, + {} +}; +MODULE_DEVICE_TABLE(i2c, qcom_ec_i2c_id_table); + +static DEFINE_SIMPLE_DEV_PM_OPS(qcom_ec_pm_ops, + qcom_ec_suspend, + qcom_ec_resume); + +static struct i2c_driver qcom_ec_i2c_driver = { + .driver = { + .name = "qcom-hamoa-ec", + .of_match_table = qcom_ec_of_match, + .pm = &qcom_ec_pm_ops, + }, + .probe = qcom_ec_probe, + .remove = qcom_ec_remove, + .id_table = qcom_ec_i2c_id_table, +}; +module_i2c_driver(qcom_ec_i2c_driver); + +MODULE_DESCRIPTION("QCOM Hamoa Embedded Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 2281d6dacc9b..ca2e8442026e 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -6,6 +6,7 @@ menuconfig CHROME_PLATFORMS bool "Platform support for Chrome hardware" depends on X86 || ARM || ARM64 || COMPILE_TEST + depends on !CPU_BIG_ENDIAN || COMPILE_TEST help Say Y here to get to see options for platform support for various Chromebooks and Chromeboxes. This option alone does @@ -75,7 +76,6 @@ config CHROMEOS_OF_HW_PROBER config CROS_EC tristate "ChromeOS Embedded Controller" select CROS_EC_PROTO - depends on X86 || ARM || ARM64 || COMPILE_TEST help If you say Y here you get support for the ChromeOS Embedded Controller (EC) providing keyboard, battery and power services. diff --git a/drivers/platform/chrome/chromeos_of_hw_prober.c b/drivers/platform/chrome/chromeos_of_hw_prober.c index f3cd612e5584..54d8941617e2 100644 --- a/drivers/platform/chrome/chromeos_of_hw_prober.c +++ b/drivers/platform/chrome/chromeos_of_hw_prober.c @@ -70,10 +70,8 @@ static const struct chromeos_i2c_probe_data chromeos_i2c_probe_hana_trackpad = { /* * ELAN trackpad needs 2 ms for H/W init and 100 ms for F/W init. * Synaptics trackpad needs 100 ms. - * However, the regulator is set to "always-on", presumably to - * avoid this delay. The ELAN driver is also missing delays. */ - .post_power_on_delay_ms = 0, + .post_power_on_delay_ms = 110, }, }; @@ -100,7 +98,7 @@ static const struct hw_prober_entry hw_prober_platforms[] = { }, { .compatible = "google,spherion", .prober = chromeos_i2c_component_prober, - .data = &chromeos_i2c_probe_hana_trackpad, + .data = &chromeos_i2c_probe_dumb_trackpad, }, { .compatible = "google,squirtle", .prober = chromeos_i2c_component_prober, diff --git a/drivers/platform/chrome/chromeos_privacy_screen.c b/drivers/platform/chrome/chromeos_privacy_screen.c index bb74ddf9af4a..407b04207de2 100644 --- a/drivers/platform/chrome/chromeos_privacy_screen.c +++ b/drivers/platform/chrome/chromeos_privacy_screen.c @@ -12,6 +12,7 @@ */ #include <linux/acpi.h> +#include <linux/platform_device.h> #include <drm/drm_privacy_screen_driver.h> /* @@ -32,11 +33,10 @@ chromeos_privacy_screen_get_hw_state(struct drm_privacy_screen *drm_privacy_screen) { union acpi_object *obj; - acpi_handle handle; struct device *privacy_screen = drm_privacy_screen_get_drvdata(drm_privacy_screen); + acpi_handle handle = ACPI_HANDLE(privacy_screen); - handle = acpi_device_handle(to_acpi_device(privacy_screen)); obj = acpi_evaluate_dsm(handle, &chromeos_privacy_screen_dsm_guid, PRIV_SCRN_DSM_REVID, PRIV_SCRN_DSM_FN_GET_STATUS, NULL); @@ -65,11 +65,9 @@ chromeos_privacy_screen_set_sw_state(struct drm_privacy_screen enum drm_privacy_screen_status state) { union acpi_object *obj = NULL; - acpi_handle handle; struct device *privacy_screen = drm_privacy_screen_get_drvdata(drm_privacy_screen); - - handle = acpi_device_handle(to_acpi_device(privacy_screen)); + acpi_handle handle = ACPI_HANDLE(privacy_screen); if (state == PRIVACY_SCREEN_DISABLED) { obj = acpi_evaluate_dsm(handle, @@ -104,30 +102,31 @@ static const struct drm_privacy_screen_ops chromeos_privacy_screen_ops = { .set_sw_state = chromeos_privacy_screen_set_sw_state, }; -static int chromeos_privacy_screen_add(struct acpi_device *adev) +static int chromeos_privacy_screen_probe(struct platform_device *pdev) { + if (!ACPI_COMPANION(&pdev->dev)) + return -ENODEV; + struct drm_privacy_screen *drm_privacy_screen = - drm_privacy_screen_register(&adev->dev, + drm_privacy_screen_register(&pdev->dev, &chromeos_privacy_screen_ops, - &adev->dev); + &pdev->dev); if (IS_ERR(drm_privacy_screen)) { - dev_err(&adev->dev, "Error registering privacy-screen\n"); + dev_err(&pdev->dev, "Error registering privacy-screen\n"); return PTR_ERR(drm_privacy_screen); } - adev->driver_data = drm_privacy_screen; - dev_info(&adev->dev, "registered privacy-screen '%s'\n", + platform_set_drvdata(pdev, drm_privacy_screen); + dev_info(&pdev->dev, "registered privacy-screen '%s'\n", dev_name(&drm_privacy_screen->dev)); return 0; } -static void chromeos_privacy_screen_remove(struct acpi_device *adev) +static void chromeos_privacy_screen_remove(struct platform_device *pdev) { - struct drm_privacy_screen *drm_privacy_screen = acpi_driver_data(adev); - - drm_privacy_screen_unregister(drm_privacy_screen); + drm_privacy_screen_unregister(platform_get_drvdata(pdev)); } static const struct acpi_device_id chromeos_privacy_screen_device_ids[] = { @@ -136,17 +135,16 @@ static const struct acpi_device_id chromeos_privacy_screen_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, chromeos_privacy_screen_device_ids); -static struct acpi_driver chromeos_privacy_screen_driver = { - .name = "chromeos_privacy_screen_driver", - .class = "ChromeOS", - .ids = chromeos_privacy_screen_device_ids, - .ops = { - .add = chromeos_privacy_screen_add, - .remove = chromeos_privacy_screen_remove, +static struct platform_driver chromeos_privacy_screen_driver = { + .probe = chromeos_privacy_screen_probe, + .remove = chromeos_privacy_screen_remove, + .driver = { + .name = "chromeos_privacy_screen_driver", + .acpi_match_table = chromeos_privacy_screen_device_ids, }, }; -module_acpi_driver(chromeos_privacy_screen_driver); +module_platform_driver(chromeos_privacy_screen_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("ChromeOS ACPI Privacy Screen driver"); MODULE_AUTHOR("Rajat Jain <rajatja@google.com>"); diff --git a/drivers/platform/chrome/chromeos_tbmc.c b/drivers/platform/chrome/chromeos_tbmc.c index d1cf8f3463ce..fd756761a481 100644 --- a/drivers/platform/chrome/chromeos_tbmc.c +++ b/drivers/platform/chrome/chromeos_tbmc.c @@ -16,6 +16,7 @@ #include <linux/input.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/platform_device.h> #include <linux/printk.h> #define DRV_NAME "chromeos_tbmc" @@ -40,20 +41,20 @@ static int chromeos_tbmc_query_switch(struct acpi_device *adev, static __maybe_unused int chromeos_tbmc_resume(struct device *dev) { - struct acpi_device *adev = to_acpi_device(dev); - - return chromeos_tbmc_query_switch(adev, adev->driver_data); + return chromeos_tbmc_query_switch(ACPI_COMPANION(dev), dev_get_drvdata(dev)); } -static void chromeos_tbmc_notify(struct acpi_device *adev, u32 event) +static void chromeos_tbmc_notify(acpi_handle handle, u32 event, void *data) { - acpi_pm_wakeup_event(&adev->dev); + struct device *dev = data; + + acpi_pm_wakeup_event(dev); switch (event) { case 0x80: - chromeos_tbmc_query_switch(adev, adev->driver_data); + chromeos_tbmc_query_switch(ACPI_COMPANION(dev), dev_get_drvdata(dev)); break; default: - dev_err(&adev->dev, "Unexpected event: 0x%08X\n", event); + dev_err(dev, "Unexpected event: 0x%08X\n", event); } } @@ -64,12 +65,17 @@ static int chromeos_tbmc_open(struct input_dev *idev) return chromeos_tbmc_query_switch(adev, idev); } -static int chromeos_tbmc_add(struct acpi_device *adev) +static int chromeos_tbmc_probe(struct platform_device *pdev) { struct input_dev *idev; - struct device *dev = &adev->dev; + struct device *dev = &pdev->dev; + struct acpi_device *adev; int ret; + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + idev = devm_input_allocate_device(dev); if (!idev) return -ENOMEM; @@ -83,7 +89,7 @@ static int chromeos_tbmc_add(struct acpi_device *adev) idev->open = chromeos_tbmc_open; input_set_drvdata(idev, adev); - adev->driver_data = idev; + platform_set_drvdata(pdev, idev); input_set_capability(idev, EV_SW, SW_TABLET_MODE); ret = input_register_device(idev); @@ -92,9 +98,25 @@ static int chromeos_tbmc_add(struct acpi_device *adev) return ret; } device_init_wakeup(dev, true); + + ret = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, + chromeos_tbmc_notify, dev); + if (ret) { + dev_err(dev, "cannot install ACPI notify handler\n"); + device_init_wakeup(dev, false); + return ret; + } + return 0; } +static void chromeos_tbmc_remove(struct platform_device *pdev) +{ + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, chromeos_tbmc_notify); + device_init_wakeup(&pdev->dev, false); +} + static const struct acpi_device_id chromeos_tbmc_acpi_device_ids[] = { { ACPI_DRV_NAME, 0 }, { } @@ -104,18 +126,17 @@ MODULE_DEVICE_TABLE(acpi, chromeos_tbmc_acpi_device_ids); static SIMPLE_DEV_PM_OPS(chromeos_tbmc_pm_ops, NULL, chromeos_tbmc_resume); -static struct acpi_driver chromeos_tbmc_driver = { - .name = DRV_NAME, - .class = DRV_NAME, - .ids = chromeos_tbmc_acpi_device_ids, - .ops = { - .add = chromeos_tbmc_add, - .notify = chromeos_tbmc_notify, +static struct platform_driver chromeos_tbmc_driver = { + .probe = chromeos_tbmc_probe, + .remove = chromeos_tbmc_remove, + .driver = { + .name = DRV_NAME, + .acpi_match_table = chromeos_tbmc_acpi_device_ids, + .pm = &chromeos_tbmc_pm_ops, }, - .drv.pm = &chromeos_tbmc_pm_ops, }; -module_acpi_driver(chromeos_tbmc_driver); +module_platform_driver(chromeos_tbmc_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("ChromeOS ACPI tablet switch driver"); diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c index 1da79e3d215b..5d0f5a9187b1 100644 --- a/drivers/platform/chrome/cros_ec.c +++ b/drivers/platform/chrome/cros_ec.c @@ -249,11 +249,8 @@ int cros_ec_register(struct cros_ec_device *ec_dev) cros_ec_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "chromeos-ec", ec_dev); - if (err) { - dev_err(dev, "Failed to request IRQ %d: %d\n", - ec_dev->irq, err); + if (err) goto exit; - } } /* Register a platform device for the main EC instance */ diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c index 002be3352100..18763947aaa7 100644 --- a/drivers/platform/chrome/cros_ec_chardev.c +++ b/drivers/platform/chrome/cros_ec_chardev.c @@ -13,8 +13,8 @@ #include <linux/init.h> #include <linux/device.h> #include <linux/fs.h> +#include <linux/kref.h> #include <linux/miscdevice.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/notifier.h> #include <linux/platform_data/cros_ec_chardev.h> @@ -22,6 +22,7 @@ #include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/poll.h> +#include <linux/rwsem.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/uaccess.h> @@ -31,14 +32,44 @@ /* Arbitrary bounded size for the event queue */ #define CROS_MAX_EVENT_LEN PAGE_SIZE -struct chardev_priv { +/* + * Platform device driver data. + */ +struct chardev_pdata { + struct miscdevice misc; + struct kref kref; + struct rw_semaphore ec_dev_sem; struct cros_ec_device *ec_dev; + u16 cmd_offset; + struct blocking_notifier_head subscribers; + struct notifier_block relay; +}; + +static void chardev_pdata_release(struct kref *kref) +{ + struct chardev_pdata *pdata = container_of(kref, typeof(*pdata), kref); + + kfree(pdata); +} + +static int cros_ec_chardev_relay_event(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_notify) +{ + struct chardev_pdata *pdata = container_of(nb, typeof(*pdata), relay); + + blocking_notifier_call_chain(&pdata->subscribers, queued_during_suspend, + _notify); + return NOTIFY_OK; +} + +struct chardev_priv { struct notifier_block notifier; wait_queue_head_t wait_event; unsigned long event_mask; struct list_head events; size_t event_len; - u16 cmd_offset; + struct chardev_pdata *pdata; }; struct ec_event { @@ -61,10 +92,10 @@ static int ec_get_version(struct chardev_priv *priv, char *str, int maxlen) if (!msg) return -ENOMEM; - msg->command = EC_CMD_GET_VERSION + priv->cmd_offset; + msg->command = EC_CMD_GET_VERSION + priv->pdata->cmd_offset; msg->insize = sizeof(*resp); - ret = cros_ec_cmd_xfer_status(priv->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(priv->pdata->ec_dev, msg); if (ret < 0) { snprintf(str, maxlen, "Unknown EC version, returned error: %d\n", @@ -92,10 +123,18 @@ static int cros_ec_chardev_mkbp_event(struct notifier_block *nb, { struct chardev_priv *priv = container_of(nb, struct chardev_priv, notifier); - struct cros_ec_device *ec_dev = priv->ec_dev; + struct cros_ec_device *ec_dev; struct ec_event *event; - unsigned long event_bit = 1 << ec_dev->event_data.event_type; - int total_size = sizeof(*event) + ec_dev->event_size; + unsigned long event_bit; + int total_size; + + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return NOTIFY_DONE; + ec_dev = priv->pdata->ec_dev; + + event_bit = 1 << ec_dev->event_data.event_type; + total_size = sizeof(*event) + ec_dev->event_size; if (!(event_bit & priv->event_mask) || (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) @@ -157,8 +196,7 @@ out: static int cros_ec_chardev_open(struct inode *inode, struct file *filp) { struct miscdevice *mdev = filp->private_data; - struct cros_ec_dev *ec = dev_get_drvdata(mdev->parent); - struct cros_ec_device *ec_dev = ec->ec_dev; + struct chardev_pdata *pdata = container_of(mdev, typeof(*pdata), misc); struct chardev_priv *priv; int ret; @@ -166,18 +204,20 @@ static int cros_ec_chardev_open(struct inode *inode, struct file *filp) if (!priv) return -ENOMEM; - priv->ec_dev = ec_dev; - priv->cmd_offset = ec->cmd_offset; + priv->pdata = pdata; + kref_get(&pdata->kref); filp->private_data = priv; INIT_LIST_HEAD(&priv->events); init_waitqueue_head(&priv->wait_event); nonseekable_open(inode, filp); priv->notifier.notifier_call = cros_ec_chardev_mkbp_event; - ret = blocking_notifier_chain_register(&ec_dev->event_notifier, + ret = blocking_notifier_chain_register(&pdata->subscribers, &priv->notifier); if (ret) { - dev_err(ec_dev->dev, "failed to register event notifier\n"); + dev_err(pdata->ec_dev->dev, + "failed to register event notifier\n"); + kref_put(&priv->pdata->kref, chardev_pdata_release); kfree(priv); } @@ -188,6 +228,10 @@ static __poll_t cros_ec_chardev_poll(struct file *filp, poll_table *wait) { struct chardev_priv *priv = filp->private_data; + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return EPOLLHUP; + poll_wait(filp, &priv->wait_event, wait); if (list_empty(&priv->events)) @@ -205,6 +249,10 @@ static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer, size_t count; int ret; + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return -ENODEV; + if (priv->event_mask) { /* queued MKBP event */ struct ec_event *event; @@ -251,11 +299,11 @@ static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer, static int cros_ec_chardev_release(struct inode *inode, struct file *filp) { struct chardev_priv *priv = filp->private_data; - struct cros_ec_device *ec_dev = priv->ec_dev; struct ec_event *event, *e; - blocking_notifier_chain_unregister(&ec_dev->event_notifier, + blocking_notifier_chain_unregister(&priv->pdata->subscribers, &priv->notifier); + kref_put(&priv->pdata->kref, chardev_pdata_release); list_for_each_entry_safe(event, e, &priv->events, node) { list_del(&event->node); @@ -298,8 +346,8 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev_priv *priv, void __user *a goto exit; } - s_cmd->command += priv->cmd_offset; - ret = cros_ec_cmd_xfer(priv->ec_dev, s_cmd); + s_cmd->command += priv->pdata->cmd_offset; + ret = cros_ec_cmd_xfer(priv->pdata->ec_dev, s_cmd); /* Only copy data to userland if data was received. */ if (ret < 0) goto exit; @@ -313,7 +361,7 @@ exit: static long cros_ec_chardev_ioctl_readmem(struct chardev_priv *priv, void __user *arg) { - struct cros_ec_device *ec_dev = priv->ec_dev; + struct cros_ec_device *ec_dev = priv->pdata->ec_dev; struct cros_ec_readmem s_mem = { }; long num; @@ -343,6 +391,10 @@ static long cros_ec_chardev_ioctl(struct file *filp, unsigned int cmd, { struct chardev_priv *priv = filp->private_data; + guard(rwsem_read)(&priv->pdata->ec_dev_sem); + if (!priv->pdata->ec_dev) + return -ENODEV; + if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) return -ENOTTY; @@ -374,33 +426,66 @@ static int cros_ec_chardev_probe(struct platform_device *pdev) { struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent); struct cros_ec_platform *ec_platform = dev_get_platdata(ec->dev); - struct miscdevice *misc; + struct chardev_pdata *pdata; + int ret; - /* Create a char device: we want to create it anew */ - misc = devm_kzalloc(&pdev->dev, sizeof(*misc), GFP_KERNEL); - if (!misc) + pdata = kzalloc_obj(*pdata); + if (!pdata) return -ENOMEM; - misc->minor = MISC_DYNAMIC_MINOR; - misc->fops = &chardev_fops; - misc->name = ec_platform->ec_name; - misc->parent = pdev->dev.parent; + platform_set_drvdata(pdev, pdata); + kref_init(&pdata->kref); + init_rwsem(&pdata->ec_dev_sem); + pdata->ec_dev = ec->ec_dev; + pdata->cmd_offset = ec->cmd_offset; + BLOCKING_INIT_NOTIFIER_HEAD(&pdata->subscribers); + pdata->relay.notifier_call = cros_ec_chardev_relay_event; + ret = blocking_notifier_chain_register(&pdata->ec_dev->event_notifier, + &pdata->relay); + if (ret) { + dev_err(&pdev->dev, "failed to register event notifier\n"); + goto err_put_pdata; + } + + pdata->misc.minor = MISC_DYNAMIC_MINOR; + pdata->misc.fops = &chardev_fops; + pdata->misc.name = ec_platform->ec_name; + pdata->misc.parent = pdev->dev.parent; - dev_set_drvdata(&pdev->dev, misc); + ret = misc_register(&pdata->misc); + if (ret) { + dev_err(&pdev->dev, "failed to register misc device\n"); + goto err_unregister_notifier; + } - return misc_register(misc); + return 0; +err_unregister_notifier: + blocking_notifier_chain_unregister(&pdata->ec_dev->event_notifier, + &pdata->relay); +err_put_pdata: + kref_put(&pdata->kref, chardev_pdata_release); + return ret; } static void cros_ec_chardev_remove(struct platform_device *pdev) { - struct miscdevice *misc = dev_get_drvdata(&pdev->dev); + struct chardev_pdata *pdata = platform_get_drvdata(pdev); + struct cros_ec_device *ec_dev = pdata->ec_dev; - misc_deregister(misc); + /* stop new fops from being created */ + misc_deregister(&pdata->misc); + /* stop existing fops from running */ + scoped_guard(rwsem_write, &pdata->ec_dev_sem) + pdata->ec_dev = NULL; + + blocking_notifier_chain_unregister(&ec_dev->event_notifier, + &pdata->relay); + kref_put(&pdata->kref, chardev_pdata_release); } static const struct platform_device_id cros_ec_chardev_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_ec_chardev_id); diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index d10f9561990c..c3e40ad7e03c 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -7,7 +7,6 @@ #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/fs.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/platform_data/cros_ec_commands.h> @@ -513,7 +512,7 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) ret = blocking_notifier_chain_register(&ec->ec_dev->panic_notifier, &debug_info->notifier_panic); if (ret) - goto remove_debugfs; + goto cleanup_console_log; ec->debug_info = debug_info; @@ -521,6 +520,8 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) return 0; +cleanup_console_log: + cros_ec_cleanup_console_log(debug_info); remove_debugfs: debugfs_remove_recursive(debug_info->dir); return ret; @@ -530,6 +531,8 @@ static void cros_ec_debugfs_remove(struct platform_device *pd) { struct cros_ec_dev *ec = dev_get_drvdata(pd->dev.parent); + blocking_notifier_chain_unregister(&ec->ec_dev->panic_notifier, + &ec->debug_info->notifier_panic); debugfs_remove_recursive(ec->debug_info->dir); cros_ec_cleanup_console_log(ec->debug_info); } @@ -558,8 +561,8 @@ static SIMPLE_DEV_PM_OPS(cros_ec_debugfs_pm_ops, cros_ec_debugfs_suspend, cros_ec_debugfs_resume); static const struct platform_device_id cros_ec_debugfs_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_ec_debugfs_id); diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrome/cros_ec_i2c.c index def1144a077e..2f46be4a2756 100644 --- a/drivers/platform/chrome/cros_ec_i2c.c +++ b/drivers/platform/chrome/cros_ec_i2c.c @@ -348,7 +348,7 @@ MODULE_DEVICE_TABLE(of, cros_ec_i2c_of_match); #endif static const struct i2c_device_id cros_ec_i2c_id[] = { - { "cros-ec-i2c" }, + { .name = "cros-ec-i2c" }, { } }; MODULE_DEVICE_TABLE(i2c, cros_ec_i2c_id); diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 2d1aa6edda1a..96dde19b15fa 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -9,7 +9,6 @@ #include <linux/fs.h> #include <linux/kobject.h> #include <linux/kstrtox.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> @@ -461,6 +460,8 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_SEQ; param->seq.num = num; + msg->outsize = offsetof(typeof(*param), seq) + sizeof(param->seq); + msg->insize = 0; ret = lb_throttle(); if (ret) goto exit; @@ -503,9 +504,14 @@ static ssize_t program_store(struct device *dev, struct device_attribute *attr, return -EINVAL; } } else { + /* + * Bound the payload strictly by the maximum value the structural + * size field can natively support. + */ extra_bytes = offsetof(typeof(*param), set_program_ex) + sizeof(param->set_program_ex); - max_size = ec->ec_dev->max_request - extra_bytes; + max_size = min_t(size_t, ec->ec_dev->max_request - extra_bytes, + type_max(typeof(param->set_program_ex.size))); } msg = alloc_lightbar_cmd_msg(ec); @@ -516,6 +522,7 @@ static ssize_t program_store(struct device *dev, struct device_attribute *attr, if (ret) goto exit; param = (struct ec_params_lightbar *)msg->data; + msg->insize = 0; if (lb_version < 3) { dev_info(dev, "Copying %zu byte program to EC", count); @@ -681,8 +688,8 @@ static SIMPLE_DEV_PM_OPS(cros_ec_lightbar_pm_ops, cros_ec_lightbar_suspend, cros_ec_lightbar_resume); static const struct platform_device_id cros_ec_lightbar_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_ec_lightbar_id); diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 1d8d9168ec1a..724d1313f6b2 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -947,6 +947,27 @@ u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev) EXPORT_SYMBOL(cros_ec_get_host_event); /** + * cros_ec_read_features() - Read EC features + * + * @ec: EC device. + * + * Return: >= 0 on success, negative error number on failure. + */ +int cros_ec_read_features(struct cros_ec_dev *ec) +{ + int ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_GET_FEATURES + ec->cmd_offset, + NULL, 0, &ec->features, sizeof(ec->features)); + + if (ret < 0) { + dev_warn(ec->dev, "cannot get EC features: %d\n", ret); + memset(&ec->features, 0, sizeof(ec->features)); + } + + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_read_features); + +/** * cros_ec_check_features() - Test for the presence of EC features * * @ec: EC device, does not have to be connected directly to the AP, @@ -960,17 +981,10 @@ EXPORT_SYMBOL(cros_ec_get_host_event); bool cros_ec_check_features(struct cros_ec_dev *ec, int feature) { struct ec_response_get_features *features = &ec->features; - int ret; if (features->flags[0] == -1U && features->flags[1] == -1U) { /* features bitmap not read yet */ - ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_GET_FEATURES + ec->cmd_offset, - NULL, 0, features, sizeof(*features)); - if (ret < 0) { - dev_warn(ec->dev, "cannot get EC features: %d\n", ret); - memset(features, 0, sizeof(*features)); - } - + cros_ec_read_features(ec); dev_dbg(ec->dev, "EC features %08x %08x\n", features->flags[0], features->flags[1]); } diff --git a/drivers/platform/chrome/cros_ec_sensorhub.c b/drivers/platform/chrome/cros_ec_sensorhub.c index 9bad8f72680e..a8395ab0a12a 100644 --- a/drivers/platform/chrome/cros_ec_sensorhub.c +++ b/drivers/platform/chrome/cros_ec_sensorhub.c @@ -9,7 +9,6 @@ #include <linux/init.h> #include <linux/device.h> #include <linux/delay.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> @@ -122,8 +121,12 @@ static int cros_ec_sensorhub_register(struct device *dev, sensor_type[sensorhub->resp->info.type]++; } - if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) + if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) { ec->has_kb_wake_angle = true; + if (ec->group && sysfs_update_group(&ec->class_dev.kobj, + ec->group)) + dev_warn(dev, "Unable to update sysfs"); + } if (cros_ec_check_features(ec, EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) { @@ -264,8 +267,8 @@ static SIMPLE_DEV_PM_OPS(cros_ec_sensorhub_pm_ops, cros_ec_sensorhub_resume); static const struct platform_device_id cros_ec_sensorhub_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_ec_sensorhub_id); diff --git a/drivers/platform/chrome/cros_ec_sensorhub_ring.c b/drivers/platform/chrome/cros_ec_sensorhub_ring.c index a10579144c34..d92b60213720 100644 --- a/drivers/platform/chrome/cros_ec_sensorhub_ring.c +++ b/drivers/platform/chrome/cros_ec_sensorhub_ring.c @@ -475,6 +475,21 @@ cros_ec_sensor_ring_process_event(struct cros_ec_sensorhub *sensorhub, fifo_timestamp, *current_timestamp, now); + + /* + * A standalone timestamp event typically has a sensor_num of + * 0xff. Return early here to prevent it from hitting the + * bounds check below and spamming the logs. + */ + return false; + } + + /* Skip event if sensor_num from EC is out of bounds. */ + if (in->sensor_num >= sensorhub->sensor_num) { + dev_warn_ratelimited(sensorhub->dev, + "Invalid sensor number %u from EC\n", + in->sensor_num); + return false; } if (in->flags & MOTIONSENSE_SENSOR_FLAG_ODR) { @@ -502,10 +517,6 @@ cros_ec_sensor_ring_process_event(struct cros_ec_sensorhub *sensorhub, return true; } - if (in->flags & MOTIONSENSE_SENSOR_FLAG_TIMESTAMP) - /* If we just have a timestamp, skip this entry. */ - return false; - /* Regular sample */ out->sensor_id = in->sensor_num; trace_cros_ec_sensorhub_data(in->sensor_num, @@ -825,8 +836,15 @@ static void cros_ec_sensorhub_ring_handler(struct cros_ec_sensorhub *sensorhub) sensorhub->msg->outsize = 1; sensorhub->msg->insize = fifo_info_length; - if (cros_ec_cmd_xfer_status(ec->ec_dev, sensorhub->msg) < 0) + ret = cros_ec_cmd_xfer_status(ec->ec_dev, sensorhub->msg); + if (ret < 0) goto error; + if (ret != fifo_info_length) { + dev_warn_ratelimited(sensorhub->dev, + "Mismatch read length: size %d - expected %d\n", + ret, fifo_info_length); + goto error; + } memcpy(fifo_info, &sensorhub->resp->fifo_info, fifo_info_length); diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index f22e9523da3e..fad593df3b58 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -8,7 +8,6 @@ #include <linux/device.h> #include <linux/fs.h> #include <linux/kobject.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> @@ -405,7 +404,8 @@ static int cros_ec_sysfs_probe(struct platform_device *pd) struct device *dev = &pd->dev; int ret; - ret = sysfs_create_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group); + ec_dev->group = &cros_ec_attr_group; + ret = sysfs_create_group(&ec_dev->class_dev.kobj, ec_dev->group); if (ret < 0) dev_err(dev, "failed to create attributes. err=%d\n", ret); @@ -416,12 +416,12 @@ static void cros_ec_sysfs_remove(struct platform_device *pd) { struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); - sysfs_remove_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group); + sysfs_remove_group(&ec_dev->class_dev.kobj, ec_dev->group); } static const struct platform_device_id cros_ec_sysfs_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_ec_sysfs_id); diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index c0806c562bb9..50a68819ceb7 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -1119,6 +1119,12 @@ static void cros_typec_register_partner_pdos(struct cros_typec_data *typec, if (!resp->source_cap_count && !resp->sink_cap_count) return; + if (resp->source_cap_count > PDO_MAX_OBJECTS || + resp->sink_cap_count > PDO_MAX_OBJECTS) { + dev_warn(typec->dev, "Invalid PDO count from EC, port: %d\n", port_num); + return; + } + port->partner_pd = typec_partner_usb_power_delivery_register(port->partner, &desc); if (IS_ERR(port->partner_pd)) { dev_warn(typec->dev, "Failed to register partner PD device, port: %d\n", port_num); diff --git a/drivers/platform/chrome/cros_ec_vbc.c b/drivers/platform/chrome/cros_ec_vbc.c index 5ee8adaa6564..c21e1f85f2fb 100644 --- a/drivers/platform/chrome/cros_ec_vbc.c +++ b/drivers/platform/chrome/cros_ec_vbc.c @@ -6,7 +6,6 @@ #include <linux/of.h> #include <linux/platform_device.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> @@ -135,8 +134,8 @@ static void cros_ec_vbc_remove(struct platform_device *pd) } static const struct platform_device_id cros_ec_vbc_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_ec_vbc_id); diff --git a/drivers/platform/chrome/cros_hps_i2c.c b/drivers/platform/chrome/cros_hps_i2c.c index ac6498c593e3..3b9485627831 100644 --- a/drivers/platform/chrome/cros_hps_i2c.c +++ b/drivers/platform/chrome/cros_hps_i2c.c @@ -131,7 +131,7 @@ static int hps_resume(struct device *dev) static DEFINE_RUNTIME_DEV_PM_OPS(hps_pm_ops, hps_suspend, hps_resume, NULL); static const struct i2c_device_id hps_i2c_id[] = { - { "cros-hps" }, + { .name = "cros-hps" }, { } }; MODULE_DEVICE_TABLE(i2c, hps_i2c_id); diff --git a/drivers/platform/chrome/cros_kbd_led_backlight.c b/drivers/platform/chrome/cros_kbd_led_backlight.c index f4c2282129f5..94888db1c00e 100644 --- a/drivers/platform/chrome/cros_kbd_led_backlight.c +++ b/drivers/platform/chrome/cros_kbd_led_backlight.c @@ -10,7 +10,6 @@ #include <linux/kernel.h> #include <linux/leds.h> #include <linux/mfd/core.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_data/cros_ec_commands.h> @@ -32,12 +31,11 @@ struct keyboard_led { * @brightness_set_blocking: Set LED brightness level. It can block the * caller for the time required for accessing a * LED device register - * @max_brightness: Maximum brightness. * * See struct led_classdev in include/linux/leds.h for more details. */ struct keyboard_led_drvdata { - int (*init)(struct platform_device *pdev); + int (*init)(struct platform_device *pdev, struct keyboard_led *keyboard_led); enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); @@ -45,12 +43,8 @@ struct keyboard_led_drvdata { enum led_brightness brightness); int (*brightness_set_blocking)(struct led_classdev *led_cdev, enum led_brightness brightness); - - enum led_brightness max_brightness; }; -#define KEYBOARD_BACKLIGHT_MAX 100 - #ifdef CONFIG_ACPI /* Keyboard LED ACPI Device must be defined in firmware */ @@ -94,7 +88,8 @@ keyboard_led_get_brightness_acpi(struct led_classdev *cdev) return brightness; } -static int keyboard_led_init_acpi(struct platform_device *pdev) +static int keyboard_led_init_acpi(struct platform_device *pdev, + struct keyboard_led *keyboard_led) { acpi_handle handle; acpi_status status; @@ -116,17 +111,15 @@ static const struct keyboard_led_drvdata keyboard_led_drvdata_acpi = { .init = keyboard_led_init_acpi, .brightness_set = keyboard_led_set_brightness_acpi, .brightness_get = keyboard_led_get_brightness_acpi, - .max_brightness = KEYBOARD_BACKLIGHT_MAX, }; #endif /* CONFIG_ACPI */ -#if IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) -static int keyboard_led_init_ec_pwm_mfd(struct platform_device *pdev) +static int keyboard_led_init_ec_pwm_mfd(struct platform_device *pdev, + struct keyboard_led *keyboard_led) { struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); struct cros_ec_device *cros_ec = ec_dev->ec_dev; - struct keyboard_led *keyboard_led = platform_get_drvdata(pdev); keyboard_led->ec = cros_ec; @@ -175,15 +168,8 @@ static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd = { .init = keyboard_led_init_ec_pwm_mfd, .brightness_set_blocking = keyboard_led_set_brightness_ec_pwm, .brightness_get = keyboard_led_get_brightness_ec_pwm, - .max_brightness = KEYBOARD_BACKLIGHT_MAX, }; -#else /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */ - -static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd = {}; - -#endif /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */ - static int keyboard_led_is_mfd_device(struct platform_device *pdev) { return IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) && mfd_get_cell(pdev); @@ -205,17 +191,16 @@ static int keyboard_led_probe(struct platform_device *pdev) keyboard_led = devm_kzalloc(&pdev->dev, sizeof(*keyboard_led), GFP_KERNEL); if (!keyboard_led) return -ENOMEM; - platform_set_drvdata(pdev, keyboard_led); if (drvdata->init) { - err = drvdata->init(pdev); + err = drvdata->init(pdev, keyboard_led); if (err) return err; } keyboard_led->cdev.name = "chromeos::kbd_backlight"; keyboard_led->cdev.flags |= LED_CORE_SUSPENDRESUME | LED_REJECT_NAME_CONFLICT; - keyboard_led->cdev.max_brightness = drvdata->max_brightness; + keyboard_led->cdev.max_brightness = 100; keyboard_led->cdev.brightness_set = drvdata->brightness_set; keyboard_led->cdev.brightness_set_blocking = drvdata->brightness_set_blocking; keyboard_led->cdev.brightness_get = drvdata->brightness_get; @@ -235,8 +220,8 @@ MODULE_DEVICE_TABLE(acpi, keyboard_led_acpi_match); #endif static const struct platform_device_id keyboard_led_id[] = { - { "cros-keyboard-leds", 0 }, - {} + { .name = "cros-keyboard-leds" }, + { } }; MODULE_DEVICE_TABLE(platform, keyboard_led_id); diff --git a/drivers/platform/chrome/cros_typec_altmode.c b/drivers/platform/chrome/cros_typec_altmode.c index 557340b53af0..66c546bf89b5 100644 --- a/drivers/platform/chrome/cros_typec_altmode.c +++ b/drivers/platform/chrome/cros_typec_altmode.c @@ -359,6 +359,7 @@ cros_typec_register_thunderbolt(struct cros_typec_port *port, } INIT_WORK(&adata->work, cros_typec_altmode_work); + mutex_init(&adata->lock); adata->alt = alt; adata->port = port; adata->ap_mode_entry = true; diff --git a/drivers/platform/chrome/cros_usbpd_logger.c b/drivers/platform/chrome/cros_usbpd_logger.c index 7ce75e2e039e..0eb714d679c8 100644 --- a/drivers/platform/chrome/cros_usbpd_logger.c +++ b/drivers/platform/chrome/cros_usbpd_logger.c @@ -5,9 +5,9 @@ * Copyright 2018 Google LLC. */ +#include <linux/devm-helpers.h> #include <linux/ktime.h> #include <linux/math64.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> @@ -199,6 +199,7 @@ static int cros_usbpd_logger_probe(struct platform_device *pd) struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); struct device *dev = &pd->dev; struct logger_data *logger; + int ret; logger = devm_kzalloc(dev, sizeof(*logger), GFP_KERNEL); if (!logger) @@ -210,25 +211,20 @@ static int cros_usbpd_logger_probe(struct platform_device *pd) platform_set_drvdata(pd, logger); /* Retrieve PD event logs periodically */ - INIT_DELAYED_WORK(&logger->log_work, cros_usbpd_log_check); - logger->log_workqueue = create_singlethread_workqueue("cros_usbpd_log"); + logger->log_workqueue = devm_alloc_ordered_workqueue(dev, "cros_usbpd_log", 0); if (!logger->log_workqueue) return -ENOMEM; + ret = devm_delayed_work_autocancel(dev, &logger->log_work, cros_usbpd_log_check); + if (ret) + return ret; + queue_delayed_work(logger->log_workqueue, &logger->log_work, CROS_USBPD_LOG_UPDATE_DELAY); return 0; } -static void cros_usbpd_logger_remove(struct platform_device *pd) -{ - struct logger_data *logger = platform_get_drvdata(pd); - - cancel_delayed_work_sync(&logger->log_work); - destroy_workqueue(logger->log_workqueue); -} - static int __maybe_unused cros_usbpd_logger_resume(struct device *dev) { struct logger_data *logger = dev_get_drvdata(dev); @@ -252,8 +248,8 @@ static SIMPLE_DEV_PM_OPS(cros_usbpd_logger_pm_ops, cros_usbpd_logger_suspend, cros_usbpd_logger_resume); static const struct platform_device_id cros_usbpd_logger_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_usbpd_logger_id); @@ -263,7 +259,6 @@ static struct platform_driver cros_usbpd_logger_driver = { .pm = &cros_usbpd_logger_pm_ops, }, .probe = cros_usbpd_logger_probe, - .remove = cros_usbpd_logger_remove, .id_table = cros_usbpd_logger_id, }; diff --git a/drivers/platform/chrome/cros_usbpd_notify.c b/drivers/platform/chrome/cros_usbpd_notify.c index c90174360004..d842c05f4db0 100644 --- a/drivers/platform/chrome/cros_usbpd_notify.c +++ b/drivers/platform/chrome/cros_usbpd_notify.c @@ -7,7 +7,6 @@ #include <linux/acpi.h> #include <linux/fwnode.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_data/cros_usbpd_notify.h> @@ -233,8 +232,8 @@ static void cros_usbpd_notify_remove_plat(struct platform_device *pdev) } static const struct platform_device_id cros_usbpd_notify_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, cros_usbpd_notify_id); diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c index 9f978e531e1f..9d4521889d40 100644 --- a/drivers/platform/chrome/wilco_ec/core.c +++ b/drivers/platform/chrome/wilco_ec/core.c @@ -10,7 +10,6 @@ #include <linux/acpi.h> #include <linux/device.h> #include <linux/ioport.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/wilco-ec.h> #include <linux/platform_device.h> @@ -152,8 +151,8 @@ static const struct acpi_device_id wilco_ec_acpi_device_ids[] = { MODULE_DEVICE_TABLE(acpi, wilco_ec_acpi_device_ids); static const struct platform_device_id wilco_ec_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, wilco_ec_id); diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c index 0617292b5cd7..32fb11939b9b 100644 --- a/drivers/platform/chrome/wilco_ec/debugfs.c +++ b/drivers/platform/chrome/wilco_ec/debugfs.c @@ -10,7 +10,6 @@ #include <linux/ctype.h> #include <linux/debugfs.h> #include <linux/fs.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/wilco-ec.h> #include <linux/platform_device.h> @@ -266,8 +265,8 @@ static void wilco_ec_debugfs_remove(struct platform_device *pdev) } static const struct platform_device_id wilco_ec_debugfs_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, wilco_ec_debugfs_id); diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c index 743cd4839bff..1b5cb89839e0 100644 --- a/drivers/platform/chrome/wilco_ec/event.c +++ b/drivers/platform/chrome/wilco_ec/event.c @@ -38,6 +38,7 @@ #include <linux/io.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/platform_device.h> #include <linux/poll.h> #include <linux/spinlock.h> #include <linux/uaccess.h> @@ -198,7 +199,7 @@ struct event_device_data { /** * enqueue_events() - Place EC events in queue to be read by userspace. - * @adev: Device the events came from. + * @dev: Device the events came from. * @buf: Buffer of event data. * @length: Length of event data buffer. * @@ -209,9 +210,9 @@ struct event_device_data { * * Return: 0 on success or negative error code on failure. */ -static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) +static int enqueue_events(struct device *dev, const u8 *buf, u32 length) { - struct event_device_data *dev_data = adev->driver_data; + struct event_device_data *dev_data = dev_get_drvdata(dev); struct ec_event *event, *queue_event, *old_event; size_t num_words, event_size; u32 offset = 0; @@ -222,14 +223,14 @@ static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) num_words = ec_event_num_words(event); event_size = ec_event_size(event); if (num_words > EC_ACPI_MAX_EVENT_WORDS) { - dev_err(&adev->dev, "Too many event words: %zu > %d\n", + dev_err(dev, "Too many event words: %zu > %d\n", num_words, EC_ACPI_MAX_EVENT_WORDS); return -EOVERFLOW; } /* Ensure event does not overflow the available buffer */ if ((offset + event_size) > length) { - dev_err(&adev->dev, "Event exceeds buffer: %zu > %d\n", + dev_err(dev, "Event exceeds buffer: %zu > %d\n", offset + event_size, length); return -EOVERFLOW; } @@ -253,19 +254,22 @@ static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) /** * event_device_notify() - Callback when EC generates an event over ACPI. - * @adev: The device that the event is coming from. + * @handle: ACPI handle of the device that the event is coming from. * @value: Value passed to Notify() in ACPI. + * @data: Notify handler data. * * This function will read the events from the device and enqueue them. */ -static void event_device_notify(struct acpi_device *adev, u32 value) +static void event_device_notify(acpi_handle handle, u32 value, void *data) { struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct device *dev = data; + struct acpi_device *adev = ACPI_COMPANION(dev); union acpi_object *obj; acpi_status status; if (value != EC_ACPI_NOTIFY_EVENT) { - dev_err(&adev->dev, "Invalid event: 0x%08x\n", value); + dev_err(dev, "Invalid event: 0x%08x\n", value); return; } @@ -273,31 +277,31 @@ static void event_device_notify(struct acpi_device *adev, u32 value) status = acpi_evaluate_object(adev->handle, EC_ACPI_GET_EVENT, NULL, &event_buffer); if (ACPI_FAILURE(status)) { - dev_err(&adev->dev, "Error executing ACPI method %s()\n", + dev_err(dev, "Error executing ACPI method %s()\n", EC_ACPI_GET_EVENT); return; } obj = (union acpi_object *)event_buffer.pointer; if (!obj) { - dev_err(&adev->dev, "Nothing returned from %s()\n", + dev_err(dev, "Nothing returned from %s()\n", EC_ACPI_GET_EVENT); return; } if (obj->type != ACPI_TYPE_BUFFER) { - dev_err(&adev->dev, "Invalid object returned from %s()\n", + dev_err(dev, "Invalid object returned from %s()\n", EC_ACPI_GET_EVENT); kfree(obj); return; } if (obj->buffer.length < sizeof(struct ec_event)) { - dev_err(&adev->dev, "Invalid buffer length %d from %s()\n", + dev_err(dev, "Invalid buffer length %d from %s()\n", obj->buffer.length, EC_ACPI_GET_EVENT); kfree(obj); return; } - enqueue_events(adev, obj->buffer.pointer, obj->buffer.length); + enqueue_events(dev, obj->buffer.pointer, obj->buffer.length); kfree(obj); } @@ -432,8 +436,8 @@ static void hangup_device(struct event_device_data *dev_data) } /** - * event_device_add() - Callback when creating a new device. - * @adev: ACPI device that we will be receiving events from. + * event_device_probe() - Callback when creating a new device. + * @pdev: Platform device that we will be receiving events from. * * This finds a free minor number for the device, allocates and initializes * some device data, and creates a new device and char dev node. @@ -445,15 +449,20 @@ static void hangup_device(struct event_device_data *dev_data) * * Return: 0 on success, negative error code on failure. */ -static int event_device_add(struct acpi_device *adev) +static int event_device_probe(struct platform_device *pdev) { struct event_device_data *dev_data; + struct acpi_device *adev; int error, minor; + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return -ENODEV; + minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL); if (minor < 0) { error = minor; - dev_err(&adev->dev, "Failed to find minor number: %d\n", error); + dev_err(&pdev->dev, "Failed to find minor number: %d\n", error); return error; } @@ -464,7 +473,7 @@ static int event_device_add(struct acpi_device *adev) } /* Initialize the device data. */ - adev->driver_data = dev_data; + platform_set_drvdata(pdev, dev_data); dev_data->events = event_queue_new(queue_size); if (!dev_data->events) { kfree(dev_data); @@ -489,8 +498,16 @@ static int event_device_add(struct acpi_device *adev) if (error) goto free_dev_data; + /* Install an ACPI notify handler. */ + error = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, + event_device_notify, &pdev->dev); + if (error) + goto free_cdev; + return 0; +free_cdev: + cdev_device_del(&dev_data->cdev, &dev_data->dev); free_dev_data: hangup_device(dev_data); free_minor: @@ -498,10 +515,12 @@ free_minor: return error; } -static void event_device_remove(struct acpi_device *adev) +static void event_device_remove(struct platform_device *pdev) { - struct event_device_data *dev_data = adev->driver_data; + struct event_device_data *dev_data = platform_get_drvdata(pdev); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, event_device_notify); cdev_device_del(&dev_data->cdev, &dev_data->dev); ida_free(&event_ida, MINOR(dev_data->dev.devt)); hangup_device(dev_data); @@ -513,14 +532,12 @@ static const struct acpi_device_id event_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, event_acpi_ids); -static struct acpi_driver event_driver = { - .name = DRV_NAME, - .class = DRV_NAME, - .ids = event_acpi_ids, - .ops = { - .add = event_device_add, - .notify = event_device_notify, - .remove = event_device_remove, +static struct platform_driver event_driver = { + .probe = event_device_probe, + .remove = event_device_remove, + .driver = { + .name = DRV_NAME, + .acpi_match_table = event_acpi_ids, }, }; @@ -543,7 +560,7 @@ static int __init event_module_init(void) } event_major = MAJOR(dev_num); - ret = acpi_bus_register_driver(&event_driver); + ret = platform_driver_register(&event_driver); if (ret < 0) { pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); goto unregister_region; @@ -561,7 +578,7 @@ destroy_class: static void __exit event_module_exit(void) { - acpi_bus_unregister_driver(&event_driver); + platform_driver_unregister(&event_driver); unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV); class_unregister(&event_class); ida_destroy(&event_ida); diff --git a/drivers/platform/chrome/wilco_ec/telemetry.c b/drivers/platform/chrome/wilco_ec/telemetry.c index cadb68fa0a40..45be318b05f6 100644 --- a/drivers/platform/chrome/wilco_ec/telemetry.c +++ b/drivers/platform/chrome/wilco_ec/telemetry.c @@ -30,7 +30,6 @@ #include <linux/cdev.h> #include <linux/device.h> #include <linux/fs.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/wilco-ec.h> #include <linux/platform_device.h> @@ -410,8 +409,8 @@ static void telem_device_remove(struct platform_device *pdev) } static const struct platform_device_id telem_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; MODULE_DEVICE_TABLE(platform, telem_id); diff --git a/drivers/platform/cznic/turris-omnia-mcu-gpio.c b/drivers/platform/cznic/turris-omnia-mcu-gpio.c index 7f0ada4fa606..4e430d6c3fc4 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-gpio.c +++ b/drivers/platform/cznic/turris-omnia-mcu-gpio.c @@ -893,7 +893,7 @@ static bool omnia_irq_read_pending_old(struct omnia_mcu *mcu, if (status & OMNIA_STS_BUTTON_PRESSED) { mcu->button_pressed_emul = true; - mod_delayed_work(system_wq, &mcu->button_release_emul_work, + mod_delayed_work(system_percpu_wq, &mcu->button_release_emul_work, msecs_to_jiffies(FRONT_BUTTON_RELEASE_DELAY_MS)); } else if (mcu->button_pressed_emul) { status |= OMNIA_STS_BUTTON_PRESSED; diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 75c86f5688c9..fa241ca8feb0 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -48,7 +48,6 @@ */ #include <linux/module.h> -#include <linux/mod_devicetable.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/spinlock.h> diff --git a/drivers/platform/loongarch/loongson-laptop.c b/drivers/platform/loongarch/loongson-laptop.c index 61b18ac206c9..4ec56621db24 100644 --- a/drivers/platform/loongarch/loongson-laptop.c +++ b/drivers/platform/loongarch/loongson-laptop.c @@ -30,7 +30,6 @@ #define LOONGSON_ACPI_HKEY_HID "LOON0000" #define ACPI_LAPTOP_NAME "loongson-laptop" -#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson" #define MAX_ACPI_ARGS 3 #define GENERIC_HOTKEY_MAP_MAX 64 @@ -167,8 +166,6 @@ static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) } sub_driver->device->driver_data = sub_driver; - sprintf(acpi_device_class(sub_driver->device), "%s/%s", - ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name); status = acpi_install_notify_handler(*sub_driver->handle, sub_driver->type, dispatch_acpi_notify, sub_driver); @@ -189,6 +186,7 @@ static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) static int loongson_hotkey_suspend(struct device *dev) { + bl_powered = false; return 0; } diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c index f67c7f56ab2b..9ddc7fa1a973 100644 --- a/drivers/platform/mellanox/mlxbf-bootctl.c +++ b/drivers/platform/mellanox/mlxbf-bootctl.c @@ -10,6 +10,7 @@ #include <linux/acpi.h> #include <linux/arm-smccc.h> +#include <linux/bitfield.h> #include <linux/delay.h> #include <linux/if_ether.h> #include <linux/iopoll.h> diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c index 5ec1ad471696..2ad9e2b0493c 100644 --- a/drivers/platform/mellanox/mlxbf-pmc.c +++ b/drivers/platform/mellanox/mlxbf-pmc.c @@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev) static int mlxbf_pmc_probe(struct platform_device *pdev) { - struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); - const char *hid = acpi_device_hid(acpi_dev); struct device *dev = &pdev->dev; + struct acpi_device *acpi_dev; struct arm_smccc_res res; + const char *hid; guid_t guid; int ret; + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + + hid = acpi_device_hid(acpi_dev); + /* Ensure we have the UUID we expect for this service. */ arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); guid_parse(mlxbf_pmc_svc_uuid_str, &guid); diff --git a/drivers/platform/mellanox/nvsw-sn2201.c b/drivers/platform/mellanox/nvsw-sn2201.c index 51504113c17e..92b58ba8f97b 100644 --- a/drivers/platform/mellanox/nvsw-sn2201.c +++ b/drivers/platform/mellanox/nvsw-sn2201.c @@ -10,7 +10,6 @@ #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/irq.h> -#include <linux/gpio.h> #include <linux/module.h> #include <linux/platform_data/mlxcpld.h> #include <linux/platform_data/mlxreg.h> diff --git a/drivers/platform/olpc/olpc-xo175-ec.c b/drivers/platform/olpc/olpc-xo175-ec.c index fa7b3bda688a..bee271a4fda1 100644 --- a/drivers/platform/olpc/olpc-xo175-ec.c +++ b/drivers/platform/olpc/olpc-xo175-ec.c @@ -482,7 +482,7 @@ static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp, dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len); if (inlen > 5) { - dev_err(dev, "command len %zd too big!\n", resp_len); + dev_err(dev, "command len %zd too big!\n", inlen); return -EOVERFLOW; } diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index a9dcb0bbe90e..593a7aba6243 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev) static int san_probe(struct platform_device *pdev) { - struct acpi_device *san = ACPI_COMPANION(&pdev->dev); struct ssam_controller *ctrl; + struct acpi_device *san; struct san_data *data; acpi_status astatus; int status; + san = ACPI_COMPANION(&pdev->dev); + if (!san) + return -ENODEV; + ctrl = ssam_client_bind(&pdev->dev); if (IS_ERR(ctrl)) return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c index 8b8b80228c14..541e9180f976 100644 --- a/drivers/platform/surface/surface_aggregator_hub.c +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_hub_match[] = { - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), + .driver_data = (unsigned long)&kip_hub, + }, { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), + .driver_data = (unsigned long)&base_hub, + }, { } }; MODULE_DEVICE_TABLE(ssam, ssam_hub_match); diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 0599d5adf02e..d37a7e4c6b02 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -295,8 +295,6 @@ static const struct software_node *ssam_node_group_sl6[] = { /* Devices for Surface Laptop 7. */ static const struct software_node *ssam_node_group_sl7[] = { &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, &ssam_node_tmp_perf_profile_with_fan, &ssam_node_fan_speed, &ssam_node_hid_sam_keyboard, @@ -422,6 +420,19 @@ static const struct software_node *ssam_node_group_sp11[] = { NULL, }; +/* Devices for Surface Pro 12" first edition (ARM/QCOM) */ +static const struct software_node *ssam_node_group_sp12in[] = { + &ssam_node_root, + &ssam_node_hub_kip, + &ssam_node_tmp_sensors, + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_sam_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_fwupd, + &ssam_node_pos_tablet_switch, + NULL, +}; + /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { @@ -500,6 +511,8 @@ static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = { { .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g }, /* Surface Pro 11 (ARM/QCOM) */ { .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 }, + /* Surface Pro 12in First Edition (ARM/QCOM) */ + { .compatible = "microsoft,surface-pro-12in", (void *)ssam_node_group_sp12in }, /* Surface Laptop 7 */ { .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 }, { .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 }, diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c index ffa36ed92897..13031c329553 100644 --- a/drivers/platform/surface/surface_aggregator_tabletsw.c +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_tablet_sw_match[] = { - { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, - { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, + { + SSAM_SDEV(KIP, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_kip_sw_desc, + }, { + SSAM_SDEV(POS, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_pos_sw_desc, + }, { }, }; MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index b359413903b1..29b264f2a444 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -11,6 +11,7 @@ #include <linux/acpi.h> #include <linux/dmi.h> +#include <linux/err.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> @@ -290,9 +291,9 @@ static struct platform_device *surface_gpe_device; static int __init surface_gpe_init(void) { + struct platform_device_info pdevinfo; const struct dmi_system_id *match; struct platform_device *pdev; - struct fwnode_handle *fwnode; int status; match = dmi_first_match(dmi_lid_device_table); @@ -305,44 +306,27 @@ static int __init surface_gpe_init(void) if (status) return status; - fwnode = fwnode_create_software_node(match->driver_data, NULL); - if (IS_ERR(fwnode)) { - status = PTR_ERR(fwnode); - goto err_node; - } - - pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); - if (!pdev) { - status = -ENOMEM; - goto err_alloc; + pdevinfo = (struct platform_device_info){ + .name = "surface_gpe", + .id = PLATFORM_DEVID_NONE, + .properties = match->driver_data, + }; + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + platform_driver_unregister(&surface_gpe_driver); + return PTR_ERR(pdev); } - pdev->dev.fwnode = fwnode; - - status = platform_device_add(pdev); - if (status) - goto err_add; - surface_gpe_device = pdev; return 0; - -err_add: - platform_device_put(pdev); -err_alloc: - fwnode_remove_software_node(fwnode); -err_node: - platform_driver_unregister(&surface_gpe_driver); - return status; } module_init(surface_gpe_init); static void __exit surface_gpe_exit(void) { - struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode; - platform_device_unregister(surface_gpe_device); platform_driver_unregister(&surface_gpe_driver); - fwnode_remove_software_node(fwnode); } module_exit(surface_gpe_exit); diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c index c0d83ed5a208..33a8a9d41900 100644 --- a/drivers/platform/surface/surface_hotplug.c +++ b/drivers/platform/surface/surface_hotplug.c @@ -14,7 +14,7 @@ */ #include <linux/acpi.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 9bd39f09c7db..b38aa010053e 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -14,12 +14,12 @@ #include <linux/types.h> #include <linux/input.h> #include <linux/acpi.h> +#include <linux/platform_device.h> #include <acpi/button.h> #define SURFACE_PRO3_BUTTON_HID "MSHW0028" #define SURFACE_PRO4_BUTTON_HID "MSHW0040" #define SURFACE_BUTTON_OBJ_NAME "VGBI" -#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" #define MSHW0040_DSM_REVISION 0x01 #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision @@ -72,9 +72,10 @@ struct surface_button { bool suspended; }; -static void surface_button_notify(struct acpi_device *device, u32 event) +static void surface_button_notify(acpi_handle handle, u32 event, void *data) { - struct surface_button *button = acpi_driver_data(device); + struct device *dev = data; + struct surface_button *button = dev_get_drvdata(dev); struct input_dev *input; int key_code = KEY_RESERVED; bool pressed = false; @@ -109,18 +110,17 @@ static void surface_button_notify(struct acpi_device *device, u32 event) key_code = KEY_VOLUMEDOWN; break; case SURFACE_BUTTON_NOTIFY_TABLET_MODE: - dev_warn_once(&device->dev, "Tablet mode is not supported\n"); + dev_warn_once(dev, "Tablet mode is not supported\n"); break; default: - dev_info_ratelimited(&device->dev, - "Unsupported event [0x%x]\n", event); + dev_info_ratelimited(dev, "Unsupported event [0x%x]\n", event); break; } input = button->input; if (key_code == KEY_RESERVED) return; if (pressed) - pm_wakeup_dev_event(&device->dev, 0, button->suspended); + pm_wakeup_dev_event(dev, 0, button->suspended); if (button->suspended) return; input_report_key(input, key_code, pressed?1:0); @@ -130,8 +130,7 @@ static void surface_button_notify(struct acpi_device *device, u32 event) #ifdef CONFIG_PM_SLEEP static int surface_button_suspend(struct device *dev) { - struct acpi_device *device = to_acpi_device(dev); - struct surface_button *button = acpi_driver_data(device); + struct surface_button *button = dev_get_drvdata(dev); button->suspended = true; return 0; @@ -139,8 +138,7 @@ static int surface_button_suspend(struct device *dev) static int surface_button_resume(struct device *dev) { - struct acpi_device *device = to_acpi_device(dev); - struct surface_button *button = acpi_driver_data(device); + struct surface_button *button = dev_get_drvdata(dev); button->suspended = false; return 0; @@ -155,9 +153,8 @@ static int surface_button_resume(struct device *dev) * Returns true if the driver should bind to this device, i.e. the device is * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. */ -static bool surface_button_check_MSHW0040(struct acpi_device *dev) +static bool surface_button_check_MSHW0040(struct device *dev, acpi_handle handle) { - acpi_handle handle = dev->handle; union acpi_object *result; u64 oem_platform_rev = 0; // valid revisions are nonzero @@ -179,44 +176,48 @@ static bool surface_button_check_MSHW0040(struct acpi_device *dev) ACPI_FREE(result); } - dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); return oem_platform_rev == 0; } -static int surface_button_add(struct acpi_device *device) +static int surface_button_probe(struct platform_device *pdev) { struct surface_button *button; + struct acpi_device *device; struct input_dev *input; - const char *hid = acpi_device_hid(device); int error; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, strlen(SURFACE_BUTTON_OBJ_NAME))) return -ENODEV; - if (!surface_button_check_MSHW0040(device)) + if (!surface_button_check_MSHW0040(&pdev->dev, device->handle)) return -ENODEV; button = kzalloc_obj(struct surface_button); if (!button) return -ENOMEM; - device->driver_data = button; + platform_set_drvdata(pdev, button); button->input = input = input_allocate_device(); if (!input) { error = -ENOMEM; goto err_free_button; } - strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); - snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); + snprintf(button->phys, sizeof(button->phys), "%s/buttons", + acpi_device_hid(device)); - input->name = acpi_device_name(device); + input->name = "Surface Pro 3/4 Buttons"; input->phys = button->phys; input->id.bustype = BUS_HOST; - input->dev.parent = &device->dev; + input->dev.parent = &pdev->dev; input_set_capability(input, EV_KEY, KEY_POWER); input_set_capability(input, EV_KEY, KEY_LEFTMETA); input_set_capability(input, EV_KEY, KEY_VOLUMEUP); @@ -226,9 +227,18 @@ static int surface_button_add(struct acpi_device *device) if (error) goto err_free_input; - device_init_wakeup(&device->dev, true); - dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device), - acpi_device_bid(device)); + device_init_wakeup(&pdev->dev, true); + + error = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + surface_button_notify, &pdev->dev); + if (error) { + device_init_wakeup(&pdev->dev, false); + input_unregister_device(input); + goto err_free_button; + } + + dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device)); + return 0; err_free_input: @@ -238,10 +248,13 @@ static int surface_button_add(struct acpi_device *device) return error; } -static void surface_button_remove(struct acpi_device *device) +static void surface_button_remove(struct platform_device *pdev) { - struct surface_button *button = acpi_driver_data(device); + struct surface_button *button = platform_get_drvdata(pdev); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, surface_button_notify); + device_init_wakeup(&pdev->dev, false); input_unregister_device(button->input); kfree(button); } @@ -249,16 +262,14 @@ static void surface_button_remove(struct acpi_device *device) static SIMPLE_DEV_PM_OPS(surface_button_pm, surface_button_suspend, surface_button_resume); -static struct acpi_driver surface_button_driver = { - .name = "surface_pro3_button", - .class = "SurfacePro3", - .ids = surface_button_device_ids, - .ops = { - .add = surface_button_add, - .remove = surface_button_remove, - .notify = surface_button_notify, +static struct platform_driver surface_button_driver = { + .probe = surface_button_probe, + .remove = surface_button_remove, + .driver = { + .name = "surface_pro3_button", + .acpi_match_table = surface_button_device_ids, + .pm = &surface_button_pm, }, - .drv.pm = &surface_button_pm, }; -module_acpi_driver(surface_button_driver); +module_platform_driver(surface_button_driver); diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c index b8e6b9a421c6..529825dcfbfe 100644 --- a/drivers/platform/wmi/core.c +++ b/drivers/platform/wmi/core.c @@ -364,20 +364,23 @@ acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 met EXPORT_SYMBOL_GPL(wmidev_evaluate_method); /** - * wmidev_invoke_method - Invoke a WMI method + * wmidev_invoke_method - Invoke a WMI method that returns values * @wdev: A wmi bus device from a driver * @instance: Instance index * @method_id: Method ID to call * @in: Mandatory WMI buffer containing input for the method call - * @out: Optional WMI buffer to return the method results + * @out: Mandatory WMI buffer to return the method results + * @min_size: Minimum size of the method result data in bytes * - * Invoke a WMI method, the caller must free the resulting data inside @out. - * Said data is guaranteed to be aligned on a 8-byte boundary. + * Invoke a WMI method that returns values, the caller must free the resulting + * data inside @out using kfree(). Said data is guaranteed to be aligned on a + * 8-byte boundary. Use wmidev_invoke_procedure() for WMI methods that + * return no values. * * Return: 0 on success or negative error code on failure. */ int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, - const struct wmi_buffer *in, struct wmi_buffer *out) + const struct wmi_buffer *in, struct wmi_buffer *out, size_t min_size) { struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -398,10 +401,7 @@ int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, ain.pointer = in->data; } - if (out) - status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout); - else - status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL); + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout); if (wblock->gblock.flags & ACPI_WMI_STRING) kfree(ain.pointer); @@ -409,24 +409,68 @@ int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id, if (ACPI_FAILURE(status)) return -EIO; - if (!out) - return 0; - obj = aout.pointer; if (!obj) { + if (min_size != 0) + return -ENOMSG; + out->length = 0; out->data = ZERO_SIZE_PTR; return 0; } - ret = wmi_unmarshal_acpi_object(obj, out); + ret = wmi_unmarshal_acpi_object(obj, out, min_size); kfree(obj); return ret; } EXPORT_SYMBOL_GPL(wmidev_invoke_method); +/** + * wmidev_invoke_procedure - Invoke a WMI method that does not return values + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @method_id: Method ID to call + * @in: Mandatory WMI buffer containing input for the method call + * + * Invoke a WMI method that does not return any values. Use wmidev_invoke_method() + * for WMI methods that do return values. + * + * Return: 0 on success or negative error code on failure. + */ +int wmidev_invoke_procedure(struct wmi_device *wdev, u8 instance, u32 method_id, + const struct wmi_buffer *in) +{ + struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev); + struct acpi_buffer ain; + acpi_status status; + int ret; + + if (wblock->gblock.flags & ACPI_WMI_STRING) { + ret = wmi_marshal_string(in, &ain); + if (ret < 0) + return ret; + } else { + if (in->length > U32_MAX) + return -E2BIG; + + ain.length = in->length; + ain.pointer = in->data; + } + + status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL); + + if (wblock->gblock.flags & ACPI_WMI_STRING) + kfree(ain.pointer); + + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(wmidev_invoke_procedure); + static acpi_status __query_block(struct wmi_block *wblock, u8 instance, struct acpi_buffer *out) { @@ -524,13 +568,15 @@ EXPORT_SYMBOL_GPL(wmidev_block_query); * @wdev: A wmi bus device from a driver * @instance: Instance index * @out: WMI buffer to fill + * @min_size: Minimum size of the result data in bytes * - * Query a WMI data block, the caller must free the resulting data inside @out. - * Said data is guaranteed to be aligned on a 8-byte boundary. + * Query a WMI data block, the caller must free the resulting data inside @out + * using kfree(). Said data is guaranteed to be aligned on a 8-byte boundary. * * Return: 0 on success or a negative error code on failure. */ -int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out) +int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out, + size_t min_size) { union acpi_object *obj; int ret; @@ -539,7 +585,7 @@ int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer * if (!obj) return -EIO; - ret = wmi_unmarshal_acpi_object(obj, out); + ret = wmi_unmarshal_acpi_object(obj, out, min_size); kfree(obj); return ret; @@ -812,7 +858,8 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, return sysfs_emit(buf, "wmi:%pUL\n", &wblock->gblock.guid); } -static DEVICE_ATTR_RO(modalias); + +static const DEVICE_ATTR_RO(modalias); static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -821,7 +868,8 @@ static ssize_t guid_show(struct device *dev, struct device_attribute *attr, return sysfs_emit(buf, "%pUL\n", &wblock->gblock.guid); } -static DEVICE_ATTR_RO(guid); + +static const DEVICE_ATTR_RO(guid); static ssize_t instance_count_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -830,7 +878,8 @@ static ssize_t instance_count_show(struct device *dev, return sysfs_emit(buf, "%d\n", (int)wblock->gblock.instance_count); } -static DEVICE_ATTR_RO(instance_count); + +static const DEVICE_ATTR_RO(instance_count); static ssize_t expensive_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -840,41 +889,14 @@ static ssize_t expensive_show(struct device *dev, return sysfs_emit(buf, "%d\n", (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0); } -static DEVICE_ATTR_RO(expensive); - -static ssize_t driver_override_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct wmi_device *wdev = to_wmi_device(dev); - ssize_t ret; - - device_lock(dev); - ret = sysfs_emit(buf, "%s\n", wdev->driver_override); - device_unlock(dev); - - return ret; -} - -static ssize_t driver_override_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct wmi_device *wdev = to_wmi_device(dev); - int ret; - - ret = driver_set_override(dev, &wdev->driver_override, buf, count); - if (ret < 0) - return ret; - return count; -} -static DEVICE_ATTR_RW(driver_override); +static const DEVICE_ATTR_RO(expensive); -static struct attribute *wmi_attrs[] = { +static const struct attribute * const wmi_attrs[] = { &dev_attr_modalias.attr, &dev_attr_guid.attr, &dev_attr_instance_count.attr, &dev_attr_expensive.attr, - &dev_attr_driver_override.attr, NULL }; ATTRIBUTE_GROUPS(wmi); @@ -886,9 +908,10 @@ static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr, return sysfs_emit(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id); } -static DEVICE_ATTR_RO(notify_id); -static struct attribute *wmi_event_attrs[] = { +static const DEVICE_ATTR_RO(notify_id); + +static const struct attribute * const wmi_event_attrs[] = { &dev_attr_notify_id.attr, NULL }; @@ -902,7 +925,8 @@ static ssize_t object_id_show(struct device *dev, struct device_attribute *attr, return sysfs_emit(buf, "%c%c\n", wblock->gblock.object_id[0], wblock->gblock.object_id[1]); } -static DEVICE_ATTR_RO(object_id); + +static const DEVICE_ATTR_RO(object_id); static ssize_t setable_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -911,16 +935,17 @@ static ssize_t setable_show(struct device *dev, struct device_attribute *attr, return sysfs_emit(buf, "%d\n", (int)wdev->setable); } -static DEVICE_ATTR_RO(setable); -static struct attribute *wmi_data_attrs[] = { +static const DEVICE_ATTR_RO(setable); + +static const struct attribute * const wmi_data_attrs[] = { &dev_attr_object_id.attr, &dev_attr_setable.attr, NULL }; ATTRIBUTE_GROUPS(wmi_data); -static struct attribute *wmi_method_attrs[] = { +static const struct attribute * const wmi_method_attrs[] = { &dev_attr_object_id.attr, NULL }; @@ -943,7 +968,6 @@ static void wmi_dev_release(struct device *dev) { struct wmi_block *wblock = dev_to_wblock(dev); - kfree(wblock->dev.driver_override); kfree(wblock); } @@ -952,10 +976,12 @@ static int wmi_dev_match(struct device *dev, const struct device_driver *driver) const struct wmi_driver *wmi_driver = to_wmi_driver(driver); struct wmi_block *wblock = dev_to_wblock(dev); const struct wmi_device_id *id = wmi_driver->id_table; + int ret; /* When driver_override is set, only bind to the matching driver */ - if (wblock->dev.driver_override) - return !strcmp(wblock->dev.driver_override, driver->name); + ret = device_match_driver_override(dev, driver); + if (ret >= 0) + return ret; if (id == NULL) return 0; @@ -997,7 +1023,7 @@ static int wmi_dev_probe(struct device *dev) } if (wdriver->notify || wdriver->notify_new) { - if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) + if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && wdriver->min_event_size) return -ENODEV; } @@ -1069,13 +1095,14 @@ static void wmi_dev_shutdown(struct device *dev) } } -static struct class wmi_bus_class = { +static const struct class wmi_bus_class = { .name = "wmi_bus", }; static const struct bus_type wmi_bus_type = { .name = "wmi", .dev_groups = wmi_groups, + .driver_override = true, .match = wmi_dev_match, .uevent = wmi_dev_uevent, .probe = wmi_dev_probe, @@ -1355,10 +1382,14 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) { struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver); + struct wmi_buffer dummy = { + .length = 0, + .data = ZERO_SIZE_PTR, + }; struct wmi_buffer buffer; int ret; - if (!obj && !driver->no_notify_data) { + if (!obj && driver->min_event_size) { dev_warn(&wblock->dev.dev, "Event contains no event data\n"); return; } @@ -1368,11 +1399,11 @@ static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj) if (driver->notify_new) { if (!obj) { - driver->notify_new(&wblock->dev, NULL); + driver->notify_new(&wblock->dev, &dummy); return; } - ret = wmi_unmarshal_acpi_object(obj, &buffer); + ret = wmi_unmarshal_acpi_object(obj, &buffer, driver->min_event_size); if (ret < 0) { dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret); return; diff --git a/drivers/platform/wmi/internal.h b/drivers/platform/wmi/internal.h index 9a39ffa31ad1..c02908694563 100644 --- a/drivers/platform/wmi/internal.h +++ b/drivers/platform/wmi/internal.h @@ -11,7 +11,8 @@ union acpi_object; struct wmi_buffer; -int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer); +int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer, + size_t min_size); int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out); #endif /* _WMI_INTERNAL_H_ */ diff --git a/drivers/platform/wmi/marshalling.c b/drivers/platform/wmi/marshalling.c index 63a92c4ebab5..87091832568e 100644 --- a/drivers/platform/wmi/marshalling.c +++ b/drivers/platform/wmi/marshalling.c @@ -151,7 +151,8 @@ static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer) return 0; } -int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer) +int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer, + size_t min_size) { size_t length, alloc_length; u8 *data; @@ -161,6 +162,9 @@ int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *b if (ret < 0) return ret; + if (length < min_size) + return -ENODATA; + if (ARCH_KMALLOC_MINALIGN < 8) { /* * kmalloc() guarantees that the alignment of the resulting memory allocation is at diff --git a/drivers/platform/wmi/tests/marshalling_kunit.c b/drivers/platform/wmi/tests/marshalling_kunit.c index 0c7cd8774aa3..471963076d58 100644 --- a/drivers/platform/wmi/tests/marshalling_kunit.c +++ b/drivers/platform/wmi/tests/marshalling_kunit.c @@ -372,7 +372,7 @@ static void wmi_unmarshal_acpi_object_test(struct kunit *test) struct wmi_buffer result; int ret; - ret = wmi_unmarshal_acpi_object(¶m->obj, &result); + ret = wmi_unmarshal_acpi_object(¶m->obj, &result, param->buffer.length); if (ret < 0) KUNIT_FAIL_AND_ABORT(test, "Unmarshalling of ACPI object failed\n"); @@ -389,7 +389,7 @@ static void wmi_unmarshal_acpi_object_failure_test(struct kunit *test) struct wmi_buffer result; int ret; - ret = wmi_unmarshal_acpi_object(¶m->obj, &result); + ret = wmi_unmarshal_acpi_object(¶m->obj, &result, 0); if (ret < 0) return; @@ -427,6 +427,25 @@ static void wmi_marshal_string_failure_test(struct kunit *test) KUNIT_FAIL(test, "Invalid string was not rejected\n"); } +static void wmi_unmarshal_acpi_object_undersized_test(struct kunit *test) +{ + const union acpi_object obj = { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = 0xdeadbeef, + }, + }; + struct wmi_buffer result; + int ret; + + ret = wmi_unmarshal_acpi_object(&obj, &result, sizeof(expected_single_integer) + 1); + if (ret < 0) + return; + + kfree(result.data); + KUNIT_FAIL(test, "Undersized unmarshalling result was not rejected\n"); +} + static struct kunit_case wmi_marshalling_test_cases[] = { KUNIT_CASE_PARAM(wmi_unmarshal_acpi_object_test, wmi_unmarshal_acpi_object_gen_params), @@ -436,6 +455,7 @@ static struct kunit_case wmi_marshalling_test_cases[] = { wmi_unmarshal_acpi_object_failure_gen_params), KUNIT_CASE_PARAM(wmi_marshal_string_failure_test, wmi_marshal_string_failure_gen_params), + KUNIT_CASE(wmi_unmarshal_acpi_object_undersized_test), {} }; diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4cb7d97a9fcc..957034f39e4e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -113,6 +113,25 @@ config GIGABYTE_WMI To compile this driver as a module, choose M here: the module will be called gigabyte-wmi. +config BITLAND_MIFS_WMI + tristate "Bitland MIFS (MiInterface) WMI driver" + depends on ACPI_WMI + depends on HWMON + depends on INPUT + depends on LEDS_CLASS + depends on POWER_SUPPLY + select ACPI_PLATFORM_PROFILE + select INPUT_SPARSEKMAP + help + This is a driver for Bitland MiInterface based laptops. + + It provides the access to the temperature, fan speed, gpu + control, keyboard backlight brightness and platform profile + via hwmon and sysfs. + + To compile this driver as a module, choose M here: the module will + be called bitland-mifs-wmi. + config ACERHDF tristate "Acer Aspire One temperature and fan driver" depends on ACPI_EC && THERMAL @@ -327,6 +346,7 @@ config AYANEO_EC config MERAKI_MX100 tristate "Cisco Meraki MX100 Platform Driver" depends on GPIOLIB + depends on LPC_ICH depends on GPIO_ICH depends on LEDS_CLASS select LEDS_GPIO @@ -782,7 +802,6 @@ config LG_LAPTOP tristate "LG Laptop Extras" depends on ACPI depends on ACPI_BATTERY - depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP select NEW_LEDS diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index d25762f7114f..872ac3842391 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_NVIDIA_WMI_EC_BACKLIGHT) += nvidia-wmi-ec-backlight.o obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o obj-$(CONFIG_REDMI_WMI) += redmi-wmi.o obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o +obj-$(CONFIG_BITLAND_MIFS_WMI) += bitland-mifs-wmi.o # Acer obj-$(CONFIG_ACERHDF) += acerhdf.o diff --git a/drivers/platform/x86/acer-wireless.c b/drivers/platform/x86/acer-wireless.c index 1b5d935d085a..fae8e5ad0f97 100644 --- a/drivers/platform/x86/acer-wireless.c +++ b/drivers/platform/x86/acer-wireless.c @@ -10,6 +10,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/pci_ids.h> +#include <linux/platform_device.h> #include <linux/types.h> static const struct acpi_device_id acer_wireless_acpi_ids[] = { @@ -18,13 +19,14 @@ static const struct acpi_device_id acer_wireless_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, acer_wireless_acpi_ids); -static void acer_wireless_notify(struct acpi_device *adev, u32 event) +static void acer_wireless_notify(acpi_handle handle, u32 event, void *data) { - struct input_dev *idev = acpi_driver_data(adev); + struct device *dev = data; + struct input_dev *idev = dev_get_drvdata(dev); - dev_dbg(&adev->dev, "event=%#x\n", event); + dev_dbg(dev, "event=%#x\n", event); if (event != 0x80) { - dev_notice(&adev->dev, "Unknown SMKB event: %#x\n", event); + dev_notice(dev, "Unknown SMKB event: %#x\n", event); return; } input_report_key(idev, KEY_RFKILL, 1); @@ -33,15 +35,21 @@ static void acer_wireless_notify(struct acpi_device *adev, u32 event) input_sync(idev); } -static int acer_wireless_add(struct acpi_device *adev) +static int acer_wireless_probe(struct platform_device *pdev) { + struct acpi_device *adev; struct input_dev *idev; + int ret; - idev = devm_input_allocate_device(&adev->dev); + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return -ENODEV; + + idev = devm_input_allocate_device(&pdev->dev); if (!idev) return -ENOMEM; - adev->driver_data = idev; + platform_set_drvdata(pdev, idev); idev->name = "Acer Wireless Radio Control"; idev->phys = "acer-wireless/input0"; idev->id.bustype = BUS_HOST; @@ -50,19 +58,31 @@ static int acer_wireless_add(struct acpi_device *adev) set_bit(EV_KEY, idev->evbit); set_bit(KEY_RFKILL, idev->keybit); - return input_register_device(idev); + ret = input_register_device(idev); + if (ret) + return ret; + + return acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, + acer_wireless_notify, + &pdev->dev); +} + +static void acer_wireless_remove(struct platform_device *pdev) +{ + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, + acer_wireless_notify); } -static struct acpi_driver acer_wireless_driver = { - .name = "Acer Wireless Radio Control Driver", - .class = "hotkey", - .ids = acer_wireless_acpi_ids, - .ops = { - .add = acer_wireless_add, - .notify = acer_wireless_notify, +static struct platform_driver acer_wireless_driver = { + .probe = acer_wireless_probe, + .remove = acer_wireless_remove, + .driver = { + .name = "Acer Wireless Radio Control Driver", + .acpi_match_table = acer_wireless_acpi_ids, }, }; -module_acpi_driver(acer_wireless_driver); +module_platform_driver(acer_wireless_driver); MODULE_DESCRIPTION("Acer Wireless Radio Control Driver"); MODULE_AUTHOR("Chris Chiu <chiu@gmail.com>"); diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index e0eaaefb13d0..61ae622c93d9 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out) return -EIO; obj = result.pointer; - if (obj && out) { + if (!obj && out) { + ret = -ENOMSG; + } else if (obj && out) { switch (obj->type) { case ACPI_TYPE_INTEGER: *out = obj->integer.value; diff --git a/drivers/platform/x86/adv_swbutton.c b/drivers/platform/x86/adv_swbutton.c index 6fa60f3fc53c..8f7a26e6de81 100644 --- a/drivers/platform/x86/adv_swbutton.c +++ b/drivers/platform/x86/adv_swbutton.c @@ -48,10 +48,14 @@ static int adv_swbutton_probe(struct platform_device *device) { struct adv_swbutton *button; struct input_dev *input; - acpi_handle handle = ACPI_HANDLE(&device->dev); + acpi_handle handle; acpi_status status; int error; + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + button = devm_kzalloc(&device->dev, sizeof(*button), GFP_KERNEL); if (!button) return -ENOMEM; diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig index b813f9265368..a1a74ef6c859 100644 --- a/drivers/platform/x86/amd/Kconfig +++ b/drivers/platform/x86/amd/Kconfig @@ -34,6 +34,17 @@ config AMD_WBRF This mechanism will only be activated on platforms that advertise a need for it. +config AMD_HALO_LED + tristate "AMD Halo Box RGB LED Driver" + depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR + help + This driver provides RGB LED control for AMD Halo Box devices + through the LED multicolor subsystem. The Halo Box light bar can + be controlled via sysfs to display any RGB color combination. + + To compile this driver as a module, choose M here: the module + will be called amd_halo_led. + config AMD_ISP_PLATFORM tristate "AMD ISP4 platform driver" depends on I2C && X86_64 && ACPI diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile index f6ff0c837f34..2f467dbbfc8a 100644 --- a/drivers/platform/x86/amd/Makefile +++ b/drivers/platform/x86/amd/Makefile @@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/ obj-$(CONFIG_AMD_HSMP) += hsmp/ obj-$(CONFIG_AMD_PMF) += pmf/ obj-$(CONFIG_AMD_WBRF) += wbrf.o +obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o obj-$(CONFIG_AMD_HFI) += hfi/ diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c new file mode 100644 index 000000000000..b21d956c7d96 --- /dev/null +++ b/drivers/platform/x86/amd/amd_halo_led.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Halo Box RGB LED Driver + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + * + * This driver provides RGB LED control for AMD Halo Box devices through + * the LED multicolor subsystem. The Halo Box light bar can be controlled + * via sysfs to display any RGB color combination. + */ + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/compiler_attributes.h> +#include <linux/container_of.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/device/devres.h> +#include <linux/led-class-multicolor.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/wmi.h> + +#include <linux/byteorder/generic.h> + +#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86" + +/* WMI method IDs from MOF */ +enum { + AMD_HALO_WMI_TURN_OFF = 0x04, + AMD_HALO_WMI_RGB = 0x07, +}; + +/* Arg0 of the AMD_HALO_WMI_RGB Method */ +enum { + AMD_HALO_RGB_CMD_GET = 0x0, + AMD_HALO_RGB_CMD_SET = 0x1, +}; + +/* Status codes from spec */ +#define AMD_HALO_STATUS_SUCCESS 0x0000 +#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD + +/* Brightness uses 0-100 range */ +#define AMD_HALO_MAX_HW_BRIGHTNESS 100 + +/** + * struct amd_halo_led_data - Driver private data + * @wdev: WMI device pointer + * @led_mc: LED multicolor class device + * @subled_info: RGB channel information + * @lock: Mutex to protect WMI calls + */ +struct amd_halo_led_data { + struct wmi_device *wdev; + struct led_classdev_mc led_mc; + struct mc_subled subled_info[3]; + struct mutex lock; +}; + +struct amd_halo_wmi_args { + __le32 arg0; + __le32 arg1; +}; + +struct amd_halo_wmi_args_rgb { + __le32 cmd; + __le32 red; + __le32 green; + __le32 blue; +}; + +struct amd_halo_wmi_output_rgb { + __le16 status; + u8 red; + u8 green; + u8 blue; +} __packed; + +static inline int wmi_status_to_err(u16 status) +{ + switch (status) { + case AMD_HALO_STATUS_SUCCESS: + return 0; + case AMD_HALO_STATUS_INVALID_PARAM: + return -EINVAL; + default: + return -EIO; + } +} + +static int __amd_halo_wmi_call(struct wmi_device *wdev, + u32 method_id, void *data, size_t length) +{ + struct wmi_buffer input = { + .length = length, + .data = data, + }; + struct wmi_buffer output = { }; + int ret; + + /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */ + ret = wmidev_invoke_method(wdev, 0, method_id, + &input, &output, sizeof(__le16)); + if (ret) + return ret; + + __le16 *result_status __free(kfree) = output.data; + + return wmi_status_to_err(le16_to_cpu(*result_status)); +} + +/** + * amd_halo_wmi_turn_off - Turn off all LED channels + * @wdev: WMI device pointer + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_turn_off(struct wmi_device *wdev) +{ + struct amd_halo_wmi_args args = { }; + + return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args)); +} + +/** + * amd_halo_wmi_set_rgb - Set all RGB channels atomically + * @wdev: WMI device pointer + * @r: brightness for red channel (0 - 100) + * @g: brightness for green channel (0 - 100) + * @b: brightness for blue channel (0 - 100) + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b) +{ + struct amd_halo_wmi_args_rgb args = { + .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET), + .red = cpu_to_le32(r), + .green = cpu_to_le32(g), + .blue = cpu_to_le32(b), + }; + + if (r > AMD_HALO_MAX_HW_BRIGHTNESS || + g > AMD_HALO_MAX_HW_BRIGHTNESS || + b > AMD_HALO_MAX_HW_BRIGHTNESS) { + return -EINVAL; + } + + return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args)); +} + +/** + * amd_halo_wmi_get_rgb - Get RGB values + * @wdev: WMI device pointer + * @r: output buffer for red value + * @g: output buffer for green value + * @b: output buffer for blue value + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b) +{ + struct amd_halo_wmi_args_rgb args = { + .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET), + }; + struct wmi_buffer input = { + .length = sizeof(args), + .data = &args, + }; + struct wmi_buffer output = { }; + int ret; + + ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB, + &input, &output, sizeof(struct amd_halo_wmi_output_rgb)); + if (ret) + return ret; + + struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data; + + ret = wmi_status_to_err(le16_to_cpu(data->status)); + if (ret) + return ret; + + if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS || + data->green > AMD_HALO_MAX_HW_BRIGHTNESS || + data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) { + return -EPROTO; + } + + *r = data->red; + *g = data->green; + *b = data->blue; + + return 0; +} + +/** + * amd_halo_brightness_set - Set LED brightness + * @cdev: LED class device + * @brightness: Brightness value + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct amd_halo_led_data *data = container_of(mc_cdev, + struct amd_halo_led_data, + led_mc); + u32 red_hw, green_hw, blue_hw; + int ret; + + guard(mutex)(&data->lock); + + led_mc_calc_color_components(mc_cdev, brightness); + + if (brightness == 0) + return amd_halo_wmi_turn_off(data->wdev); + + red_hw = mc_cdev->subled_info[0].brightness; + green_hw = mc_cdev->subled_info[1].brightness; + blue_hw = mc_cdev->subled_info[2].brightness; + + ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw); + if (ret) + goto out; + + return 0; + +out: + /* + * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed. + * Attempt to turn the LED off completely as clean-up. + */ + if (amd_halo_wmi_turn_off(data->wdev)) + dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n"); + + return ret; +} + +static int amd_halo_probe(struct wmi_device *wdev, const void *context) +{ + struct led_init_data led_init_data = { + .devicename = "amd_halo", + .default_label = "multicolor:" LED_FUNCTION_STATUS, + .devname_mandatory = true, + }; + struct amd_halo_led_data *data; + u8 r, g, b; + int ret; + + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = devm_mutex_init(&wdev->dev, &data->lock); + if (ret) + return ret; + + data->wdev = wdev; + dev_set_drvdata(&wdev->dev, data); + + data->subled_info[0].color_index = LED_COLOR_ID_RED; + data->subled_info[1].color_index = LED_COLOR_ID_GREEN; + data->subled_info[2].color_index = LED_COLOR_ID_BLUE; + + data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS; + data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS; + data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set; + data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; + data->led_mc.num_colors = ARRAY_SIZE(data->subled_info); + data->led_mc.subled_info = data->subled_info; + + ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b); + if (ret) + return ret; + + data->subled_info[0].intensity = r; + data->subled_info[1].intensity = g; + data->subled_info[2].intensity = b; + + ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc, + &led_init_data); + if (ret) + return dev_err_probe(&wdev->dev, ret, + "Failed to register multicolor LED\n"); + return 0; +} + +static const struct wmi_device_id amd_halo_id_table[] = { + { .guid_string = AMD_HALO_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, amd_halo_id_table); + +static struct wmi_driver amd_halo_driver = { + .driver = { + .name = "amd_halo_led", + }, + .id_table = amd_halo_id_table, + .probe = amd_halo_probe, + .no_singleton = true, +}; + +module_wmi_driver(amd_halo_driver); + +MODULE_AUTHOR("Mario Limonciello (AMD) <superm1@kernel.org>"); +MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) <Leo.Lin@amd.com>"); +MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/amd/hfi/hfi.c b/drivers/platform/x86/amd/hfi/hfi.c index 83863a5e0fbc..e0ebcb0c4acd 100644 --- a/drivers/platform/x86/amd/hfi/hfi.c +++ b/drivers/platform/x86/amd/hfi/hfi.c @@ -33,7 +33,8 @@ #define AMD_HFI_DRIVER "amd_hfi" #define AMD_HFI_MAILBOX_COUNT 1 -#define AMD_HETERO_RANKING_TABLE_VER 2 +#define AMD_HETERO_RANKING_TABLE_MIN_VER 2 +#define AMD_HETERO_RANKING_TABLE_MAX_VER 3 #define AMD_HETERO_CPUID_27 0x80000027 @@ -158,7 +159,8 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data) dev_err(amd_hfi_data->dev, "invalid signature in shared memory\n"); return -EINVAL; } - if (amd_hfi_data->shmem->version_number != AMD_HETERO_RANKING_TABLE_VER) { + if (amd_hfi_data->shmem->version_number < AMD_HETERO_RANKING_TABLE_MIN_VER || + amd_hfi_data->shmem->version_number > AMD_HETERO_RANKING_TABLE_MAX_VER) { dev_err(amd_hfi_data->dev, "invalid version %d\n", amd_hfi_data->shmem->version_number); return -EINVAL; diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 97ed71593bdf..ddd7a04ee753 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -15,12 +15,18 @@ #include <linux/array_size.h> #include <linux/bits.h> #include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/dev_printk.h> #include <linux/ioport.h> +#include <linux/kref.h> #include <linux/kstrtox.h> +#include <linux/lockdep.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/string.h> #include <linux/sysfs.h> #include <linux/topology.h> #include <linux/uuid.h> @@ -38,6 +44,17 @@ static struct hsmp_plat_device *hsmp_pdev; +/* + * Tracks the ACPI socket platform devices that share the socket array and the + * /dev/hsmp misc device. The first probe initializes it, each further probe + * takes a reference and every remove (or probe failure) drops one; the last + * put frees the shared state via hsmp_acpi_sock_release(). All get/put run + * under hsmp_sock_rwsem held for write, so the counting is already serialized + * and the atomic in kref is not strictly needed; kref is used for the clearer + * get/put interface and its release callback. + */ +static struct kref hsmp_acpi_sock_kref; + struct hsmp_sys_attr { struct device_attribute dattr; u32 msg_id; @@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) * bytes to integer. */ uid = acpi_device_uid(ACPI_COMPANION(dev)); + if (!uid || strlen(uid) < 3) + return -EINVAL; return kstrtou16(uid + 2, 10, sock_ind); } @@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) return AE_OK; } -static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) +static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *guid, *mailbox_package; @@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) int ret = 0; int j; - status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, + status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, &buf, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", acpi_format_exception(status)); return -ENODEV; } @@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) guid = &dsd->package.elements[0]; mailbox_package = &dsd->package.elements[1]; if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { - dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); + dev_err(dev, "Invalid hsmp _DSD table data\n"); ret = -EINVAL; goto free_buf; } @@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) union acpi_object *msgobj, *msgstr, *msgint; msgobj = &mailbox_package->package.elements[j]; - msgstr = &msgobj->package.elements[0]; - msgint = &msgobj->package.elements[1]; /* package should have 1 string and 1 integer object */ if (msgobj->type != ACPI_TYPE_PACKAGE || - msgstr->type != ACPI_TYPE_STRING || + msgobj->package.count < 2) { + ret = -EINVAL; + goto free_buf; + } + + msgstr = &msgobj->package.elements[0]; + msgint = &msgobj->package.elements[1]; + + if (msgstr->type != ACPI_TYPE_STRING || msgint->type != ACPI_TYPE_INTEGER) { ret = -EINVAL; goto free_buf; @@ -183,14 +208,14 @@ free_buf: return ret; } -static int hsmp_read_acpi_crs(struct hsmp_socket *sock) +static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) { acpi_status status; - status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, + status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, hsmp_resource, sock); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", acpi_format_exception(status)); return -EINVAL; } @@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) return -EINVAL; /* The mapped region should be un-cached */ - sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, + sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, sock->mbinfo.size); if (!sock->virt_base_addr) { - dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); + dev_err(dev, "Failed to ioremap MP1 base address\n"); return -ENOMEM; } @@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) int ret; sock->sock_ind = sock_ind; - sock->dev = dev; sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; sema_init(&sock->hsmp_sem, 1); @@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) dev_set_drvdata(dev, sock); /* Read MP1 base address from CRS method */ - ret = hsmp_read_acpi_crs(sock); + ret = hsmp_read_acpi_crs(dev, sock); if (ret) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(sock); + ret = hsmp_read_acpi_dsd(dev, sock); + if (ret) + return ret; + + /* + * Publish sock->dev last. hsmp_send_message() uses it (via + * smp_load_acquire()) as the readiness gate for the lock-free data + * plane, so it must become visible only after virt_base_addr, the + * mailbox offsets and the semaphore are fully initialized. On a + * multi-socket system socket 0 exposes /dev/hsmp before later sockets + * finish probing, so without this an ioctl aimed at a socket still in + * bring-up could pass the gate and dereference a NULL virt_base_addr. + */ + smp_store_release(&sock->dev, dev); + + return 0; } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, @@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj struct device *dev = container_of(kobj, struct device, kobj); struct hsmp_socket *sock = dev_get_drvdata(dev); + /* + * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. + * It can therefore only carry the protocol version 6 metric table + * (struct hsmp_metric_table). The larger tables defined from protocol + * version 7 onwards do not fit; userspace on those systems must read + * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. + * Surface the unsupported case here as -EOPNOTSUPP rather than + * silently truncating the snapshot. + */ + if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) + return -EOPNOTSUPP; + return hsmp_metric_tbl_read(sock, buf, count); } static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, const struct bin_attribute *battr, int id) { - if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) + /* + * Keep metrics_bin visible on protocol version 7 and later as well, + * so that userspace which expects the file to exist gets a clear + * -EOPNOTSUPP from the read handler instead of -ENOENT, and is + * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. + */ + if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) return battr->attr.mode; return 0; @@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att return len; } +/* + * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox + * handshake and register its sysfs/hwmon interfaces. + * + * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the + * per-socket bring-up cannot race a concurrent probe or remove. + */ static int init_acpi(struct device *dev) { u16 sock_ind; int ret; + lockdep_assert_held_write(&hsmp_sock_rwsem); + ret = hsmp_get_uid(dev, &sock_ind); if (ret) return ret; @@ -491,7 +557,7 @@ static int init_acpi(struct device *dev) return ret; } - if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { + if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { ret = hsmp_get_tbl_dram_base(sock_ind); if (ret) dev_info(dev, "Failed to init metric table\n"); @@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); +/* + * kref release: tear down the shared ACPI socket state once the last socket + * drops its reference. Deregister /dev/hsmp if it was registered, unmap any + * metric-table DRAM, destroy the per-socket mutexes and free the socket array. + * + * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove + * and probe-failure paths both drop their reference under that lock. The write + * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox + * and freeing the array cannot race the data plane. + */ +static void hsmp_acpi_sock_release(struct kref *kref) +{ + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) + hsmp_misc_deregister(); + hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); + kfree(hsmp_pdev->sock); + hsmp_pdev->sock = NULL; + hsmp_pdev->num_sockets = 0; + hsmp_pdev->proto_ver = 0; +} + +/** + * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. + * @dev: ACPI companion device whose probe failed. + * + * This device already took a reference on entry to hsmp_acpi_probe(), so clear + * its sock->dev and drop that reference; the shared state is released if it was + * the last one. + * + * Clearing sock->dev matters on multi-socket systems: when a non-first socket + * fails, the array stays alive (owned by an already-probed socket) and + * remove() is never called for this device, yet devres unmaps its mailbox once + * probe() returns. Without clearing dev, a later message to this index would + * pass every gate in hsmp_send_message() and reach the unmapped mailbox. + * + * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata. + * + * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. + */ +static void hsmp_acpi_probe_failure_cleanup(struct device *dev) +{ + struct hsmp_socket *sock = dev_get_drvdata(dev); + + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (sock) + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); +} + static int hsmp_acpi_probe(struct platform_device *pdev) { int ret; @@ -584,34 +704,60 @@ static int hsmp_acpi_probe(struct platform_device *pdev) if (!hsmp_pdev) return -ENOMEM; - if (!hsmp_pdev->is_probed) { + /* + * Multiple ACPI socket devices probe in parallel, but the one-time + * socket-array allocation and /dev/hsmp registration below must run + * exactly once. Hold the socket rwsem for write across the whole + * bring-up so it cannot race a concurrent probe or remove, and so the + * probe-failure teardown drains the data plane. + */ + guard(rwsem_write)(&hsmp_sock_rwsem); + + if (!hsmp_pdev->sock) { hsmp_pdev->num_sockets = topology_max_packages(); if (!hsmp_pdev->num_sockets) { dev_err(&pdev->dev, "No CPU sockets detected\n"); return -ENODEV; } - hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, - sizeof(*hsmp_pdev->sock), - GFP_KERNEL); + hsmp_pdev->sock = kzalloc_objs(*hsmp_pdev->sock, + hsmp_pdev->num_sockets); if (!hsmp_pdev->sock) return -ENOMEM; + + hsmp_init_metric_read_locks(hsmp_pdev); + kref_init(&hsmp_acpi_sock_kref); + } else { + kref_get(&hsmp_acpi_sock_kref); } + /* + * This socket now holds a reference (kref_init on the first socket, + * kref_get afterwards). Every failure path below drops it via + * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to + * hsmp_acpi_remove(). + */ ret = init_acpi(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); return ret; } - if (!hsmp_pdev->is_probed) { - ret = hsmp_misc_register(&pdev->dev); + if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { + /* + * Register /dev/hsmp unparented. It is a singleton shared by all + * ACPI sockets and outlives all but the last of them, so + * parenting it to this socket's device would leave a dangling + * parent once that socket is unbound. + */ + ret = hsmp_misc_register(NULL); if (ret) { dev_err(&pdev->dev, "Failed to register misc device\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); return ret; } - hsmp_pdev->is_probed = true; - dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); + dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); } return 0; @@ -619,14 +765,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev) static void hsmp_acpi_remove(struct platform_device *pdev) { + struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); + /* - * We register only one misc_device even on multi-socket system. - * So, deregister should happen only once. + * Serialize the kref_put() and any release it triggers against a + * concurrent probe, and drain the data plane for the whole + * teardown: this covers the per-socket unbind, whose mailbox devres + * unmaps once we return, and the last unbind that frees the socket + * array in hsmp_acpi_sock_release(). */ - if (hsmp_pdev->is_probed) { - hsmp_misc_deregister(); - hsmp_pdev->is_probed = false; - } + guard(rwsem_write)(&hsmp_sock_rwsem); + + /* + * Clear this socket's dev so hsmp_send_message() rejects it before + * devres unmaps the mailbox. On a non-final unbind the socket array + * stays alive, so without this a later message to this index would + * reach an unmapped iomem region. + */ + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); } static struct platform_driver amd_hsmp_driver = { diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 19f82c1d3090..5e123a4ecea9 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -10,10 +10,17 @@ #include <asm/amd/hsmp.h> #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/nospec.h> +#include <linux/rwsem.h> #include <linux/semaphore.h> +#include <linux/slab.h> #include <linux/sysfs.h> +#include <linux/uaccess.h> #include "hsmp.h" @@ -41,6 +48,17 @@ static struct hsmp_plat_device hsmp_pdev; /* + * Gates the AMD HSMP data plane against socket bring-up and teardown. + * + * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads + * run concurrently. Probe and remove take it for write: probe brings sockets + * up (running the mailbox handshake via hsmp_send_message_locked()) and remove + * tears them down, both excluding and draining the data plane. + */ +DECLARE_RWSEM(hsmp_sock_rwsem); +EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); + +/* * Send a message to the HSMP port via PCI-e config space registers * or by writing to MMIO space. * @@ -117,7 +135,7 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms } if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) { - dev_err(sock->dev, "Message ID 0x%X failure : SMU tmeout (status = 0x%X)\n", + dev_err(sock->dev, "Message ID 0x%X failure : SMU timeout (status = 0x%X)\n", msg->msg_id, mbox_status); return -ETIMEDOUT; } else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) { @@ -182,28 +200,33 @@ static int validate_message(struct hsmp_message *msg) return -EINVAL; /* - * Some older HSMP SET messages are updated to add GET in the same message. - * In these messages, GET returns the current value and SET also returns - * the successfully set value. To support this GET and SET in same message - * while maintaining backward compatibility for the HSMP users, - * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. + * As the HSMP protocol evolves, newer platforms may define more + * response arguments for existing messages. Use an upper-bound + * check so that older userspace callers requesting fewer response + * words than what the current hsmp_msg_desc_table[] defines are + * still accepted, while rejecting requests that exceed the + * hardware capability. */ - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { - if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } else { - /* only HSMP_SET or HSMP_GET messages go through this strict check */ - if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } + if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) + return -EINVAL; + return 0; } -int hsmp_send_message(struct hsmp_message *msg) +/* + * Core message send. The caller must hold hsmp_sock_rwsem: the data plane + * takes it for read so many messages run concurrently, while the probe-time + * senders run under the write lock taken by probe. Holding it here serializes + * every message against socket teardown, which also holds it for write. + */ +static int hsmp_send_message_locked(struct hsmp_message *msg) { struct hsmp_socket *sock; + unsigned int sock_ind; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!msg) return -EINVAL; ret = validate_message(msg); @@ -212,7 +235,29 @@ int hsmp_send_message(struct hsmp_message *msg) if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets) return -ENODEV; - sock = &hsmp_pdev.sock[msg->sock_ind]; + + /* + * Sanitize sock_ind after the bounds check. A mispredicted branch can + * still let the CPU speculatively use msg->sock_ind as an index into + * hsmp_pdev.sock[] (Spectre v1, CVE-2017-5753), including for callers + * other than hsmp_ioctl_msg() that pass a user-derived socket index. + */ + sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + + /* + * A slot exists for every possible socket, but it is only usable once + * that socket has actually been probed. Reject messages aimed at a + * socket that was never brought up or is still in bring-up, so we never + * operate on a zero-initialized semaphore or an unmapped mailbox. A + * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and + * the semaphore are visible. + * + * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev) + * in hsmp_parse_acpi_table(). + */ + if (!smp_load_acquire(&sock->dev)) + return -ENODEV; ret = down_interruptible(&sock->hsmp_sem); if (ret < 0) @@ -224,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg) return ret; } + +int hsmp_send_message(struct hsmp_message *msg) +{ + /* + * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue + * messages from here. Take hsmp_sock_rwsem for read so messages run + * concurrently with each other but are drained and kept out while + * probe/remove hold it for write to tear a socket down. + */ + guard(rwsem_read)(&hsmp_sock_rwsem); + + return hsmp_send_message_locked(msg); +} EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) @@ -264,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value) msg.args[0] = value; msg.sock_ind = sock_ind; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -292,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg) return false; } -long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) { int __user *arguser = (int __user *)arg; struct hsmp_message msg = { 0 }; @@ -308,6 +366,19 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX) return -ENOMSG; + /* + * Sanitize the user-controlled msg_id against speculative + * execution. The bounds check above retires the out-of-range + * case with -ENOMSG, but a mispredicted branch can still let the + * CPU speculatively use msg_id as an index into + * hsmp_msg_desc_table[] (here and in validate_message() / + * is_get_msg() called downstream via hsmp_send_message()), and + * pull arbitrary kernel memory into the cache (Spectre v1, + * CVE-2017-5753). Clamp once into msg.msg_id so every downstream + * dereference sees the sanitized value. + */ + msg.msg_id = array_index_nospec(msg.msg_id, HSMP_MSG_ID_MAX); + switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { case FMODE_WRITE: /* @@ -348,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) return 0; } -ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size); + +/* + * Fetch the firmware metric (telemetry) table for the requested socket and + * copy it to the userspace buffer described by the request. + * + * The metric table size is variable across HSMP protocol versions and on + * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer + * size, which may be anything up to the size firmware reported for this + * socket's table. + */ +static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) +{ + void *kbuf __free(kvfree) = NULL; + void __user *arguser = (void __user *)arg; + struct hsmp_telemetry_data req; + struct hsmp_socket *sock; + void __user *user_buf; + size_t tbl_size; + unsigned int sock_ind; + int ret; + + /* Telemetry data is read-only; require read access on the fd. */ + if (!(fp->f_mode & FMODE_READ)) + return -EPERM; + + if (copy_from_user(&req, arguser, sizeof(req))) + return -EFAULT; + + /* + * Reserved fields must be zero so future kernels can safely + * repurpose them without breaking already-deployed userspace. + */ + if (req.reserved) + return -EINVAL; + + user_buf = u64_to_user_ptr(req.buf); + + /* + * /dev/hsmp is a singleton character device that outlives an individual + * socket unbind, so an ioctl on an already-open fd can run concurrently + * with socket teardown. Hold hsmp_sock_rwsem for read across the socket + * lookup, the checks on its metric-table state and the read itself: + * probe and remove take the same lock for write, so they cannot free the + * socket array, unmap the table or destroy the per-socket mutex while + * this runs. + * + * The lock is dropped before the copy_to_user() below. Faulting in the + * destination can block indefinitely on a userfaultfd-backed buffer, + * which would leave a socket unbind waiting for the write lock. + */ + scoped_guard(rwsem_read, &hsmp_sock_rwsem) { + if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) + return -ENODEV; + + /* + * Sanitize the user-controlled socket index against speculative + * execution. The bounds check above retires the out-of-range + * case with -ENODEV, but a mispredicted branch can still let the + * CPU speculatively use sock_ind as an index into + * hsmp_pdev.sock[] and pull arbitrary kernel memory into the + * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns + * the bounds check into a data-flow clamp so the speculative + * load is in-range too. + */ + sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + if (!sock->metric_tbl_addr) + return -ENODEV; + + tbl_size = sock->metric_tbl_size; + if (!tbl_size) + return -ENODEV; + + /* + * A request shorter than the firmware table is served with the + * leading @size bytes of the snapshot, so userspace built + * against an older table layout keeps working on firmware that + * grew the table. Asking for more than firmware provides is + * rejected rather than short-written, so a caller can never + * mistake a partial copy for a full one. + */ + if (!req.size || req.size > tbl_size) + return -EINVAL; + + /* + * The bounce buffer is overwritten in full by memcpy_fromio() + * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid + * the zeroing cost of kvzalloc() on the ~13 KB allocation done + * on every ioctl call. + */ + kbuf = kvmalloc(tbl_size, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); + } + + if (ret < 0) + return ret; + + if (copy_to_user(user_buf, kbuf, req.size)) + return -EFAULT; + + return 0; +} + +long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case HSMP_IOCTL_CMD: + return hsmp_ioctl_msg(fp, arg); + case HSMP_IOCTL_GET_TELEMETRY_DATA: + return hsmp_ioctl_get_telemetry(fp, arg); + default: + return -ENOTTY; + } +} + +/* + * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping + * and its metric_read_lock alive: probe and remove take the same lock for + * write while they bring sockets up and tear them down. + */ +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size) { struct hsmp_message msg = { 0 }; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!sock || !buf) return -EINVAL; @@ -361,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) return -ENOMEM; } - /* Do not support lseek(), also don't allow more than the size of metric table */ - if (size != sizeof(struct hsmp_metric_table)) { + if (size != sock->metric_tbl_size) { dev_err(sock->dev, "Wrong buffer size\n"); return -EINVAL; } @@ -370,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) msg.msg_id = HSMP_GET_METRIC_TABLE; msg.sock_ind = sock->sock_ind; - ret = hsmp_send_message(&msg); + /* + * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared + * metric DRAM region, which is then copied out below. Hold the + * per-socket lock across the fill-and-copy so concurrent readers of the + * same socket cannot return a torn snapshot. + */ + guard(mutex)(&sock->metric_read_lock); + + ret = hsmp_send_message_locked(&msg); if (ret) return ret; memcpy_fromio(buf, sock->metric_tbl_addr, size); return size; } + +ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +{ + guard(rwsem_read)(&hsmp_sock_rwsem); + + return hsmp_metric_tbl_read_locked(sock, buf, size); +} EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_init(&pdev->sock[i].metric_read_lock); +} +EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); + +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_destroy(&pdev->sock[i].metric_read_lock); +} +EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); + +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) +{ + struct hsmp_socket *sock; + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) { + sock = &pdev->sock[i]; + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } + sock->metric_tbl_size = 0; + } +} +EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); + int hsmp_get_tbl_dram_base(u16 sock_ind) { struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; struct hsmp_message msg = { 0 }; phys_addr_t dram_addr; + size_t tbl_size; int ret; msg.sock_ind = sock_ind; msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -403,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) dev_err(sock->dev, "Invalid DRAM address for metric table\n"); return -ENOMEM; } - sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, - sizeof(struct hsmp_metric_table)); + /* + * The ACPI socket array is shared across sockets and outlives a + * per-socket unbind, so metric_tbl_addr may hold a mapping from an + * earlier bind of this socket. Unmap it before remapping so an + * unbind/rebind cycle does not leak a metric-table mapping. This runs + * during probe before the metric sysfs attribute is exposed, so no + * reader can be using it. + */ + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } + sock->metric_tbl_size = 0; + + /* SMU returns table size from Family 1Ah Model 50h and forward */ + if (msg.args[2]) + tbl_size = msg.args[2]; + else + tbl_size = sizeof(struct hsmp_metric_table); + + sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); return -ENOMEM; } + sock->metric_tbl_size = tbl_size; + return 0; } EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP"); @@ -422,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) msg.sock_ind = sock_ind; msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (!ret) hsmp_pdev.proto_ver = msg.args[0]; @@ -441,6 +710,14 @@ int hsmp_misc_register(struct device *dev) hsmp_pdev.mdev.name = HSMP_CDEV_NAME; hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; hsmp_pdev.mdev.fops = &hsmp_fops; + /* + * The caller chooses the parent. The platform driver has a single + * device whose lifetime matches /dev/hsmp and parents it there. The + * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by + * per-socket devices that can be unbound individually and out of order, + * so parenting it to one would leave it attached to an already-removed + * device. + */ hsmp_pdev.mdev.parent = dev; hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; hsmp_pdev.mdev.mode = 0644; @@ -452,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP"); void hsmp_misc_deregister(void) { misc_deregister(&hsmp_pdev.mdev); + hsmp_pdev.mdev.this_device = NULL; } EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 0509a442eaae..8dbff16a87b1 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -15,9 +15,12 @@ #include <linux/hwmon.h> #include <linux/kconfig.h> #include <linux/miscdevice.h> +#include <linux/mutex.h> #include <linux/pci.h> +#include <linux/rwsem.h> #include <linux/semaphore.h> #include <linux/sysfs.h> +#include <linux/types.h> #define HSMP_METRICS_TABLE_NAME "metrics_bin" @@ -27,7 +30,7 @@ #define HSMP_DEVNODE_NAME "hsmp" #define ACPI_HSMP_DEVICE_HID "AMDI0097" -#define DRIVER_VERSION "2.5" +#define DRIVER_VERSION "2.6" struct hsmp_mbaddr_info { u32 base_addr; @@ -41,8 +44,12 @@ struct hsmp_socket { struct bin_attribute hsmp_attr; struct hsmp_mbaddr_info mbinfo; void __iomem *metric_tbl_addr; + /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ + size_t metric_tbl_size; void __iomem *virt_base_addr; struct semaphore hsmp_sem; + /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ + struct mutex metric_read_lock; char name[HSMP_ATTR_GRP_NAME_SIZE]; struct device *dev; u16 sock_ind; @@ -54,7 +61,6 @@ struct hsmp_plat_device { struct hsmp_socket *sock; u32 proto_ver; u16 num_sockets; - bool is_probed; }; int hsmp_cache_proto_ver(u16 sock_ind); @@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); void hsmp_misc_deregister(void); int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); #if IS_ENABLED(CONFIG_HWMON) @@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } #endif int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); + +/* + * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and + * remove take it for write to bring sockets up and tear them down. + */ +extern struct rw_semaphore hsmp_sock_rwsem; #endif /* HSMP_H */ diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c index 0cc9a742497f..c8314eee06f4 100644 --- a/drivers/platform/x86/amd/hsmp/hwmon.c +++ b/drivers/platform/x86/amd/hsmp/hwmon.c @@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, if (attr != hwmon_power_cap) return -EOPNOTSUPP; + if (val < 0) + return -EINVAL; + msg.num_args = 1; msg.args[0] = val / MICROWATT_PER_MILLIWATT; msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index e07f68575055..e9b2b809c0f5 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -13,12 +13,14 @@ #include <linux/acpi.h> #include <linux/build_bug.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/dev_printk.h> #include <linux/kconfig.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/platform_device.h> +#include <linux/rwsem.h> #include <linux/sysfs.h> #include <asm/amd/node.h> @@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev) return 0; } +/* + * The socket array is devm-managed and freed by the driver core, but the + * metric-table DRAM regions are mapped with plain ioremap() during probe and + * the per-socket mutexes need an explicit mutex_destroy(), neither of which + * devres covers. + * + * Take the data-plane rwsem for write to drain any in-flight + * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop + * the global socket pointer, all before devres frees the array. Registered as + * a devres action so it runs on both remove and probe failure. + */ +static void hsmp_pltdrv_release(void *data) +{ + guard(rwsem_write)(&hsmp_sock_rwsem); + hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); + hsmp_pdev->sock = NULL; +} + static int hsmp_pltdrv_probe(struct platform_device *pdev) { int ret; @@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (!hsmp_pdev->sock) return -ENOMEM; - ret = init_platform_device(&pdev->dev); + hsmp_init_metric_read_locks(hsmp_pdev); + + ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); + if (ret) + return ret; + + /* + * init_platform_device() runs the mailbox handshake via the probe-only + * senders, which issue messages through hsmp_send_message_locked() and + * so require hsmp_sock_rwsem held. Hold it for write, matching probe's + * role as a socket bring-up path. The lock is not held across + * devm_add_action_or_reset() above so the release action, which also + * takes it for write, does not deadlock if that registration fails. + */ + scoped_guard(rwsem_write, &hsmp_sock_rwsem) + ret = init_platform_device(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); return ret; diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c index 753d630f3283..8a33e75b71c3 100644 --- a/drivers/platform/x86/amd/pmc/mp1_stb.c +++ b/drivers/platform/x86/amd/pmc/mp1_stb.c @@ -157,7 +157,7 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp) struct amd_pmc_dev *dev = filp->f_inode->i_private; u32 fsize, num_samples, val, stb_rdptr_offset = 0; struct amd_stb_v2_data *stb_data_arr; - int ret; + int ret = 0; /* Write dummy postcode while reading the STB buffer */ ret = amd_stb_write(dev, AMD_PMC_STB_DUMMY_PC); @@ -176,22 +176,24 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp) * the enhanced dram size. Note that we land here only for the * platforms that support enhanced dram size reporting. */ - if (dump_custom_stb) - return amd_stb_handle_efr(filp); + if (dump_custom_stb) { + ret = amd_stb_handle_efr(filp); + goto out; + } /* Get the num_samples to calculate the last push location */ ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->stb_arg.s2d_msg_id, true); - /* Clear msg_port for other SMU operation */ - dev->msg_port = MSG_PORT_PMC; if (ret) { dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret); - return ret; + goto out; } fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX); stb_data_arr = kmalloc_flex(*stb_data_arr, data, fsize); - if (!stb_data_arr) - return -ENOMEM; + if (!stb_data_arr) { + ret = -ENOMEM; + goto out; + } stb_data_arr->size = fsize; @@ -214,7 +216,10 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp) filp->private_data = stb_data_arr; - return 0; +out: + /* Restore the default message port for subsequent SMU operations */ + dev->msg_port = MSG_PORT_PMC; + return ret; } static ssize_t amd_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size, @@ -289,15 +294,12 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) u32 phys_addr_low, phys_addr_hi; u64 stb_phys_addr; u32 size = 0; - int ret; + int ret = 0; if (!enable_stb) return 0; - if (amd_is_stb_supported(dev)) { - debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, - &amd_stb_debugfs_fops_v2); - } else { + if (!amd_is_stb_supported(dev)) { debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, &amd_stb_debugfs_fops); return 0; @@ -306,27 +308,52 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) /* Spill to DRAM feature uses separate SMU message port */ dev->msg_port = MSG_PORT_S2D; - amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true); - if (size != S2D_TELEMETRY_BYTES_MAX) - return -EIO; + ret = amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true); + if (ret) + goto out; + if (size != S2D_TELEMETRY_BYTES_MAX) { + ret = -EIO; + goto out; + } - /* Get DRAM size */ - ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true); - if (ret || !dev->dram_size) + /* Get DRAM size; fall back to the default if the query fails */ + if (amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true) || + !dev->dram_size) dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX; /* Get STB DRAM address */ - amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->stb_arg.s2d_msg_id, true); - amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->stb_arg.s2d_msg_id, true); + ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, + dev->stb_arg.s2d_msg_id, true); + if (ret) + goto out; + ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, + dev->stb_arg.s2d_msg_id, true); + if (ret) + goto out; stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); - - /* Clear msg_port for other SMU operation */ - dev->msg_port = MSG_PORT_PMC; + if (!stb_phys_addr) { + dev_err(dev->dev, "S2D phys addr query returned invalid address\n"); + ret = -ENXIO; + goto out; + } dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); - if (!dev->stb_virt_addr) - return -ENOMEM; + if (!dev->stb_virt_addr) { + ret = -ENOMEM; + goto out; + } - return 0; + /* + * Only expose stb_read once the buffer is mapped; otherwise a read + * faults on a NULL dev->stb_virt_addr, now that a failed STB init no + * longer aborts probe. + */ + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_stb_debugfs_fops_v2); + +out: + /* Restore the default message port for subsequent SMU operations */ + dev->msg_port = MSG_PORT_PMC; + return ret; } diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index ed285afaf9b0..09a30f333759 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -18,6 +18,7 @@ struct quirk_entry { u32 s2idle_bug_mmio; bool spurious_8042; + bool need_suspend_delay; }; static struct quirk_entry quirk_s2idle_bug = { @@ -33,6 +34,10 @@ static struct quirk_entry quirk_s2idle_spurious_8042 = { .spurious_8042 = true, }; +static struct quirk_entry quirk_s2idle_need_suspend_delay = { + .need_suspend_delay = true, +}; + static const struct dmi_system_id fwbug_list[] = { { .ident = "L14 Gen2 AMD", @@ -67,6 +72,14 @@ static const struct dmi_system_id fwbug_list[] = { } }, { + .ident = "T14 Gen2 AMD", + .driver_data = &quirk_s2idle_spurious_8042, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "20XL"), + } + }, + { .ident = "T14 Gen1 AMD", .driver_data = &quirk_s2idle_spurious_8042, .matches = { @@ -203,6 +216,44 @@ static const struct dmi_system_id fwbug_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "82XQ"), } }, + /* https://bugzilla.kernel.org/show_bug.cgi?id=221383 */ + { + .ident = "Zen3-based IdeaPad Slim and similar", + .driver_data = &quirk_s2idle_need_suspend_delay, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + /* + * Note: there are also some Zen2-based 82X* devices that + * need different quirks, they're already handled above + */ + DMI_MATCH(DMI_PRODUCT_NAME, "82X"), + } + }, + { + .ident = "Zen3+-based IdeaPad Slim and similar", + .driver_data = &quirk_s2idle_need_suspend_delay, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83K"), + } + }, + { + .ident = "IdeaPad Slim 3 15ARP10 (83MM)", + .driver_data = &quirk_s2idle_need_suspend_delay, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83MM"), + } + }, + /* https://bugzilla.kernel.org/show_bug.cgi?id=221273 */ + { + .ident = "Thinkpad L14 Gen3", + .driver_data = &quirk_s2idle_bug, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21C6"), + } + }, /* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */ { .ident = "Lenovo Yoga 6 13ALC6", @@ -347,6 +398,11 @@ void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev) amd_pmc_skip_nvme_smi_handler(dev->quirks->s2idle_bug_mmio); } +bool amd_pmc_quirk_need_suspend_delay(struct amd_pmc_dev *dev) +{ + return dev->quirks && dev->quirks->need_suspend_delay; +} + void amd_pmc_quirks_init(struct amd_pmc_dev *dev) { const struct dmi_system_id *dmi_id; diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index cae3fcafd4d7..6792aa2c6187 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -16,6 +16,7 @@ #include <linux/bits.h> #include <linux/debugfs.h> #include <linux/delay.h> +#include <linux/dmi.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/limits.h> @@ -32,6 +33,28 @@ #include "pmc.h" +static const struct amd_pmc_bit_map soc15_ip_blk_v3[] = { + {"VDDCR", BIT(0)}, + {"VDDCR_LP", BIT(1)}, + {"LSOCV", BIT(2)}, + {"DISPLAY", BIT(3)}, + {"VCN", BIT(4)}, + {"JPEG", BIT(5)}, + {"UMSCH", BIT(6)}, + {"VPE", BIT(7)}, + {"MPM", BIT(8)}, + {"NPU", BIT(9)}, + {"USB_HC0", BIT(10)}, + {"eUSB_HC0", BIT(11)}, + {"RT0_ADP_HC1", BIT(12)}, + {"RT1_ADP_HC1", BIT(13)}, + {"RT2_ADP_HC2", BIT(14)}, + {"USB4_RT0", BIT(15)}, + {"USB4_RT1", BIT(16)}, + {"USB4-RT2", BIT(17)}, + {"LAPIC", BIT(18)}, +}; + static const struct amd_pmc_bit_map soc15_ip_blk_v2[] = { {"DISPLAY", BIT(0)}, {"CPU", BIT(1)}, @@ -85,10 +108,109 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { {"VPE", BIT(21)}, }; +/* CPU info structures for different SoC variants */ +static const struct amd_pmc_cpu_info amd_pco_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MESSAGE, + .smu_arg = AMD_PMC_REGISTER_ARGUMENT, + .smu_rsp = AMD_PMC_REGISTER_RESPONSE, + .num_ips = 12, + .ips_ptr = soc15_ip_blk, + .os_hint = MSG_OS_HINT_PCO, +}; + +static const struct amd_pmc_cpu_info amd_czn_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MESSAGE, + .smu_arg = AMD_PMC_REGISTER_ARGUMENT, + .smu_rsp = AMD_PMC_REGISTER_RESPONSE, + .num_ips = 12, + .scratch_reg = AMD_PMC_SCRATCH_REG_CZN, + .ips_ptr = soc15_ip_blk, + .os_hint = MSG_OS_HINT_RN, +}; + +static const struct amd_pmc_cpu_info amd_vg_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MESSAGE, + .smu_arg = AMD_PMC_REGISTER_ARGUMENT, + .smu_rsp = AMD_PMC_REGISTER_RESPONSE, + .num_ips = 12, + .ips_ptr = soc15_ip_blk, + .os_hint = MSG_OS_HINT_RN, +}; + +static const struct amd_pmc_cpu_info amd_yc_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MESSAGE, + .smu_arg = AMD_PMC_REGISTER_ARGUMENT, + .smu_rsp = AMD_PMC_REGISTER_RESPONSE, + .num_ips = 12, + .scratch_reg = AMD_PMC_SCRATCH_REG_YC, + .ips_ptr = soc15_ip_blk, + .os_hint = MSG_OS_HINT_RN, +}; + +static const struct amd_pmc_cpu_info amd_ps_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MESSAGE, + .smu_arg = AMD_PMC_REGISTER_ARGUMENT, + .smu_rsp = AMD_PMC_REGISTER_RESPONSE, + .num_ips = 21, + .scratch_reg = AMD_PMC_SCRATCH_REG_YC, + .ips_ptr = soc15_ip_blk, + .os_hint = MSG_OS_HINT_RN, +}; + +static const struct amd_pmc_cpu_info amd_1ah_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MSG_1AH_20H, + .smu_arg = AMD_PMC_REGISTER_ARGUMENT, + .smu_rsp = AMD_PMC_REGISTER_RESPONSE, + .num_ips = ARRAY_SIZE(soc15_ip_blk), + .scratch_reg = AMD_PMC_SCRATCH_REG_1AH, + .ips_ptr = soc15_ip_blk, + .os_hint = MSG_OS_HINT_RN, +}; + +static const struct amd_pmc_cpu_info amd_1ah_m70_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MSG_1AH_20H, + .smu_arg = AMD_PMC_REGISTER_ARGUMENT, + .smu_rsp = AMD_PMC_REGISTER_RESPONSE, + .num_ips = ARRAY_SIZE(soc15_ip_blk_v2), + .scratch_reg = AMD_PMC_SCRATCH_REG_1AH, + .ips_ptr = soc15_ip_blk_v2, + .os_hint = MSG_OS_HINT_RN, +}; + +static const struct amd_pmc_cpu_info amd_1ah_m80_cpu_info = { + .smu_msg = AMD_PMC_REGISTER_MSG_1AH_80H, + .smu_arg = AMD_PMC_REGISTER_ARG_1AH_80H, + .smu_rsp = AMD_PMC_REGISTER_RSP_1AH_80H, + .num_ips = ARRAY_SIZE(soc15_ip_blk_v3), + .scratch_reg = AMD_PMC_SCRATCH_REG_1AH, + .ips_ptr = soc15_ip_blk_v3, + .os_hint = MSG_OS_HINT_RN, +}; + +static const struct pci_device_id pmc_pci_ids[] = { + { PCI_DEVICE_DATA(AMD, CPU_ID_PCO, &amd_pco_cpu_info) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_CZN, &amd_czn_cpu_info) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_VG, &amd_vg_cpu_info) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_YC, &amd_yc_cpu_info) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_CB, &amd_yc_cpu_info) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_ps_cpu_info) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_SP, NULL) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_SHP, NULL) }, + { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, NULL) }, + { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, NULL) }, + { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_1ah_m80_cpu_info) }, + { } +}; + static bool disable_workarounds; module_param(disable_workarounds, bool, 0644); MODULE_PARM_DESC(disable_workarounds, "Disable workarounds for platform bugs"); +static int delay_suspend = -1; +module_param(delay_suspend, int, 0644); +MODULE_PARM_DESC(delay_suspend, + "Delays s2idle by 2.5 seconds to work around buggy ECs, often causing keyboard issues after suspend. 0: don't delay, 1: do delay, -1 (default): let amd_pmc decide. If you need this please report this to: platform-driver-x86@vger.kernel.org"); + static struct amd_pmc_dev pmc; static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset) @@ -101,35 +223,40 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 iowrite32(val, dev->regbase + reg_offset); } -static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev) +static int amd_pmc_set_cpu_info(struct amd_pmc_dev *dev, struct pci_dev *rdev) { + const struct pci_device_id *id; + + id = pci_match_id(pmc_pci_ids, rdev); + if (!id) + return -ENODEV; + + dev->cpu_id = rdev->device; + + if (id->driver_data) { + dev->cpu_info = (const struct amd_pmc_cpu_info *)id->driver_data; + return 0; + } + + /* Special case: 1Ah M20H/M60H needs x86_model detection */ switch (dev->cpu_id) { - case AMD_CPU_ID_PCO: - case AMD_CPU_ID_RN: - case AMD_CPU_ID_VG: - case AMD_CPU_ID_YC: - case AMD_CPU_ID_CB: - dev->num_ips = 12; - dev->ips_ptr = soc15_ip_blk; - dev->smu_msg = 0x538; - break; - case AMD_CPU_ID_PS: - dev->num_ips = 21; - dev->ips_ptr = soc15_ip_blk; - dev->smu_msg = 0x538; - break; case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: - if (boot_cpu_data.x86_model == 0x70) { - dev->num_ips = ARRAY_SIZE(soc15_ip_blk_v2); - dev->ips_ptr = soc15_ip_blk_v2; - } else { - dev->num_ips = ARRAY_SIZE(soc15_ip_blk); - dev->ips_ptr = soc15_ip_blk; - } - dev->smu_msg = 0x938; + if (boot_cpu_data.x86_model == 0x70) + dev->cpu_info = &amd_1ah_m70_cpu_info; + else + dev->cpu_info = &amd_1ah_cpu_info; break; + case AMD_CPU_ID_SP: + case AMD_CPU_ID_SHP: + dev_warn_once(dev->dev, "S0i3 is not supported on this hardware\n"); + return -ENODEV; + default: + dev_err(dev->dev, "Unknown CPU ID: 0x%x\n", dev->cpu_id); + return -ENODEV; } + + return 0; } static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) @@ -296,9 +423,9 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) table.timeto_resume_to_os_lastcapture); seq_puts(s, "\n=== Active time (in us) ===\n"); - for (idx = 0 ; idx < dev->num_ips ; idx++) { - if (dev->ips_ptr[idx].bit_mask & dev->active_ips) - seq_printf(s, "%-8s : %lld\n", dev->ips_ptr[idx].name, + for (idx = 0 ; idx < dev->cpu_info->num_ips ; idx++) { + if (dev->cpu_info->ips_ptr[idx].bit_mask & dev->active_ips) + seq_printf(s, "%-8s : %lld\n", dev->cpu_info->ips_ptr[idx].name, table.timecondition_notmet_lastcapture[idx]); } @@ -347,32 +474,22 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev, u32 val; int rc; - switch (pdev->cpu_id) { - case AMD_CPU_ID_CZN: - /* we haven't yet read SMU version */ + /* we haven't yet read SMU version */ + if (pdev->cpu_id == AMD_CPU_ID_CZN) { if (!pdev->major) { rc = amd_pmc_get_smu_version(pdev); if (rc) return rc; } - if (pdev->major > 56 || (pdev->major >= 55 && pdev->minor >= 37)) - val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_CZN); - else + if (!(pdev->major > 56 || (pdev->major >= 55 && pdev->minor >= 37))) return -EINVAL; - break; - case AMD_CPU_ID_YC: - case AMD_CPU_ID_CB: - case AMD_CPU_ID_PS: - val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_YC); - break; - case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: - case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: - val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_1AH); - break; - default: - return -EINVAL; } + if (!pdev->cpu_info->scratch_reg) + return -EINVAL; + + val = amd_pmc_reg_read(pdev, pdev->cpu_info->scratch_reg); + if (dev) pm_pr_dbg("SMU idlemask s0i3: 0x%x\n", val); @@ -425,9 +542,9 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) argument = dev->stb_arg.arg; response = dev->stb_arg.resp; } else { - message = dev->smu_msg; - argument = AMD_PMC_REGISTER_ARGUMENT; - response = AMD_PMC_REGISTER_RESPONSE; + message = dev->cpu_info->smu_msg; + argument = dev->cpu_info->smu_arg; + response = dev->cpu_info->smu_rsp; } value = amd_pmc_reg_read(dev, response); @@ -452,9 +569,9 @@ int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool r argument = dev->stb_arg.arg; response = dev->stb_arg.resp; } else { - message = dev->smu_msg; - argument = AMD_PMC_REGISTER_ARGUMENT; - response = AMD_PMC_REGISTER_RESPONSE; + message = dev->cpu_info->smu_msg; + argument = dev->cpu_info->smu_arg; + response = dev->cpu_info->smu_rsp; } /* Wait until we get a valid response */ @@ -512,23 +629,6 @@ int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool r return rc; } -static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) -{ - switch (dev->cpu_id) { - case AMD_CPU_ID_PCO: - return MSG_OS_HINT_PCO; - case AMD_CPU_ID_RN: - case AMD_CPU_ID_VG: - case AMD_CPU_ID_YC: - case AMD_CPU_ID_CB: - case AMD_CPU_ID_PS: - case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: - case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: - return MSG_OS_HINT_RN; - } - return -EINVAL; -} - static int amd_pmc_wa_irq1(struct amd_pmc_dev *pdev) { struct device *d; @@ -567,9 +667,12 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) rtc_device = rtc_class_open("rtc0"); if (!rtc_device) return 0; - rc = rtc_read_alarm(rtc_device, &alarm); - if (rc) - return rc; + rc = rtc_read_next_alarm(rtc_device, &alarm); + if (rc) { + if (rc == -ENOENT) + dev_dbg(pdev->dev, "no alarm pending\n"); + return rc == -ENOENT ? 0 : rc; + } if (!alarm.enabled) { dev_dbg(pdev->dev, "alarm not enabled\n"); return 0; @@ -598,13 +701,81 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) return rc; } +static bool amd_pmc_intermediate_wakeup_need_delay(struct amd_pmc_dev *pdev) +{ + /* + * Starting a new HW sleep cycle right after waking from one + * can cause electrical problems triggering the over voltage protection. + * That is avoided by delaying the next suspend a bit, see also + * https://lore.kernel.org/all/20250414162446.3853194-1-superm1@kernel.org/ + */ + struct smu_metrics table; + + return get_metrics_table(pdev, &table) == 0 && table.s0i3_last_entry_status; +} + +static bool amd_pmc_want_suspend_delay(struct amd_pmc_dev *pdev) +{ + /* + * intermediate_wakeup implies that the machine didn't get to deepest sleep + * state before - otherwise this function isn't called in amd_pmc_s2idle_check() + * because amd_pmc_intermediate_wakeup_need_delay() returns true first. + * On some IdeaPads that happens when charging, because the EC seems + * to send lots of messages then that wake the machine. + * + * But even in that case, the sleep here is necessary (on those IdeaPads), + * otherwise they wake up completely (resume) after a few seconds. + * So this variable is only used to avoid spamming dmesg on each + * intermediate wakeup. + */ + bool intermediate_wakeup = !pdev->is_first_check_after_suspend; + + /* + * Some Lenovo Laptops (like different IdeaPad 3 Slims) need some + * me-time before sleeping or they get uncooperative after waking + * up and don't send events for keyboard and lid switch anymore. + * + * Unfortunately this doesn't entirely fix the problem: It can still + * happen when resuming with a timer (wakealarm), but at least the + * more common usecases (wakeup by opening lid or pressing a key) + * work fine with this workaround. + * + * See https://bugzilla.kernel.org/show_bug.cgi?id=221383 + */ + if (amd_pmc_quirk_need_suspend_delay(pdev)) { + /* + * delay_suspend=1 force-enables this, otherwise it can be + * disabled with disable_workarounds or delay_suspend=0 + */ + if (delay_suspend == 1 || (delay_suspend == -1 && !disable_workarounds)) { + if (!intermediate_wakeup) + dev_info(pdev->dev, "Delaying suspend by 2.5s to avoid platform bug\n"); + return true; + } + if (!intermediate_wakeup) + dev_info(pdev->dev, "Not delaying suspend because of module parameter, even though your device is assumed to need it!\n"); + } else if (delay_suspend == 1) { + if (!intermediate_wakeup) + dev_info(pdev->dev, "Delaying suspend by 2.5s because delay_suspend=1. If this solves problems on your machine, please report this whole line to: platform-driver-x86@vger.kernel.org so it can be automatically detected as affected in the future. System Vendor: \"%s\" Product Name: \"%s\" Product Family: \"%s\" Board Vendor: \"%s\" Board Name: \"%s\"\n", + dmi_get_system_info(DMI_SYS_VENDOR) ?: "(Unknown)", + dmi_get_system_info(DMI_PRODUCT_NAME) ?: "(Unknown)", + dmi_get_system_info(DMI_PRODUCT_FAMILY) ?: "(Unknown)", + dmi_get_system_info(DMI_BOARD_VENDOR) ?: "(Unknown)", + dmi_get_system_info(DMI_BOARD_NAME) ?: "(Unknown)"); + return true; + } + return false; +} + static void amd_pmc_s2idle_prepare(void) { struct amd_pmc_dev *pdev = &pmc; int rc; - u8 msg; u32 arg = 1; + /* Reset this variable because this is a fresh suspend */ + pdev->is_first_check_after_suspend = true; + /* Reset and Start SMU logging - to monitor the s0i3 stats */ amd_pmc_setup_smu_logging(pdev); @@ -617,8 +788,7 @@ static void amd_pmc_s2idle_prepare(void) } } - msg = amd_pmc_get_os_hint(pdev); - rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, false); + rc = amd_pmc_send_cmd(pdev, arg, NULL, pdev->cpu_info->os_hint, false); if (rc) { dev_err(pdev->dev, "suspend failed: %d\n", rc); return; @@ -632,11 +802,10 @@ static void amd_pmc_s2idle_prepare(void) static void amd_pmc_s2idle_check(void) { struct amd_pmc_dev *pdev = &pmc; - struct smu_metrics table; int rc; - /* Avoid triggering OVP */ - if (!get_metrics_table(pdev, &table) && table.s0i3_last_entry_status) + if (amd_pmc_intermediate_wakeup_need_delay(pdev) || + amd_pmc_want_suspend_delay(pdev)) msleep(2500); /* Dump the IdleMask before we add to the STB */ @@ -645,6 +814,9 @@ static void amd_pmc_s2idle_check(void) rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_CHECK); if (rc) dev_err(pdev->dev, "error writing to STB: %d\n", rc); + + /* remember that first check after suspend is done (until next prepare) */ + pdev->is_first_check_after_suspend = false; } static int amd_pmc_dump_data(struct amd_pmc_dev *pdev) @@ -659,10 +831,8 @@ static void amd_pmc_s2idle_restore(void) { struct amd_pmc_dev *pdev = &pmc; int rc; - u8 msg; - msg = amd_pmc_get_os_hint(pdev); - rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, false); + rc = amd_pmc_send_cmd(pdev, 0, NULL, pdev->cpu_info->os_hint, false); if (rc) dev_err(pdev->dev, "resume failed: %d\n", rc); @@ -709,22 +879,6 @@ static const struct dev_pm_ops amd_pmc_pm = { .suspend = amd_pmc_suspend_handler, }; -static const struct pci_device_id pmc_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CB) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_YC) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RV) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SP) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SHP) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_VG) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) }, - { } -}; - static int amd_pmc_probe(struct platform_device *pdev) { struct amd_pmc_dev *dev = &pmc; @@ -736,17 +890,14 @@ static int amd_pmc_probe(struct platform_device *pdev) dev->dev = &pdev->dev; rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); - if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) { + if (!rdev) { err = -ENODEV; goto err_pci_dev_put; } - dev->cpu_id = rdev->device; - if (dev->cpu_id == AMD_CPU_ID_SP || dev->cpu_id == AMD_CPU_ID_SHP) { - dev_warn_once(dev->dev, "S0i3 is not supported on this hardware\n"); - err = -ENODEV; + err = amd_pmc_set_cpu_info(dev, rdev); + if (err) goto err_pci_dev_put; - } dev->rdev = rdev; err = amd_smn_read(0, AMD_PMC_BASE_ADDR_LO, &val); @@ -778,9 +929,6 @@ static int amd_pmc_probe(struct platform_device *pdev) if (err) goto err_pci_dev_put; - /* Get num of IP blocks within the SoC */ - amd_pmc_get_ip_info(dev); - platform_set_drvdata(pdev, dev); if (IS_ENABLED(CONFIG_SUSPEND)) { err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops); @@ -791,9 +939,16 @@ static int amd_pmc_probe(struct platform_device *pdev) } amd_pmc_dbgfs_register(dev); + + /* + * STB is an optional debug facility (enable_stb); a failure to set it + * up must not stop the rest of the driver - most importantly the s0i3 + * LPS0 handler - from working, so treat it as non-fatal. + */ err = amd_stb_s2d_init(dev); if (err) - goto err_pci_dev_put; + dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n", + err); if (IS_ENABLED(CONFIG_AMD_MP2_STB)) amd_mp2_stb_init(dev); @@ -825,6 +980,7 @@ static const struct acpi_device_id amd_pmc_acpi_ids[] = { {"AMDI0009", 0}, {"AMDI000A", 0}, {"AMDI000B", 0}, + {"AMDI000C", 0}, {"AMD0004", 0}, {"AMD0005", 0}, { } diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h index fe3f53eb5955..6973a639d7e3 100644 --- a/drivers/platform/x86/amd/pmc/pmc.h +++ b/drivers/platform/x86/amd/pmc/pmc.h @@ -17,6 +17,15 @@ /* SMU communication registers */ #define AMD_PMC_REGISTER_RESPONSE 0x980 #define AMD_PMC_REGISTER_ARGUMENT 0x9BC +#define AMD_PMC_REGISTER_MESSAGE 0x538 + +/* SMU communication registers for 1Ah 20h SoC */ +#define AMD_PMC_REGISTER_MSG_1AH_20H 0x938 + +/* SMU communication registers for 1Ah 80h SoC */ +#define AMD_PMC_REGISTER_MSG_1AH_80H 0xA10 +#define AMD_PMC_REGISTER_ARG_1AH_80H 0xA18 +#define AMD_PMC_REGISTER_RSP_1AH_80H 0xA14 /* PMC Scratch Registers */ #define AMD_PMC_SCRATCH_REG_CZN 0x94 @@ -90,6 +99,22 @@ struct stb_arg { u32 resp; }; +struct amd_pmc_bit_map { + const char *name; + u32 bit_mask; +}; + +/* SoC-specific information */ +struct amd_pmc_cpu_info { + u32 smu_msg; + u32 smu_arg; + u32 smu_rsp; + u32 num_ips; + u32 scratch_reg; + const struct amd_pmc_bit_map *ips_ptr; + u8 os_hint; +}; + struct amd_pmc_dev { void __iomem *regbase; void __iomem *smu_virt_addr; @@ -99,9 +124,6 @@ struct amd_pmc_dev { u32 cpu_id; u32 dram_size; u32 active_ips; - const struct amd_pmc_bit_map *ips_ptr; - u32 num_ips; - u32 smu_msg; /* SMU version information */ u8 smu_program; u8 major; @@ -114,13 +136,10 @@ struct amd_pmc_dev { struct dentry *dbgfs_dir; struct quirk_entry *quirks; bool disable_8042_wakeup; + bool is_first_check_after_suspend; struct amd_mp2_dev *mp2; struct stb_arg stb_arg; -}; - -struct amd_pmc_bit_map { - const char *name; - u32 bit_mask; + const struct amd_pmc_cpu_info *cpu_info; }; struct smu_metrics { @@ -147,24 +166,35 @@ enum amd_pmc_def { }; void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev); +bool amd_pmc_quirk_need_suspend_delay(struct amd_pmc_dev *dev); void amd_pmc_quirks_init(struct amd_pmc_dev *dev); void amd_mp2_stb_init(struct amd_pmc_dev *dev); void amd_mp2_stb_deinit(struct amd_pmc_dev *dev); -/* List of supported CPU ids */ -#define AMD_CPU_ID_RV 0x15D0 -#define AMD_CPU_ID_RN 0x1630 -#define AMD_CPU_ID_PCO AMD_CPU_ID_RV -#define AMD_CPU_ID_CZN AMD_CPU_ID_RN -#define AMD_CPU_ID_VG 0x1645 -#define AMD_CPU_ID_YC 0x14B5 -#define AMD_CPU_ID_CB 0x14D8 -#define AMD_CPU_ID_PS 0x14E8 -#define AMD_CPU_ID_SP 0x14A4 -#define AMD_CPU_ID_SHP 0x153A -#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 -#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 -#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c +/* List of supported CPU/device IDs */ +#define PCI_DEVICE_ID_AMD_CPU_ID_PCO 0x15D0 +#define PCI_DEVICE_ID_AMD_CPU_ID_CZN 0x1630 +#define PCI_DEVICE_ID_AMD_CPU_ID_VG 0x1645 +#define PCI_DEVICE_ID_AMD_CPU_ID_YC 0x14B5 +#define PCI_DEVICE_ID_AMD_CPU_ID_CB 0x14D8 +#define PCI_DEVICE_ID_AMD_CPU_ID_PS 0x14E8 +#define PCI_DEVICE_ID_AMD_CPU_ID_SP 0x14A4 +#define PCI_DEVICE_ID_AMD_CPU_ID_SHP 0x153A + +/* Backward compatibility aliases */ +#define AMD_CPU_ID_PCO PCI_DEVICE_ID_AMD_CPU_ID_PCO +#define AMD_CPU_ID_CZN PCI_DEVICE_ID_AMD_CPU_ID_CZN +#define AMD_CPU_ID_VG PCI_DEVICE_ID_AMD_CPU_ID_VG +#define AMD_CPU_ID_YC PCI_DEVICE_ID_AMD_CPU_ID_YC +#define AMD_CPU_ID_CB PCI_DEVICE_ID_AMD_CPU_ID_CB +#define AMD_CPU_ID_PS PCI_DEVICE_ID_AMD_CPU_ID_PS +#define AMD_CPU_ID_SP PCI_DEVICE_ID_AMD_CPU_ID_SP +#define AMD_CPU_ID_SHP PCI_DEVICE_ID_AMD_CPU_ID_SHP + +#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 +#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 +#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b +#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c int amd_stb_s2d_init(struct amd_pmc_dev *dev); int amd_stb_read(struct amd_pmc_dev *dev, u32 *buf); diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig index 25b8f7ae3abd..ad4faf18de47 100644 --- a/drivers/platform/x86/amd/pmf/Kconfig +++ b/drivers/platform/x86/amd/pmf/Kconfig @@ -30,3 +30,13 @@ config AMD_PMF_DEBUG in the PMF config store. Say Y here to enable more debug logs and Say N here if you are not sure. + +config AMD_PMF_UTIL_SUPPORT + bool "AMD PMF Util layer support" + depends on AMD_PMF + help + Enabling this option provides a character device for userspace to capture + PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic + Power Slider AC/DC) along with PMF metrics from the AMD PMF driver. + + Say Y here to enable it and Say N here if you are not sure. diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile index 5978464e0eb7..8cac51182433 100644 --- a/drivers/platform/x86/amd/pmf/Makefile +++ b/drivers/platform/x86/amd/pmf/Makefile @@ -7,4 +7,6 @@ obj-$(CONFIG_AMD_PMF) += amd-pmf.o amd-pmf-y := core.o acpi.o sps.o \ auto-mode.o cnqf.o \ - tee-if.o spc.o + tee-if.o spc.o metrics.o +# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled +amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index b9e5a2cf3aae..b4eae65e675b 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -11,13 +11,13 @@ #include <linux/array_size.h> #include <linux/cleanup.h> #include <linux/debugfs.h> +#include <linux/dev_printk.h> #include <linux/iopoll.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/pci.h> #include <linux/platform_device.h> #include <linux/power_supply.h> -#include <linux/string.h> #include <asm/amd/node.h> #include "pmf.h" @@ -26,6 +26,13 @@ #define AMD_PMF_REGISTER_RESPONSE 0xA78 #define AMD_PMF_REGISTER_ARGUMENT 0xA58 +/* PMF-SMU communication registers for 1AH_M80H */ +#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04 +#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08 +#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C +#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC +#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0 + /* Base address of SMU for mapping physical address to virtual address */ #define AMD_PMF_MAPPING_SIZE 0x01000 #define AMD_PMF_BASE_ADDR_OFFSET 0x10000 @@ -48,7 +55,7 @@ #define DELAY_MAX_US 3000 /* override Metrics Table sample size time (in ms) */ -static int metrics_table_loop_ms = 1000; +int metrics_table_loop_ms = 1000; module_param(metrics_table_loop_ms, int, 0644); MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)"); @@ -61,7 +68,15 @@ static bool smart_pc_support = true; module_param(smart_pc_support, bool, 0444); MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); -static struct device *pmf_device; +static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev) +{ + switch (pdev->cpu_id) { + case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: + return true; + default: + return false; + } +} static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) { @@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void) return POWER_SOURCE_DC; } -static void amd_pmf_get_metrics(struct work_struct *work) -{ - struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); - ktime_t time_elapsed_ms; - int socket_power; - - guard(mutex)(&dev->update_mutex); - - /* Transfer table contents */ - memset(dev->buf, 0, sizeof(dev->m_table)); - amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); - memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); - - time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; - /* Calculate the avg SoC power consumption */ - socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; - - if (dev->amt_enabled) { - /* Apply the Auto Mode transition */ - amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); - } - - if (dev->cnqf_enabled) { - /* Apply the CnQF transition */ - amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); - } - - dev->start_time = ktime_to_ms(ktime_get()); - schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); -} - static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) { return ioread32(dev->regbase + reg_offset); @@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) { u32 value; - value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE); + value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg); dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value); - value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); + value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); + if (amd_pmf_supports_accumulator_metrics(dev)) { + value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value); + value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value); + } - value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE); + value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); } @@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 guard(mutex)(&dev->lock); /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, val, val != 0, PMF_MSG_DELAY_MIN_US, PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 } /* Write zero to response register */ - amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0); + amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0); /* Write argument into argument register */ - amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg); + amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg); /* Write message ID to message ID register */ - amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message); + amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message); /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, val, val != 0, PMF_MSG_DELAY_MIN_US, PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 if (get) { /* PMFW may take longer time to return back the data */ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); - *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); + *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); + if (amd_pmf_supports_accumulator_metrics(dev) && + message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) { + dev->dram_addr.hi = amd_pmf_reg_read(dev, + dev->smu_regs->arg_reg[1]); + dev->dram_addr.size = amd_pmf_reg_read(dev, + dev->smu_regs->arg_reg[2]); + } } break; case AMD_PMF_RESULT_CMD_REJECT_BUSY: @@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 return rc; } -static const struct pci_device_id pmf_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) }, - { } +/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */ +static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = { + .msg_reg = AMD_PMF_REGISTER_MESSAGE, + .resp_reg = AMD_PMF_REGISTER_RESPONSE, + .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 }, }; -int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) -{ - u64 phys_addr; - u32 hi, low; - - /* Get Metrics Table Address */ - if (alloc_buffer) { - switch (dev->cpu_id) { - case AMD_CPU_ID_PS: - case AMD_CPU_ID_RMB: - dev->mtable_size = sizeof(dev->m_table); - break; - case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: - case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: - dev->mtable_size = sizeof(dev->m_table_v2); - break; - default: - dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); - } - - dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); - if (!dev->buf) - return -ENOMEM; - } - - phys_addr = virt_to_phys(dev->buf); - hi = phys_addr >> 32; - low = phys_addr & GENMASK(31, 0); - - amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); - amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); - - return 0; -} - -int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) -{ - int ret; - - INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); - - ret = amd_pmf_set_dram_addr(dev, true); - if (ret) - return ret; - - /* - * Start collecting the metrics data after a small delay - * or else, we might end up getting stale values from PMFW. - */ - schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); - - return 0; -} - -static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) -{ - switch (pdev->cpu_id) { - case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: - case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: - return 0; - default: - return -EOPNOTSUPP; - } -} - -static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) -{ - int ret, i; - - guard(mutex)(&dev->metrics_mutex); - - ret = is_npu_metrics_supported(dev); - if (ret) - return ret; - - ret = amd_pmf_set_dram_addr(dev, true); - if (ret) - return ret; - - memset(dev->buf, 0, dev->mtable_size); - - /* Send SMU command to get NPU metrics */ - ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); - if (ret) { - dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); - return ret; - } - - memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); - - data->npuclk_freq = dev->m_table_v2.npuclk_freq; - for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) - data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; - data->npu_power = dev->m_table_v2.npu_power; - data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; - data->npu_reads = dev->m_table_v2.npu_reads; - data->npu_writes = dev->m_table_v2.npu_writes; - - return 0; -} - -int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) -{ - struct amd_pmf_dev *pdev; - - if (!info) - return -EINVAL; - - if (!pmf_device) - return -ENODEV; - - pdev = dev_get_drvdata(pmf_device); - if (!pdev) - return -ENODEV; +/* 1AH_M80H uses an extended mailbox with three argument registers */ +static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = { + .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2, + .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2, + .arg_reg = { + AMD_PMF_REGISTER_ARGUMENT0_V2, + AMD_PMF_REGISTER_ARGUMENT1_V2, + AMD_PMF_REGISTER_ARGUMENT2_V2, + }, +}; - return amd_pmf_get_smu_metrics(pdev, info); -} -EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); +static const struct pci_device_id pmf_pci_ids[] = { + { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) }, + { } +}; static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) { @@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev) } } +static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev) +{ + const struct pci_device_id *id; + + id = pci_match_id(pmf_pci_ids, rdev); + if (!id) + return -ENODEV; + + pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data; + + return 0; +} + static const struct acpi_device_id amd_pmf_acpi_ids[] = { {"AMDI0100", 0x100}, {"AMDI0102", 0}, @@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { {"AMDI0105", 0}, {"AMDI0107", 0}, {"AMDI0108", 0}, + {"AMDI0109", 0}, { } }; MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); @@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev) if (err) return err; + /* Populate smu_regs with SoC-specific SMU mailbox register offsets */ + err = amd_pmf_get_smu_mb_offset(dev, rdev); + if (err) + return err; + + if (amd_pmf_supports_accumulator_metrics(dev)) { + err = amd_pmf_get_tbl_dram_addr(dev); + if (err) + return err; + } + apmf_acpi_init(dev); platform_set_drvdata(pdev, dev); amd_pmf_dbgfs_register(dev); @@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev) if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); - pmf_device = dev->dev; + amd_pmf_set_device(dev->dev); + + err = amd_pmf_cdev_register(dev); + if (err) + dev_warn(dev->dev, "failed to register util interface: %d\n", err); dev_info(dev->dev, "registered PMF device successfully\n"); @@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev) { struct amd_pmf_dev *dev = platform_get_drvdata(pdev); + amd_pmf_cdev_unregister(); amd_pmf_deinit_features(dev); if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD); diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c new file mode 100644 index 000000000000..5c0e368ec8df --- /dev/null +++ b/drivers/platform/x86/amd/pmf/metrics.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD Platform Management Framework Driver - Metrics Support + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> + * Patil Rajesh Reddy <Patil.Reddy@amd.com> + */ + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/device.h> +#include <linux/device/devres.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <linux/limits.h> +#include <linux/math64.h> +#include <linux/minmax.h> +#include <linux/string.h> +#include <linux/units.h> +#include <linux/wordpart.h> +#include <linux/workqueue.h> + +#include "pmf.h" + +static struct device *pmf_device; + +static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc) +{ + u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT; + + return min_t(u16, val, U16_MAX); +} + +static u16 amd_pmf_q10_acc_to_int(u64 avg_acc) +{ + u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024); + + return min_t(u16, val, U16_MAX); +} + +static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter, + u64 curr_value, u64 prev_value) +{ + u32 counter_diff; + u64 val_diff; + + /* Check for counter reset */ + if (curr_counter <= prev_counter) + return 0; + + counter_diff = curr_counter - prev_counter; + + if (curr_value < prev_value) + return 0; + + val_diff = curr_value - prev_value; + + return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff); +} + +int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev) +{ + int ret; + + ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD, + ARG_NONE, &dev->dram_addr.lo); + if (ret) { + dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret); + return ret; + } + + dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo; + dev->mtable_size = dev->dram_addr.size; + + if (dev->mtable_size != sizeof(dev->mtable_v3)) { + dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n", + dev->dram_addr.size, sizeof(dev->mtable_v3)); + return -EINVAL; + } + + dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size); + if (!dev->metrics_table_virt) { + dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n"); + dev->metrics_table_phys = 0; + return -ENOMEM; + } + + return 0; +} + +static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev) +{ + int ret; + + if (!dev->metrics_table_virt) { + dev_err(dev->dev, "Metrics DRAM not mapped\n"); + return -EINVAL; + } + + /* Send command to PMFW to update metrics table log sample */ + ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL); + if (ret) { + dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret); + return ret; + } + + return 0; +} + +static void amd_pmf_get_metrics(struct work_struct *work) +{ + struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); + ktime_t time_elapsed_ms; + int socket_power; + + guard(mutex)(&dev->update_mutex); + + /* Transfer table contents */ + memset(dev->buf, 0, sizeof(dev->m_table)); + amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); + memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); + + time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; + /* Calculate the avg SoC power consumption */ + socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; + + if (dev->amt_enabled) { + /* Apply the Auto Mode transition */ + amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); + } + + if (dev->cnqf_enabled) { + /* Apply the CnQF transition */ + amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); + } + + dev->start_time = ktime_to_ms(ktime_get()); + schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); +} + +int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) +{ + u64 phys_addr; + u32 hi, low; + + /* Get Metrics Table Address */ + if (alloc_buffer) { + switch (dev->cpu_id) { + case AMD_CPU_ID_PS: + case AMD_CPU_ID_RMB: + dev->mtable_size = sizeof(dev->m_table); + break; + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: + dev->mtable_size = sizeof(dev->m_table_v2); + break; + default: + dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id); + } + + dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); + if (!dev->buf) + return -ENOMEM; + } + + phys_addr = virt_to_phys(dev->buf); + hi = upper_32_bits(phys_addr); + low = lower_32_bits(phys_addr); + + amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); + amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); + + return 0; +} + +int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) +{ + int ret; + + INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); + + ret = amd_pmf_set_dram_addr(dev, true); + if (ret) + return ret; + + /* + * Start collecting the metrics data after a small delay + * or else, we might end up getting stale values from PMFW. + */ + schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); + + return 0; +} + +void amd_pmf_set_device(struct device *p_device) +{ + pmf_device = p_device; +} + +static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) +{ + switch (pdev->cpu_id) { + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: + return 0; + default: + return -EOPNOTSUPP; + } +} + +static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev, + struct amd_pmf_npu_metrics *data) +{ + struct amd_pmf_metrics_iod *curr, *prev; + u64 val; + int i; + + curr = &dev->mtable_v3.iod; + prev = &dev->prev_metrics.iod; + + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npu_temp_acc, prev->npu_temp_acc); + data->npu_temp = amd_pmf_q10_acc_to_int(val); + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npu_power_acc, prev->npu_power_acc); + data->npu_power = amd_pmf_q10_acc_to_mW(val); + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npuhclk_freq_eff_acc, + prev->npuhclk_freq_eff_acc); + data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val); + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->aieclk_freq_eff_acc, + prev->aieclk_freq_eff_acc); + data->npuclk_freq = amd_pmf_q10_acc_to_int(val); + + for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) { + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npu_busy_acc[i], + prev->npu_busy_acc[i]); + data->npu_busy[i] = amd_pmf_q10_acc_to_int(val); + } +} + +static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) +{ + int ret, i; + + guard(mutex)(&dev->metrics_mutex); + + ret = is_npu_metrics_supported(dev); + if (ret) + return ret; + + memset(data, 0, sizeof(*data)); + + switch (dev->cpu_id) { + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: + ret = amd_pmf_set_dram_addr(dev, true); + if (ret) + return ret; + + memset(dev->buf, 0, dev->mtable_size); + + /* Send SMU command to get NPU metrics */ + ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); + if (ret) { + dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); + return ret; + } + + memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); + + data->npuclk_freq = dev->m_table_v2.npuclk_freq; + for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) + data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; + data->npu_power = dev->m_table_v2.npu_power; + data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; + data->npu_reads = dev->m_table_v2.npu_reads; + data->npu_writes = dev->m_table_v2.npu_writes; + break; + case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: + ret = amd_pmf_get_metrics_table_log_sample(dev); + if (ret) + return ret; + + memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3)); + + /* + * Ignore the first sample, as previous metrics is uninitialized (zero) + * Metrics are calculated as: + * metrics = current_metrics - previous_metrics + * Skipping the initial sample ensures accurate delta calculations. + */ + if (!dev->npu_metrics_have_prev) { + memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); + dev->npu_metrics_have_prev = true; + return 0; + } + + amd_pmf_calculate_acc_npu_metrics(dev, data); + memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); + break; + } + + return 0; +} + +int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) +{ + struct amd_pmf_dev *pdev; + + if (!info) + return -EINVAL; + + if (!pmf_device) + return -ENODEV; + + pdev = dev_get_drvdata(pmf_device); + if (!pdev) + return -ENODEV; + + return amd_pmf_get_smu_metrics(pdev, info); +} +EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 69fef7448744..07ec00684233 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -14,10 +14,13 @@ #include <linux/acpi.h> #include <linux/amd-pmf-io.h> #include <linux/circ_buf.h> +#include <linux/compiler_attributes.h> +#include <linux/compiler_types.h> #include <linux/input.h> #include <linux/mutex_types.h> #include <linux/platform_device.h> #include <linux/platform_profile.h> +#include <linux/types.h> #define POLICY_BUF_MAX_SZ 0x4b000 #define POLICY_SIGN_COOKIE 0x31535024 @@ -28,6 +31,11 @@ #define AMD_CPU_ID_PS 0x14e8 #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 +#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b + +/* Aliases required by PCI_DEVICE_DATA() macro naming convention */ +#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB +#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS struct cookie_header { u32 sign; @@ -70,6 +78,10 @@ struct cookie_header { #define SET_PMF_PPT 0x25 #define SET_PMF_PPT_APU_ONLY 0x26 +/* Message IDs for 1AH_M80H platform */ +#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E +#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F + /* OS slider update notification */ #define DC_BEST_PERF 0 #define DC_BETTER_PERF 1 @@ -130,6 +142,14 @@ struct cookie_header { #define GET_CMD true #define METRICS_TABLE_ID 7 +#define BIOS_OUTPUT_MAX 10 + +extern int metrics_table_loop_ms; + +#define AMD_PMF_METRIC_CORE_TYPE_MAX 4 +#define AMD_PMF_METRIC_CCX_MAX 4 +#define AMD_PMF_NUM_CLK_DPM_LEVELS 8 +#define AMD_PMF_NUM_MAX_CORES 12 typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); @@ -242,6 +262,199 @@ struct apmf_fan_idx { u32 fan_ctl_idx; } __packed; +struct amd_pmf_metrics_iod { + u32 counter_acc; + /* Set Voltage */ + u64 vddcr_set_voltage; + u64 vddcr_soc_set_voltage; + u64 vddcr_npu_set_voltage; + u64 vddcr_lp_set_voltage; + u64 vddcr_gfx_set_voltage; + u64 vdd_misc_set_voltage; + /* Telemetry Voltages */ + u64 vddcr_telemetry_voltage; + u64 vddcr_soc_telemetry_voltage; + u64 vddcr_npu_telemetry_voltage; + u64 vddcr_lp_telemetry_voltage; + u64 vddcr_gfx_telemetry_voltage; + u64 vdd_misc_telemetry_voltage; + /* Telemetry Powers */ + u64 vddcr_telemetry_power; + u64 vddcr_soc_telemetry_power; + u64 vddcr_npu_telemetry_power; + u64 vddcr_lp_telemetry_power; + u64 vddcr_gfx_telemetry_power; + u64 vdd_misc_telemetry_power; + /* Throttlers - Fast PPT */ + u32 fppt_fused_limit; + u32 fppt_max_irm_limit; + u32 fppt_max_pbo_limit; + u32 fppt_limit; + u64 fppt_value_acc; + u32 fppt_residency_acc; + /* Throttlers - Slow PPT */ + u32 sppt_fused_limit; + u32 sppt_max_irm_limit; + u32 sppt_max_pbo_limit; + u32 sppt_limit; + u64 sppt_value_acc; + u32 sppt_residency_acc; + /* Throttlers - STAPM */ + u32 spl_fused_limit; + u32 spl_max_irm_limit; + u32 spl_max_pbo_limit; + u32 spl_limit; + u64 spl_value_acc; + u32 spl_residency_acc; + /* Throttlers - TDC VDDCR */ + u32 tdc_vddcr_fused_limit; + u32 tdc_vddcr_max_irm_limit; + u32 tdc_vddcr_max_pbo_limit; + u32 tdc_vddcr_limit; + u64 tdc_vddcr_value_acc; + u32 tdc_vddcr_residency_acc; + /* Throttlers - TDC VDDCR_SOC */ + u32 tdc_vddcr_soc_fused_limit; + u32 tdc_vddcr_soc_max_irm_limit; + u32 tdc_vddcr_soc_max_pbo_limit; + u32 tdc_vddcr_soc_limit; + u64 tdc_vddcr_soc_value_acc; + u32 tdc_vddcr_soc_residency_acc; + /* Throttlers - TDC VDDCR_NPU */ + u32 tdc_vddcr_npu_fused_limit; + u32 tdc_vddcr_npu_max_irm_limit; + u32 tdc_vddcr_npu_max_pbo_limit; + u32 tdc_vddcr_npu_limit; + u64 tdc_vddcr_npu_value_acc; + u32 tdc_vddcr_npu_residency_acc; + /* Throttlers - TDC VDDCR_LP */ + u32 tdc_vddcr_lp_fused_limit; + u32 tdc_vddcr_lp_max_irm_limit; + u32 tdc_vddcr_lp_max_pbo_limit; + u32 tdc_vddcr_lp_limit; + u64 tdc_vddcr_lp_value_acc; + u32 tdc_vddcr_lp_residency_acc; + /* Throttlers - TDC VDDCR_GFX */ + u32 tdc_vddcr_gfx_fused_limit; + u32 tdc_vddcr_gfx_max_irm_limit; + u32 tdc_vddcr_gfx_max_pbo_limit; + u32 tdc_vddcr_gfx_limit; + u64 tdc_vddcr_gfx_value_acc; + u32 tdc_vddcr_gfx_residency_acc; + /* Throttlers - EDC VDDCR */ + u32 edc_vddcr_fused_limit; + u32 edc_vddcr_max_irm_limit; + u32 edc_vddcr_max_pbo_limit; + u32 edc_vddcr_limit; + /* Throttlers - Thermal */ + u32 thm_fused_limit; + u32 thm_limit; + u64 thm_value_acc; + u32 thm_residency_acc; + u32 prochot_residency_acc; + u64 gfx_temp_acc; + u64 soc_temp_acc; + u32 p3t_fused_limit; + u64 p3t_value_acc; + /* Power */ + u64 system_power_acc; + u64 apu_power_acc; + u64 dgpu_power_acc; + u64 npu_power_acc; + /* Frequencies */ + u64 fclk_freq_eff_acc; + u64 memclk_freq_eff_acc; + u64 lclk_freq_eff_acc; + u64 gfxclk_freq_eff_acc; + u64 socclk_freq_eff_acc; + u64 vclk_freq_eff_acc; + u64 vpeclk_freq_eff_acc; + u64 aieclk_freq_eff_acc; + u64 npuhclk_freq_eff_acc; + /* Bandwidth */ + u64 dram_read_bandwidth; + u64 dram_write_bandwidth; + /* Activity Monitors */ + u64 gfx_busy_acc; + u64 vcn_busy_acc; + u64 npu_busy_acc[3]; + /* STT Limits */ + u32 stt_min_limit; + u64 stt_apu_hotspot_temp_acc; + u64 stt_hs2_hotspot_temp_acc; + u32 stt_apu_temp_limit; + u64 stt_apu_skin_temp_acc; + /* Residencies */ + u64 cpuoff_residency_ccx0; + u64 cpuoff_residency_ccx1; + u64 cpuoff_residency_ccx2; + u64 cpuoff_residency_ccx3; + /* DF-pstates */ + u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS]; + /* System */ + u8 gfx_disabled; + u8 spare2[3]; + u32 gfxclk_fmax; + u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; + u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; + u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; + /* Overclock Capable */ + u8 cpu_precise_and_direct_oc_capable; + u8 gfx_precise_and_direct_oc_capable; + u8 pbo_basic_oc_capable; + u8 pbo_advanced_oc_capable; + u8 pbo_nitro_oc_capable; + u8 memory_and_fabric_oc_capable; + u8 misc_oc_capable; + u8 extreme_cold_oc_capable; + u8 down_config_control_capable; + u8 spare0[3]; + /* Overclock Status */ + u32 fit_limit_scalar; + u8 ln2_enabled; + u8 cpu_precise_and_direct_oc_enabled; + u8 gfx_precise_and_direct_oc_enabled; + u8 spare1[2]; + /* Voltage Guardband in PSM count */ + s8 psm_guardband[5][5][3]; + s32 core_power_limit_offset; + u32 max_freq_offset[5]; + u64 npu_temp_acc; + u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u32 cclk_fboost; + /* PMF */ + u32 pmf_fast_apu_ppt_limit; + u64 pmf_fast_apu_ppt_value_acc; + u32 pmf_fast_apu_ppt_residency_acc; + u32 pmf_slow_apu_ppt_limit; + u64 pmf_slow_apu_ppt_value_acc; + u32 pmf_slow_apu_ppt_residency_acc; + u32 pmf_fast_spm_limit; + u64 pmf_fast_spm_value_acc; + u32 pmf_fast_spm_residency_acc; + u32 pmf_slow_spm_limit; + u64 pmf_slow_spm_value_acc; + u32 pmf_slow_spm_residency_acc; + u32 spare3[5]; +} __packed __aligned(4); + +struct amd_pmf_metrics_ccx { + u64 core_c0[AMD_PMF_NUM_MAX_CORES]; + u64 core_cc6[AMD_PMF_NUM_MAX_CORES]; + u64 core_freq[AMD_PMF_NUM_MAX_CORES]; + u64 core_freqeff[AMD_PMF_NUM_MAX_CORES]; + u64 core_temp[AMD_PMF_NUM_MAX_CORES]; + u64 core_power[AMD_PMF_NUM_MAX_CORES]; +} __packed __aligned(4); + +struct amd_pmf_metrics_v3 { + struct amd_pmf_metrics_iod iod; + struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX]; +} __packed __aligned(4); + struct smu_pmf_metrics_v2 { u16 core_frequency[16]; /* MHz */ u16 core_power[16]; /* mW */ @@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer { int tail; }; +/* SoC-specific SMU mailbox register offsets */ +struct amd_pmf_smu_regs { + u32 msg_reg; + u32 resp_reg; + u32 arg_reg[3]; +}; + +struct amd_pmf_arg_data { + u32 lo; + u32 hi; + u32 size; +}; + struct amd_pmf_dev { void __iomem *regbase; void __iomem *smu_virt_addr; @@ -442,6 +668,14 @@ struct amd_pmf_dev { struct pmf_cbi_ring_buffer cbi_buf; struct mutex cbi_mutex; /* Protects ring buffer access */ struct mutex metrics_mutex; + u32 bios_output[BIOS_OUTPUT_MAX]; + const struct amd_pmf_smu_regs *smu_regs; + void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */ + phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */ + struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */ + struct amd_pmf_arg_data dram_addr; + struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */ + bool npu_metrics_have_prev; }; struct apmf_sps_prop_granular_v2 { @@ -680,14 +914,6 @@ enum system_state { SYSTEM_STATE_MAX, }; -enum ta_slider { - TA_BEST_BATTERY, - TA_BETTER_BATTERY, - TA_BETTER_PERFORMANCE, - TA_BEST_PERFORMANCE, - TA_MAX, -}; - struct amd_pmf_pb_bitmap { const char *name; u32 bit_mask; @@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, }; -enum platform_type { - PTYPE_UNKNOWN = 0, - LID_CLOSE, - CLAMSHELL, - FLAT, - TENT, - STAND, - TABLET, - BOOK, - PRESENTATION, - PULL_FWD, - PTYPE_INVALID = 0xf, -}; - /* Command ids for TA communication */ enum ta_pmf_command { TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, @@ -871,6 +1083,10 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer); int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag); u32 fixp_q88_fromint(u32 val); int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev); +void amd_pmf_set_device(struct device *p_device); + +/* Metrics layer */ +int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev); /* SPS Layer */ int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); @@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); +u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index); int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); +/* Util Layer */ +#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT) +int amd_pmf_cdev_register(struct amd_pmf_dev *dev); +void amd_pmf_cdev_unregister(void); +#else +static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; } +static inline void amd_pmf_cdev_unregister(void) {} +#endif + #endif /* PMF_H */ diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index f48678a23cc7..592ba4de4c7f 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -10,68 +10,14 @@ */ #include <acpi/button.h> +#include <linux/amd-pmf.h> #include <linux/amd-pmf-io.h> #include <linux/cleanup.h> #include <linux/power_supply.h> #include <linux/units.h> #include "pmf.h" -#ifdef CONFIG_AMD_PMF_DEBUG -static const char *platform_type_as_str(u16 platform_type) -{ - switch (platform_type) { - case CLAMSHELL: - return "CLAMSHELL"; - case FLAT: - return "FLAT"; - case TENT: - return "TENT"; - case STAND: - return "STAND"; - case TABLET: - return "TABLET"; - case BOOK: - return "BOOK"; - case PRESENTATION: - return "PRESENTATION"; - case PULL_FWD: - return "PULL_FWD"; - default: - return "UNKNOWN"; - } -} - -static const char *laptop_placement_as_str(u16 device_state) -{ - switch (device_state) { - case ON_TABLE: - return "ON_TABLE"; - case ON_LAP_MOTION: - return "ON_LAP_MOTION"; - case IN_BAG: - return "IN_BAG"; - case OUT_OF_BAG: - return "OUT_OF_BAG"; - default: - return "UNKNOWN"; - } -} - -static const char *ta_slider_as_str(unsigned int state) -{ - switch (state) { - case TA_BEST_PERFORMANCE: - return "PERFORMANCE"; - case TA_BETTER_PERFORMANCE: - return "BALANCED"; - case TA_BEST_BATTERY: - return "POWER_SAVER"; - default: - return "Unknown TA Slider State"; - } -} - -static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) +u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) { switch (index) { case 0 ... 1: @@ -82,13 +28,16 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int return 0; } } +EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); +#ifdef CONFIG_AMD_PMF_DEBUG void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) { int i; dev_dbg(dev->dev, "==== TA inputs START ====\n"); - dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider)); + dev_dbg(dev->dev, "Slider State: %s\n", + amd_pmf_get_slider_position(in->ev_info.power_slider)); dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage); dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design); @@ -102,9 +51,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open"); dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away"); dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light); - dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); + dev_dbg(dev->dev, "Platform type: %s\n", + amd_pmf_get_platform_type(in->ev_info.platform_type)); dev_dbg(dev->dev, "Laptop placement: %s\n", - laptop_placement_as_str(in->ev_info.device_state)); + amd_pmf_get_laptop_placement(in->ev_info.device_state)); for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1, amd_pmf_get_ta_custom_bios_inputs(in, i)); @@ -287,14 +237,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ switch (dev->current_profile) { case PLATFORM_PROFILE_PERFORMANCE: case PLATFORM_PROFILE_BALANCED_PERFORMANCE: - val = TA_BEST_PERFORMANCE; + val = AMD_PMF_TA_BEST_PERFORMANCE; break; case PLATFORM_PROFILE_BALANCED: - val = TA_BETTER_PERFORMANCE; + val = AMD_PMF_TA_BETTER_PERFORMANCE; break; case PLATFORM_PROFILE_LOW_POWER: case PLATFORM_PROFILE_QUIET: - val = TA_BEST_BATTERY; + val = AMD_PMF_TA_BEST_BATTERY; break; default: dev_err(dev->dev, "Unknown Platform Profile.\n"); diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c index 7ccd93f506b2..3fe79f0f37c5 100644 --- a/drivers/platform/x86/amd/pmf/tee-if.c +++ b/drivers/platform/x86/amd/pmf/tee-if.c @@ -8,7 +8,9 @@ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> */ +#include <linux/array_size.h> #include <linux/debugfs.h> +#include <linux/dev_printk.h> #include <linux/tee_drv.h> #include <linux/uuid.h> #include "pmf.h" @@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx) static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action) { - u32 bios_idx; + int bios_idx; + int ret; bios_idx = amd_pmf_get_bios_output_idx(action->action_index); + if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) { + dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx); + return; + } - amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); + ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); + if (!ret) + pdev->bios_output[bios_idx] = action->value; } static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out) diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c new file mode 100644 index 000000000000..4d1a61e4f1b9 --- /dev/null +++ b/drivers/platform/x86/amd/pmf/util.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD Platform Management Framework Util Layer + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> + * Sanket Goswami <Sanket.Goswami@amd.com> + */ + +#include <linux/amd-pmf.h> +#include <linux/minmax.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> + +#include "pmf.h" + +static struct amd_pmf_dev *pmf_dev_handle; +static DEFINE_MUTEX(pmf_util_lock); + +static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info) +{ + struct ta_pmf_shared_memory *ta_sm = NULL; + struct ta_pmf_enact_table *in = NULL; + int idx; + + if (!pdev || !info) + return -EINVAL; + + if (!pdev->shbuf) + return -EINVAL; + + ta_sm = pdev->shbuf; + in = &ta_sm->pmf_input.enact_table; + + /* Set size */ + info->size = sizeof(*info); + + /* PMF Feature support flags */ + if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE)) + info->features_supported |= AMD_PMF_FEAT_AUTO_MODE; + if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) + info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER; + if (pdev->smart_pc_enabled) + info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER; + if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC)) + info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC; + if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC)) + info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC; + + /* Device States */ + info->platform_type = in->ev_info.platform_type; + info->laptop_placement = in->ev_info.device_state; + info->lid_state = in->ev_info.lid_state; + info->user_presence = in->ev_info.user_present; + info->slider_position = in->ev_info.power_slider; + + /* Thermal and Power Metrics */ + info->power_source = in->ev_info.power_source; + info->skin_temp = in->ev_info.skin_temperature; + info->gfx_busy = in->ev_info.gfx_busy; + info->ambient_light = in->ev_info.ambient_light; + info->avg_c0_residency = in->ev_info.avg_c0residency; + info->max_c0_residency = in->ev_info.max_c0residency; + info->socket_power = in->ev_info.socket_power; + + /* Custom BIOS input parameters */ + for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) + info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx); + + /* BIOS output parameters */ + for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) + info->bios_output[idx] = pdev->bios_output[idx]; + + return 0; +} + +static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct amd_pmf_dev *pdev = filp->private_data; + void __user *argp = (void __user *)arg; + struct amd_pmf_info info = {}; + size_t copy_size; + __u64 user_size; + int ret; + + if (cmd != IOCTL_AMD_PMF_POPULATE_DATA) + return -ENOTTY; + + /* First read just the size field from userspace */ + if (copy_from_user(&user_size, argp, sizeof(user_size))) + return -EFAULT; + + guard(mutex)(&pmf_util_lock); + ret = amd_pmf_populate_data(pdev, &info); + if (ret) + return ret; + + copy_size = min_t(size_t, user_size, sizeof(info)); + + /* Set actual size being copied */ + info.size = copy_size; + + if (copy_to_user(argp, &info, copy_size)) + return -EFAULT; + + return 0; +} + +static int amd_pmf_open(struct inode *inode, struct file *filp) +{ + guard(mutex)(&pmf_util_lock); + if (!pmf_dev_handle) + return -ENODEV; + + filp->private_data = pmf_dev_handle; + return 0; +} + +static const struct file_operations pmf_if_ops = { + .owner = THIS_MODULE, + .open = amd_pmf_open, + .unlocked_ioctl = amd_pmf_set_ioctl, +}; + +static struct miscdevice amd_pmf_util_if = { + .minor = MISC_DYNAMIC_MINOR, + .name = "amdpmf_interface", + .fops = &pmf_if_ops, +}; + +int amd_pmf_cdev_register(struct amd_pmf_dev *dev) +{ + int ret; + + guard(mutex)(&pmf_util_lock); + pmf_dev_handle = dev; + ret = misc_register(&amd_pmf_util_if); + if (ret) + pmf_dev_handle = NULL; + + return ret; +} + +void amd_pmf_cdev_unregister(void) +{ + guard(mutex)(&pmf_util_lock); + if (pmf_dev_handle) + misc_deregister(&amd_pmf_util_if); + pmf_dev_handle = NULL; +} diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index fbc30f1f8abd..9c728ac4e045 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -1013,8 +1013,8 @@ static void gmux_remove(struct pnp_dev *pnp) } static const struct pnp_device_id gmux_device_ids[] = { - {GMUX_ACPI_HID, 0}, - {"", 0} + { .id = GMUX_ACPI_HID }, + { } }; static const struct dev_pm_ops gmux_dev_pm_ops = { diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 5b0987ccc270..93d9665717af 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -16,6 +16,7 @@ #include <linux/acpi.h> #include <linux/array_size.h> #include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/dmi.h> #include <linux/err.h> @@ -93,6 +94,8 @@ struct asus_armoury_priv { u32 mini_led_dev_id; u32 gpu_mux_dev_id; + + bool requires_fan_curve; }; static struct asus_armoury_priv asus_armoury = { @@ -216,6 +219,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, u32 result; int err; + /* On some models, PPT changes require an active fan curve */ + if (asus_armoury.requires_fan_curve) { + switch (dev_id) { + case ASUS_WMI_DEVID_PPT_PL1_SPL: + case ASUS_WMI_DEVID_PPT_PL2_SPPT: + case ASUS_WMI_DEVID_PPT_PL3_FPPT: + case ASUS_WMI_DEVID_PPT_APU_SPPT: + case ASUS_WMI_DEVID_PPT_PLAT_SPPT: + if (!asus_wmi_custom_fan_curve_is_enabled()) { + pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); + return -EBUSY; + } + break; + } + } + /* * Prevent developers from bricking devices or issuing dangerous * commands that can be difficult or impossible to recover from. @@ -370,7 +389,7 @@ static ssize_t mini_led_mode_current_value_show(struct kobject *kobj, if (err) return err; - mode = FIELD_GET(ASUS_MINI_LED_MODE_MASK, 0); + mode = FIELD_GET(ASUS_MINI_LED_MODE_MASK, mode); for (i = 0; i < mini_led_mode_map_size; i++) if (mode == mini_led_mode_map[i]) @@ -386,6 +405,7 @@ static ssize_t mini_led_mode_current_value_store(struct kobject *kobj, { u32 *mini_led_mode_map; size_t mini_led_mode_map_size; + char mapped_value[12]; u32 mode; int err; @@ -414,9 +434,16 @@ static ssize_t mini_led_mode_current_value_store(struct kobject *kobj, return -ENODEV; } - return armoury_attr_uint_store(kobj, attr, buf, count, - 0, mini_led_mode_map[mode], - NULL, asus_armoury.mini_led_dev_id); + /* + * armoury_attr_uint_store() parses and sends the value from the + * passed buffer; hand it the mapped firmware value so the device + * receives the translated mode instead of the raw index. + */ + snprintf(mapped_value, sizeof(mapped_value), "%u", mini_led_mode_map[mode]); + + return armoury_attr_uint_store(kobj, attr, mapped_value, count, 0, + mini_led_mode_map[mode], NULL, + asus_armoury.mini_led_dev_id); } static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj, @@ -981,10 +1008,11 @@ fail_class_get: /* Init / exit ****************************************************************/ /* Set up the min/max and defaults for ROG tunables */ -static void init_rog_tunables(void) +static int init_rog_tunables(void) { const struct power_limits *ac_limits, *dc_limits; - struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL; + struct rog_tunables *ac_rog_tunables __free(kfree) = NULL; + struct rog_tunables *dc_rog_tunables __free(kfree) = NULL; const struct power_data *power_data; const struct dmi_system_id *dmi_id; @@ -992,24 +1020,25 @@ static void init_rog_tunables(void) dmi_id = dmi_first_match(power_limits); if (!dmi_id) { pr_warn("No matching power limits found for this system\n"); - return; + return 0; } /* Get the power data for this system */ power_data = dmi_id->driver_data; if (!power_data) { pr_info("No power data available for this system\n"); - return; + return 0; } + asus_armoury.requires_fan_curve = power_data->requires_fan_curve; + /* Initialize AC power tunables */ ac_limits = power_data->ac_data; if (ac_limits) { - ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); + ac_rog_tunables = kzalloc_obj(*ac_rog_tunables); if (!ac_rog_tunables) - goto err_nomem; + return -ENOMEM; - asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; ac_rog_tunables->power_limits = ac_limits; /* Set initial AC values */ @@ -1052,13 +1081,10 @@ static void init_rog_tunables(void) /* Initialize DC power tunables */ dc_limits = power_data->dc_data; if (dc_limits) { - dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); - if (!dc_rog_tunables) { - kfree(ac_rog_tunables); - goto err_nomem; - } + dc_rog_tunables = kzalloc_obj(*dc_rog_tunables); + if (!dc_rog_tunables) + return -ENOMEM; - asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; dc_rog_tunables->power_limits = dc_limits; /* Set initial DC values */ @@ -1098,15 +1124,16 @@ static void init_rog_tunables(void) pr_debug("No DC PPT limits defined\n"); } - return; + asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables); + asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables); -err_nomem: - pr_err("Failed to allocate memory for tunables\n"); + return 0; } static int __init asus_fw_init(void) { char *wmi_uid; + int err; wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); if (!wmi_uid) @@ -1119,10 +1146,21 @@ static int __init asus_fw_init(void) if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) return -ENODEV; - init_rog_tunables(); + err = init_rog_tunables(); + if (err) + return err; /* Must always be last step to ensure data is available */ - return asus_fw_attr_add(); + err = asus_fw_attr_add(); + if (err) + goto err_free_tunables; + + return 0; + +err_free_tunables: + kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); + kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); + return err; } static void __exit asus_fw_exit(void) diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 208f6fe16168..0e301d663d0c 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -348,6 +348,61 @@ struct power_data { static const struct dmi_system_id power_limits[] = { { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401EA"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 95, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 100, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 71, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 71, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 71, + }, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401KM"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .nv_dynamic_boost_max = 15, + .nv_dynamic_boost_min = 5, + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .nv_tgp_max = 95, + .nv_tgp_min = 55, + .ppt_pl1_spl_max = 80, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl3_fppt_max = 80, + .ppt_pl3_fppt_min = 35, + }, + .dc_data = &(struct power_limits) { + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_max = 35, + .ppt_pl1_spl_min = 25, + .ppt_pl2_sppt_max = 44, + .ppt_pl2_sppt_min = 31, + .ppt_pl3_fppt_max = 65, + .ppt_pl3_fppt_min = 45, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA401UM"), }, .driver_data = &(struct power_data) { @@ -594,6 +649,37 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA607NU"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 25, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA607P"), }, .driver_data = &(struct power_data) { @@ -702,6 +788,40 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA608WV"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .nv_dynamic_boost_max = 25, + .nv_dynamic_boost_min = 5, + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .nv_tgp_max = 115, + .nv_tgp_min = 55, + .ppt_pl1_spl_max = 90, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 90, + .ppt_pl2_sppt_min = 35, + .ppt_pl3_fppt_max = 90, + .ppt_pl3_fppt_min = 35, + }, + .dc_data = &(struct power_limits) { + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 65, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl3_fppt_max = 65, + .ppt_pl3_fppt_min = 35, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), }, .driver_data = &(struct power_data) { @@ -857,6 +977,87 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 85, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 60, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX607VU"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_def = 115, + .ppt_pl1_spl_max = 135, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 60, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX608JPR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 100, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 10, + .nv_dynamic_boost_max = 15, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 100, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 53, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA401Q"), }, .driver_data = &(struct power_data) { @@ -871,6 +1072,38 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA402NJ"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 35, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_def = 65, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_def = 10, + .nv_dynamic_boost_max = 15, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 35, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + }, + }, + { + .matches = { // This model is full AMD. No Nvidia dGPU. DMI_MATCH(DMI_BOARD_NAME, "GA402R"), }, @@ -954,6 +1187,38 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA403UM"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 80, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 80, + .nv_dynamic_boost_min = 0, + .nv_dynamic_boost_max = 15, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 85, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 35, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA403UV"), }, .driver_data = &(struct power_data) { @@ -1082,6 +1347,20 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA503QM"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_def = 35, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_max = 80, + }, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA503QR"), }, .driver_data = &(struct power_data) { @@ -1210,6 +1489,35 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GU605CP"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 45, + .ppt_pl1_spl_max = 75, + .ppt_pl2_sppt_min = 56, + .ppt_pl2_sppt_max = 95, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 15, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_def = 75, + .nv_tgp_max = 95, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 75, + .ppt_pl2_sppt_min = 32, + .ppt_pl2_sppt_max = 95, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "GU605CR"), }, .driver_data = &(struct power_data) { @@ -1297,6 +1605,34 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GU605MU"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 90, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 20, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_max = 85, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 38, + .ppt_pl2_sppt_max = 53, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "GU605M"), }, .driver_data = &(struct power_data) { @@ -1364,6 +1700,33 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GV302XU"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 55, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 60, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 35, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 65, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "GV302XV"), }, .driver_data = &(struct power_data) { @@ -1520,6 +1883,35 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_def = 60, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 32, + .ppt_pl2_sppt_def = 75, + .ppt_pl2_sppt_max = 92, + .ppt_pl3_fppt_min = 45, + .ppt_pl3_fppt_def = 86, + .ppt_pl3_fppt_max = 93, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 80, + .ppt_pl2_sppt_min = 32, + .ppt_pl2_sppt_def = 52, + .ppt_pl2_sppt_max = 92, + .ppt_pl3_fppt_min = 45, + .ppt_pl3_fppt_def = 71, + .ppt_pl3_fppt_max = 93, + }, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "G513I"), }, .driver_data = &(struct power_data) { @@ -1598,6 +1990,74 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G614FP"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_max = 120, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_def = 140, + .ppt_pl2_sppt_max = 165, + .ppt_pl3_fppt_min = 65, + .ppt_pl3_fppt_def = 140, + .ppt_pl3_fppt_max = 165, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 15, + .nv_tgp_min = 50, + .nv_tgp_max = 100, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 75, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G614FR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_max = 120, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_def = 140, + .ppt_pl2_sppt_max = 162, + .ppt_pl3_fppt_min = 65, + .ppt_pl3_fppt_def = 140, + .ppt_pl3_fppt_max = 162, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_tgp_min = 65, + .nv_tgp_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 75, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "G614J"), }, .driver_data = &(struct power_data) { @@ -1624,6 +2084,40 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G614PR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_max = 120, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_def = 140, + .ppt_pl2_sppt_max = 145, + .ppt_pl3_fppt_min = 65, + .ppt_pl3_fppt_def = 140, + .ppt_pl3_fppt_max = 145, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_tgp_min = 65, + .nv_tgp_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 75, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "G615LR"), }, .driver_data = &(struct power_data) { @@ -1679,6 +2173,35 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G635LX"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_def = 140, + .ppt_pl1_spl_max = 175, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 175, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 80, + .nv_tgp_max = 150, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 55, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 70, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "G713PV"), }, .driver_data = &(struct power_data) { @@ -1922,6 +2445,35 @@ static const struct dmi_system_id power_limits[] = { }, { .matches = { + DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 35, + .ppt_pl1_spl_def = 60, + .ppt_pl1_spl_max = 85, + .ppt_pl2_sppt_min = 40, + .ppt_pl2_sppt_def = 70, + .ppt_pl2_sppt_max = 95, + .ppt_pl3_fppt_min = 50, + .ppt_pl3_fppt_def = 85, + .ppt_pl3_fppt_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 35, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 60, + .ppt_pl2_sppt_min = 40, + .ppt_pl2_sppt_def = 55, + .ppt_pl2_sppt_max = 70, + .ppt_pl3_fppt_min = 50, + .ppt_pl3_fppt_def = 65, + .ppt_pl3_fppt_max = 85, + }, + }, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "RC71"), }, .driver_data = &(struct power_data) { diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index d96f6af26ff7..79a575d0f5b4 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -42,8 +42,6 @@ #define ASUS_LAPTOP_VERSION "0.42" #define ASUS_LAPTOP_NAME "Asus Laptop Support" -#define ASUS_LAPTOP_CLASS "hotkey" -#define ASUS_LAPTOP_DEVICE_NAME "Hotkey" #define ASUS_LAPTOP_FILE KBUILD_MODNAME #define ASUS_LAPTOP_PREFIX "\\_SB.ATKD." @@ -1517,16 +1515,15 @@ static void asus_input_exit(struct asus_laptop *asus) /* * ACPI driver */ -static void asus_acpi_notify(struct acpi_device *device, u32 event) +static void asus_acpi_notify(acpi_handle handle, u32 event, void *data) { - struct asus_laptop *asus = acpi_driver_data(device); + struct asus_laptop *asus = data; u16 count; /* TODO Find a better way to handle events count. */ count = asus->event_count[event % 128]++; - acpi_bus_generate_netlink_event(asus->device->pnp.device_class, - dev_name(&asus->device->dev), event, - count); + acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev), + event, count); if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX) event = ATKD_BRNUP; @@ -1824,20 +1821,22 @@ static void asus_dmi_check(void) static bool asus_device_present; -static int asus_acpi_add(struct acpi_device *device) +static int asus_acpi_probe(struct platform_device *pdev) { + struct acpi_device *device; struct asus_laptop *asus; int result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + pr_notice("Asus Laptop Support version %s\n", ASUS_LAPTOP_VERSION); asus = kzalloc_obj(struct asus_laptop); if (!asus) return -ENOMEM; asus->handle = device->handle; - strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME); - strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS); - device->driver_data = asus; asus->device = device; asus_dmi_check(); @@ -1846,6 +1845,8 @@ static int asus_acpi_add(struct acpi_device *device) if (result) goto fail_platform; + platform_set_drvdata(pdev, asus); + /* * Need platform type detection first, then the platform * device. It is used as a parent for the sub-devices below. @@ -1881,6 +1882,11 @@ static int asus_acpi_add(struct acpi_device *device) if (result && result != -ENODEV) goto fail_pega_rfkill; + result = acpi_dev_install_notify_handler(device, ACPI_ALL_NOTIFY, + asus_acpi_notify, asus); + if (result) + goto fail_pega_rfkill; + asus_device_present = true; return 0; @@ -1902,10 +1908,12 @@ fail_platform: return result; } -static void asus_acpi_remove(struct acpi_device *device) +static void asus_acpi_remove(struct platform_device *pdev) { - struct asus_laptop *asus = acpi_driver_data(device); + struct asus_laptop *asus = platform_get_drvdata(pdev); + acpi_dev_remove_notify_handler(asus->device, ACPI_ALL_NOTIFY, + asus_acpi_notify); asus_backlight_exit(asus); asus_rfkill_exit(asus); asus_led_exit(asus); @@ -1924,16 +1932,13 @@ static const struct acpi_device_id asus_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, asus_device_ids); -static struct acpi_driver asus_acpi_driver = { - .name = ASUS_LAPTOP_NAME, - .class = ASUS_LAPTOP_CLASS, - .ids = asus_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = asus_acpi_add, - .remove = asus_acpi_remove, - .notify = asus_acpi_notify, - }, +static struct platform_driver asus_acpi_driver = { + .probe = asus_acpi_probe, + .remove = asus_acpi_remove, + .driver = { + .name = ASUS_LAPTOP_NAME, + .acpi_match_table = asus_device_ids, + }, }; static int __init asus_laptop_init(void) @@ -1944,7 +1949,7 @@ static int __init asus_laptop_init(void) if (result < 0) return result; - result = acpi_bus_register_driver(&asus_acpi_driver); + result = platform_driver_register(&asus_acpi_driver); if (result < 0) goto fail_acpi_driver; if (!asus_device_present) { @@ -1954,7 +1959,7 @@ static int __init asus_laptop_init(void) return 0; fail_no_device: - acpi_bus_unregister_driver(&asus_acpi_driver); + platform_driver_unregister(&asus_acpi_driver); fail_acpi_driver: platform_driver_unregister(&platform_driver); return result; @@ -1962,7 +1967,7 @@ fail_acpi_driver: static void __exit asus_laptop_exit(void) { - acpi_bus_unregister_driver(&asus_acpi_driver); + platform_driver_unregister(&asus_acpi_driver); platform_driver_unregister(&platform_driver); } diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index a38a65f5c550..aeb461b1644d 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -546,9 +546,18 @@ static const struct dmi_system_id asus_quirks[] = { }, { .callback = dmi_matched, + .ident = "ASUS Zenbook Duo UX8407AA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), + DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook Duo UX8407AA"), + }, + .driver_data = &quirk_asus_zenbook_duo_kbd, + }, + { + .callback = dmi_matched, .ident = "ASUS ROG Z13", .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), DMI_MATCH(DMI_PRODUCT_NAME, "ROG Flow Z13"), }, .driver_data = &quirk_asus_z13, @@ -623,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { { KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */ { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ + { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */ { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ diff --git a/drivers/platform/x86/asus-tf103c-dock.c b/drivers/platform/x86/asus-tf103c-dock.c index f09a3fc6524a..92466477de9a 100644 --- a/drivers/platform/x86/asus-tf103c-dock.c +++ b/drivers/platform/x86/asus-tf103c-dock.c @@ -21,7 +21,6 @@ #include <linux/input.h> #include <linux/irq.h> #include <linux/irqdomain.h> -#include <linux/mod_devicetable.h> #include <linux/moduleparam.h> #include <linux/module.h> #include <linux/pm.h> diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c index 41227bf95878..aab45f0442c5 100644 --- a/drivers/platform/x86/asus-wireless.c +++ b/drivers/platform/x86/asus-wireless.c @@ -12,6 +12,7 @@ #include <linux/acpi.h> #include <linux/input.h> #include <linux/pci_ids.h> +#include <linux/platform_device.h> #include <linux/leds.h> struct hswc_params { @@ -108,9 +109,10 @@ static void led_state_set(struct led_classdev *led, enum led_brightness value) queue_work(data->wq, &data->led_work); } -static void asus_wireless_notify(struct acpi_device *adev, u32 event) +static void asus_wireless_notify(acpi_handle handle, u32 event, void *context) { - struct asus_wireless_data *data = acpi_driver_data(adev); + struct asus_wireless_data *data = context; + struct acpi_device *adev = data->adev; dev_dbg(&adev->dev, "event=%#x\n", event); if (event != 0x88) { @@ -123,19 +125,27 @@ static void asus_wireless_notify(struct acpi_device *adev, u32 event) input_sync(data->idev); } -static int asus_wireless_add(struct acpi_device *adev) +static int asus_wireless_probe(struct platform_device *pdev) { + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); struct asus_wireless_data *data; const struct acpi_device_id *id; int err; - data = devm_kzalloc(&adev->dev, sizeof(*data), GFP_KERNEL); + id = acpi_match_acpi_device(device_ids, adev); + if (!id) + return -ENODEV; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - adev->driver_data = data; + + platform_set_drvdata(pdev, data); + data->adev = adev; + data->hswc_params = (const struct hswc_params *)id->driver_data; - data->idev = devm_input_allocate_device(&adev->dev); + data->idev = devm_input_allocate_device(&pdev->dev); if (!data->idev) return -ENOMEM; data->idev->name = "Asus Wireless Radio Control"; @@ -148,12 +158,6 @@ static int asus_wireless_add(struct acpi_device *adev) if (err) return err; - id = acpi_match_acpi_device(device_ids, adev); - if (!id) - return 0; - - data->hswc_params = (const struct hswc_params *)id->driver_data; - data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); if (!data->wq) return -ENOMEM; @@ -164,34 +168,44 @@ static int asus_wireless_add(struct acpi_device *adev) 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); + err = devm_led_classdev_register(&pdev->dev, &data->led); if (err) - destroy_workqueue(data->wq); + goto err; + + err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, + asus_wireless_notify, data); + if (err) { + devm_led_classdev_unregister(&pdev->dev, &data->led); + goto err; + } + return 0; +err: + destroy_workqueue(data->wq); return err; } -static void asus_wireless_remove(struct acpi_device *adev) +static void asus_wireless_remove(struct platform_device *pdev) { - struct asus_wireless_data *data = acpi_driver_data(adev); + struct asus_wireless_data *data = platform_get_drvdata(pdev); + acpi_dev_remove_notify_handler(data->adev, ACPI_DEVICE_NOTIFY, + asus_wireless_notify); if (data->wq) { - devm_led_classdev_unregister(&adev->dev, &data->led); + devm_led_classdev_unregister(&pdev->dev, &data->led); destroy_workqueue(data->wq); } } -static struct acpi_driver asus_wireless_driver = { - .name = "Asus Wireless Radio Control Driver", - .class = "hotkey", - .ids = device_ids, - .ops = { - .add = asus_wireless_add, - .remove = asus_wireless_remove, - .notify = asus_wireless_notify, +static struct platform_driver asus_wireless_driver = { + .probe = asus_wireless_probe, + .remove = asus_wireless_remove, + .driver = { + .name = "Asus Wireless Radio Control Driver", + .acpi_match_table = device_ids, }, }; -module_acpi_driver(asus_wireless_driver); +module_platform_driver(asus_wireless_driver); MODULE_DESCRIPTION("Asus Wireless Radio Control Driver"); MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>"); diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 7c0915e097ba..a65090429ca7 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -70,6 +70,7 @@ module_param(fnlock_default, bool, 0444); #define NOTIFY_KBD_TTP 0xae #define NOTIFY_LID_FLIP 0xfa #define NOTIFY_LID_FLIP_ROG 0xbd +#define NOTIFY_KEYSTONE 0xb4 #define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0) @@ -125,7 +126,6 @@ module_param(fnlock_default, bool, 0444); #define NVIDIA_TEMP_MIN 75 #define NVIDIA_TEMP_MAX 87 -#define ASUS_SCREENPAD_BRIGHT_MIN 20 #define ASUS_SCREENPAD_BRIGHT_MAX 255 #define ASUS_SCREENPAD_BRIGHT_DEFAULT 60 @@ -280,6 +280,8 @@ struct asus_wmi { u32 tablet_switch_dev_id; bool tablet_switch_inverted; + bool keystone_available; + enum fan_type fan_type; enum fan_type gpu_fan_type; enum fan_type mid_fan_type; @@ -647,6 +649,55 @@ static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id) return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT); } +/* Keystone *******************************************************************/ + +static int keystone_check_present(struct asus_wmi *asus) +{ + u32 retval; + int err; + + asus->keystone_available = false; + + /* + * Use a raw devstate call rather than asus_wmi_dev_is_present(). + * For this devid, PRESENCE_BIT encodes current insert state, not + * feature presence, so asus_wmi_dev_is_present() would return false + * whenever the dongle is absent at boot, even on machines that have + * a keystone slot. + * -ENODEV means the firmware doesn't know this devid at all. + * retval is not examined here, only the return code matters. + */ + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_KEYSTONE, &retval); + if (err == -ENODEV) + return 0; + if (err) + return err; + + asus->keystone_available = true; + return 0; +} + +static ssize_t keystone_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + u32 retval; + int err; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_KEYSTONE, &retval); + if (err) + return err; + + return sysfs_emit(buf, "%d\n", !!(retval & ASUS_WMI_DSTS_PRESENCE_BIT)); +} + +static DEVICE_ATTR_RO(keystone); + +static void asus_wmi_keystone_notify(struct asus_wmi *asus) +{ + sysfs_notify(&asus->platform_device->dev.kobj, NULL, "keystone"); +} + /* Input **********************************************************************/ static void asus_wmi_tablet_sw_report(struct asus_wmi *asus, bool value) { @@ -1557,13 +1608,18 @@ static ssize_t charge_control_end_threshold_show(struct device *device, struct device_attribute *attr, char *buf) { - return sysfs_emit(buf, "%d\n", charge_end_threshold); + if ((charge_end_threshold >= 0) && (charge_end_threshold <= 100)) + return sysfs_emit(buf, "%d\n", charge_end_threshold); + + return -ENODATA; } static DEVICE_ATTR_RW(charge_control_end_threshold); static int asus_wmi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) { + int ret, rv; + /* The WMI method does not provide a way to specific a battery, so we * just assume it is the first battery. * Note: On some newer ASUS laptops (Zenbook UM431DA), the primary/first @@ -1580,13 +1636,31 @@ static int asus_wmi_battery_add(struct power_supply *battery, struct acpi_batter return -ENODEV; /* The charge threshold is only reset when the system is power cycled, - * and we can't get the current threshold so let set it to 100% when - * a battery is added. + * and we can't read the current threshold, however the majority of + * platforms retains it. + * + * Setting a negative value would signal the threshold as unknown + * until user explicitly sets it to a new value, however to avoid + * regressing userspace, we initialize it to a value of 100. */ - asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, 100, NULL); charge_end_threshold = 100; + ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, charge_end_threshold, &rv); + if (ret) { + pr_err("Failed to reset battery charge threshold\n"); + goto asus_wmi_battery_add_err; + } + + if (rv != 1) { + pr_err("Error in battery charge threshold reset\n"); + ret = -EIO; + goto asus_wmi_battery_add_err; + } return 0; +asus_wmi_battery_add_err: + device_remove_file(&battery->dev, + &dev_attr_charge_control_end_threshold); + return ret; } static int asus_wmi_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) @@ -3999,6 +4073,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) return 0; } +/* + * Returns true if at least one custom fan curve is active + * + * Used by asus-armoury to check if PPT writes will be accepted by the BIOS + * on models that require an active fan curve for TDP changes. + */ +bool asus_wmi_custom_fan_curve_is_enabled(void) +{ + struct fan_curve_data *curves; + struct asus_wmi *asus; + + guard(spinlock_irqsave)(&asus_ref.lock); + asus = asus_ref.asus; + if (!asus) + return false; + + curves = asus->custom_fan_curves; + + return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || + (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || + (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); +} +EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); + /* Throttle thermal policy ****************************************************/ static int throttle_thermal_policy_write(struct asus_wmi *asus) { @@ -4408,43 +4506,35 @@ static int read_screenpad_brightness(struct backlight_device *bd) return err; /* The device brightness can only be read if powered, so return stored */ if (err == BACKLIGHT_POWER_OFF) - return asus->driver->screenpad_brightness - ASUS_SCREENPAD_BRIGHT_MIN; + return bd->props.brightness; err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &retval); if (err < 0) return err; - return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) - ASUS_SCREENPAD_BRIGHT_MIN; + return retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK; } static int update_screenpad_bl_status(struct backlight_device *bd) { - struct asus_wmi *asus = bl_get_data(bd); - int power, err = 0; - u32 ctrl_param; + u32 ctrl_param = bd->props.brightness; + int err = 0; - power = read_screenpad_backlight_power(asus); - if (power < 0) - return power; + if (bd->props.power) { + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL); + if (err < 0) + return err; - if (bd->props.power != power) { - if (power != BACKLIGHT_POWER_ON) { - /* Only brightness > 0 can power it back on */ - ctrl_param = asus->driver->screenpad_brightness - ASUS_SCREENPAD_BRIGHT_MIN; - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, - ctrl_param, NULL); - } else { - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); - } - } else if (power == BACKLIGHT_POWER_ON) { - /* Only set brightness if powered on or we get invalid/unsync state */ - ctrl_param = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL); + if (err < 0) + return err; } - /* Ensure brightness is stored to turn back on with */ - if (err == 0) - asus->driver->screenpad_brightness = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN; + if (!bd->props.power) { + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); + if (err < 0) + return err; + } return err; } @@ -4462,22 +4552,19 @@ static int asus_screenpad_init(struct asus_wmi *asus) int err, power; int brightness = 0; - power = read_screenpad_backlight_power(asus); + power = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER); if (power < 0) return power; - if (power != BACKLIGHT_POWER_OFF) { + if (power) { err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness); if (err < 0) return err; } - /* default to an acceptable min brightness on boot if too low */ - if (brightness < ASUS_SCREENPAD_BRIGHT_MIN) - brightness = ASUS_SCREENPAD_BRIGHT_DEFAULT; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; /* ensure this bd is last to be picked */ - props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX - ASUS_SCREENPAD_BRIGHT_MIN; + props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX; bd = backlight_device_register("asus_screenpad", &asus->platform_device->dev, asus, &asus_screenpad_bl_ops, &props); @@ -4488,7 +4575,7 @@ static int asus_screenpad_init(struct asus_wmi *asus) asus->screenpad_backlight_device = bd; asus->driver->screenpad_brightness = brightness; - bd->props.brightness = brightness - ASUS_SCREENPAD_BRIGHT_MIN; + bd->props.brightness = brightness; bd->props.power = power; backlight_update_status(bd); @@ -4584,6 +4671,12 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) return; } + if (code == NOTIFY_KEYSTONE) { + if (asus->keystone_available) + asus_wmi_keystone_notify(asus); + return; + } + if (code == NOTIFY_KBD_FBM || code == NOTIFY_KBD_TTP) { if (asus->fan_boost_mode_available) fan_boost_mode_switch_next(asus); @@ -4707,6 +4800,7 @@ static struct attribute *platform_attributes[] = { &dev_attr_lid_resume.attr, &dev_attr_als_enable.attr, &dev_attr_fan_boost_mode.attr, + &dev_attr_keystone.attr, #if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) &dev_attr_charge_mode.attr, &dev_attr_egpu_enable.attr, @@ -4750,6 +4844,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, devid = ASUS_WMI_DEVID_ALS_ENABLE; else if (attr == &dev_attr_fan_boost_mode.attr) ok = asus->fan_boost_mode_available; + else if (attr == &dev_attr_keystone.attr) + ok = asus->keystone_available; #if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) if (attr == &dev_attr_charge_mode.attr) @@ -5090,6 +5186,10 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform_profile_setup; + err = keystone_check_present(asus); + if (err) + dev_warn(&pdev->dev, "Failed to check Keystone presence: %d\n", err); + err = asus_wmi_sysfs_init(asus->platform_device); if (err) goto fail_sysfs; @@ -5168,20 +5268,20 @@ static int asus_wmi_add(struct platform_device *pdev) return 0; fail_wmi_handler: + asus_screenpad_exit(asus); +fail_screenpad: asus_wmi_backlight_exit(asus); fail_backlight: asus_wmi_rfkill_exit(asus); -fail_screenpad: - asus_screenpad_exit(asus); fail_rfkill: asus_wmi_led_exit(asus); fail_leds: +fail_custom_fan_curve: fail_hwmon: asus_wmi_input_exit(asus); fail_input: asus_wmi_sysfs_exit(asus->platform_device); fail_sysfs: -fail_custom_fan_curve: fail_platform_profile_setup: fail_fan_boost_mode: fail_platform: @@ -5413,17 +5513,3 @@ void asus_wmi_unregister_driver(struct asus_wmi_driver *driver) used = false; } EXPORT_SYMBOL_GPL(asus_wmi_unregister_driver); - -static int __init asus_wmi_init(void) -{ - pr_info("ASUS WMI generic driver loaded\n"); - return 0; -} - -static void __exit asus_wmi_exit(void) -{ - pr_info("ASUS WMI generic driver unloaded\n"); -} - -module_init(asus_wmi_init); -module_exit(asus_wmi_exit); diff --git a/drivers/platform/x86/barco-p50-gpio.c b/drivers/platform/x86/barco-p50-gpio.c index 6f13e81f98fb..e1400382465d 100644 --- a/drivers/platform/x86/barco-p50-gpio.c +++ b/drivers/platform/x86/barco-p50-gpio.c @@ -124,7 +124,6 @@ static const struct software_node vendor_key_node = { }; static const struct software_node *p50_swnodes[] = { - &gpiochip_node, &gpio_leds_node, &identify_led_node, &gpio_keys_node, @@ -272,30 +271,27 @@ static int p50_gpio_get(struct gpio_chip *gc, unsigned int offset) struct p50_gpio *p50 = gpiochip_get_data(gc); int ret; - mutex_lock(&p50->lock); + guard(mutex)(&p50->lock); ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_READ_GPIO, gpio_params[offset], 0); - if (ret == 0) - ret = p50_read_mbox_reg(p50, P50_MBOX_REG_DATA); + if (ret < 0) + return ret; - mutex_unlock(&p50->lock); + ret = p50_read_mbox_reg(p50, P50_MBOX_REG_DATA); + if (ret < 0) + return ret; - return ret; + return !!ret; } static int p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct p50_gpio *p50 = gpiochip_get_data(gc); - int ret; - mutex_lock(&p50->lock); + guard(mutex)(&p50->lock); - ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, - gpio_params[offset], value); - - mutex_unlock(&p50->lock); - - return ret; + return p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, + gpio_params[offset], value); } static int p50_gpio_probe(struct platform_device *pdev) @@ -427,6 +423,13 @@ MODULE_DEVICE_TABLE(dmi, dmi_ids); static int __init p50_module_init(void) { struct resource res = DEFINE_RES_IO(P50_GPIO_IO_PORT_BASE, P50_PORT_CMD + 1); + struct platform_device_info pdevinfo = { + .name = DRIVER_NAME, + .id = PLATFORM_DEVID_NONE, + .res = &res, + .num_res = 1, + .swnode = &gpiochip_node, + }; int ret; if (!dmi_first_match(dmi_ids)) @@ -436,7 +439,7 @@ static int __init p50_module_init(void) if (ret) return ret; - gpio_pdev = platform_device_register_simple(DRIVER_NAME, PLATFORM_DEVID_NONE, &res, 1); + gpio_pdev = platform_device_register_full(&pdevinfo); if (IS_ERR(gpio_pdev)) { pr_err("failed registering %s: %ld\n", DRIVER_NAME, PTR_ERR(gpio_pdev)); platform_driver_unregister(&p50_gpio_driver); diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c new file mode 100644 index 000000000000..3a373184519d --- /dev/null +++ b/drivers/platform/x86/bitland-mifs-wmi.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Linux driver for Bitland notebooks. + * + * Copyright (C) 2026 2 Mingyou Chen <qby140326@gmail.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/container_of.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/device/devres.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/input-event-codes.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/platform_profile.h> +#include <linux/pm.h> +#include <linux/power_supply.h> +#include <linux/stddef.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/unaligned.h> +#include <linux/units.h> +#include <linux/wmi.h> + +#define DRV_NAME "bitland-mifs-wmi" +#define BITLAND_MIFS_GUID "B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B" +#define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF" + +enum bitland_mifs_operation { + WMI_METHOD_GET = 250, + WMI_METHOD_SET = 251, +}; + +enum bitland_mifs_function { + WMI_FN_SYSTEM_PER_MODE = 8, + WMI_FN_GPU_MODE = 9, + WMI_FN_KBD_TYPE = 10, + WMI_FN_FN_LOCK = 11, + WMI_FN_TP_LOCK = 12, + WMI_FN_FAN_SPEEDS = 13, + WMI_FN_RGB_KB_MODE = 16, + WMI_FN_RGB_KB_COLOR = 17, + WMI_FN_RGB_KB_BRIGHTNESS = 18, + WMI_FN_SYSTEM_AC_TYPE = 19, + WMI_FN_MAX_FAN_SWITCH = 20, + WMI_FN_MAX_FAN_SPEED = 21, + WMI_FN_CPU_THERMOMETER = 22, + WMI_FN_CPU_POWER = 23, +}; + +enum bitland_system_ac_mode { + WMI_SYSTEM_AC_TYPEC = 1, + /* Unknown type, this is unused in the original driver */ + WMI_SYSTEM_AC_CIRCULARHOLE = 2, +}; + +enum bitland_mifs_power_profile { + WMI_PP_BALANCED = 0, + WMI_PP_PERFORMANCE = 1, + WMI_PP_QUIET = 2, + WMI_PP_FULL_SPEED = 3, +}; + +enum bitland_mifs_event_id { + WMI_EVENT_RESERVED_1 = 1, + WMI_EVENT_RESERVED_2 = 2, + WMI_EVENT_RESERVED_3 = 3, + WMI_EVENT_AIRPLANE_MODE = 4, + WMI_EVENT_KBD_BRIGHTNESS = 5, + WMI_EVENT_TOUCHPAD_STATE = 6, + WMI_EVENT_FNLOCK_STATE = 7, + WMI_EVENT_KBD_MODE = 8, + WMI_EVENT_CAPSLOCK_STATE = 9, + WMI_EVENT_CALCULATOR_START = 11, + WMI_EVENT_BROWSER_START = 12, + WMI_EVENT_NUMLOCK_STATE = 13, + WMI_EVENT_SCROLLLOCK_STATE = 14, + WMI_EVENT_PERFORMANCE_PLAN = 15, + WMI_EVENT_FN_J = 16, + WMI_EVENT_FN_F = 17, + WMI_EVENT_FN_0 = 18, + WMI_EVENT_FN_1 = 19, + WMI_EVENT_FN_2 = 20, + WMI_EVENT_FN_3 = 21, + WMI_EVENT_FN_4 = 22, + WMI_EVENT_FN_5 = 24, + WMI_EVENT_REFRESH_RATE = 25, + WMI_EVENT_CPU_FAN_SPEED = 26, + WMI_EVENT_GPU_FAN_SPEED = 32, + WMI_EVENT_WIN_KEY_LOCK = 33, + WMI_EVENT_RESERVED_23 = 34, + WMI_EVENT_OPEN_APP = 35, +}; + +enum bitland_mifs_event_type { + WMI_EVENT_TYPE_HOTKEY = 1, +}; + +enum bitland_wmi_device_type { + BITLAND_WMI_CONTROL = 0, + BITLAND_WMI_EVENT = 1, +}; + +struct bitland_mifs_input { + u8 reserved1; + u8 operation; + u8 reserved2; + u8 function; + u8 payload[28]; +} __packed; + +struct bitland_mifs_output { + u8 reserved1; + u8 operation; + u8 reserved2; + u8 function; + u8 data[28]; +} __packed; + +struct bitland_mifs_event { + u8 event_type; + u8 event_id; + u8 value_low; /* For most events, this is the value */ + u8 value_high; /* For fan speed events, combined with value_low */ + u8 reserved[4]; +} __packed; + +static BLOCKING_NOTIFIER_HEAD(bitland_notifier_list); + +enum bitland_notifier_actions { + BITLAND_NOTIFY_KBD_BRIGHTNESS, + BITLAND_NOTIFY_PLATFORM_PROFILE, + BITLAND_NOTIFY_HWMON, +}; + +struct bitland_fan_notify_data { + int channel; /* 0 = CPU, 1 = GPU */ + u16 speed; +}; + +struct bitland_mifs_wmi_data { + struct wmi_device *wdev; + struct mutex lock; /* Protects WMI calls */ + struct led_classdev kbd_led; + struct notifier_block notifier; + struct input_dev *input_dev; + struct device *hwmon_dev; + struct device *pp_dev; + enum platform_profile_option saved_profile; +}; + +static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data, + const struct bitland_mifs_input *input, + struct bitland_mifs_output *output) +{ + struct wmi_buffer in_buf = { .length = sizeof(*input), .data = (void *)input }; + struct wmi_buffer out_buf = { 0 }; + int ret; + + guard(mutex)(&data->lock); + + if (!output) + return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf); + + ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output)); + if (ret) + return ret; + + memcpy(output, out_buf.data, sizeof(*output)); + kfree(out_buf.data); + + return 0; +} + +static int laptop_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_GET, + .reserved2 = 0, + .function = WMI_FN_SYSTEM_PER_MODE, + }; + struct bitland_mifs_output result; + int ret; + + ret = bitland_mifs_wmi_call(data, &input, &result); + if (ret) + return ret; + + switch (result.data[0]) { + case WMI_PP_BALANCED: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case WMI_PP_PERFORMANCE: + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; + break; + case WMI_PP_QUIET: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case WMI_PP_FULL_SPEED: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: + return -EINVAL; + } + return 0; +} + +static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data) +{ + struct bitland_mifs_input input = { + .operation = WMI_METHOD_GET, + .function = WMI_FN_SYSTEM_AC_TYPE, + }; + struct bitland_mifs_output output; + int ret; + + /* Full-speed/performance mode requires DC power (not USB-C) */ + if (!power_supply_is_system_supplied()) + return -EOPNOTSUPP; + + ret = bitland_mifs_wmi_call(data, &input, &output); + if (ret) + return ret; + + if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE) + return -EOPNOTSUPP; + + return 0; +} + +static int laptop_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_SET, + .reserved2 = 0, + .function = WMI_FN_SYSTEM_PER_MODE, + }; + int ret; + u8 val; + + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + val = WMI_PP_QUIET; + break; + case PLATFORM_PROFILE_BALANCED: + val = WMI_PP_BALANCED; + break; + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + ret = bitland_check_performance_capability(data); + if (ret) + return ret; + val = WMI_PP_PERFORMANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + ret = bitland_check_performance_capability(data); + if (ret) + return ret; + val = WMI_PP_FULL_SPEED; + break; + default: + return -EOPNOTSUPP; + } + + input.payload[0] = val; + + return bitland_mifs_wmi_call(data, &input, NULL); +} + +static int platform_profile_probe(void *drvdata, unsigned long *choices) +{ + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + + return 0; +} + +static int bitland_mifs_wmi_suspend(struct device *dev) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + enum platform_profile_option profile; + int ret; + + /* Skip event device */ + if (!data->pp_dev) + return 0; + + ret = laptop_profile_get(data->pp_dev, &profile); + if (ret == 0) + data->saved_profile = profile; + + return ret; +} + +static int bitland_mifs_wmi_resume(struct device *dev) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + + /* Skip event device */ + if (!data->pp_dev) + return 0; + + dev_dbg(dev, "Resuming, restoring profile %d\n", data->saved_profile); + return laptop_profile_set(dev, data->saved_profile); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(bitland_mifs_wmi_pm_ops, + bitland_mifs_wmi_suspend, + bitland_mifs_wmi_resume); + +static const struct platform_profile_ops laptop_profile_ops = { + .probe = platform_profile_probe, + .profile_get = laptop_profile_get, + .profile_set = laptop_profile_set, +}; + +static const char *const fan_labels[] = { + "CPU", /* 0 */ + "GPU", /* 1 */ + "SYS", /* 2 */ +}; + +static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_GET, + .reserved2 = 0, + }; + struct bitland_mifs_output res; + int ret; + + switch (type) { + case hwmon_temp: + input.function = WMI_FN_CPU_THERMOMETER; + ret = bitland_mifs_wmi_call(data, &input, &res); + if (!ret) + *val = res.data[0] * MILLIDEGREE_PER_DEGREE; + return ret; + case hwmon_fan: + input.function = WMI_FN_FAN_SPEEDS; + ret = bitland_mifs_wmi_call(data, &input, &res); + if (ret) + return ret; + + switch (channel) { + case 0: /* CPU */ + *val = get_unaligned_le16(&res.data[0]); + return 0; + case 1: /* GPU */ + *val = get_unaligned_le16(&res.data[2]); + return 0; + case 2: /* SYS */ + *val = get_unaligned_le16(&res.data[6]); + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int laptop_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + if (type == hwmon_fan && attr == hwmon_fan_label) { + if (channel >= 0 && channel < ARRAY_SIZE(fan_labels)) { + *str = fan_labels[channel]; + return 0; + } + } + return -EINVAL; +} + +static const struct hwmon_channel_info *laptop_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + NULL +}; + +static const struct hwmon_ops laptop_hwmon_ops = { + .visible = 0444, + .read = laptop_hwmon_read, + .read_string = laptop_hwmon_read_string, +}; + +static const struct hwmon_chip_info laptop_chip_info = { + .ops = &laptop_hwmon_ops, + .info = laptop_hwmon_info, +}; + +static int laptop_kbd_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct bitland_mifs_wmi_data *data = + container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_SET, + .reserved2 = 0, + .function = WMI_FN_RGB_KB_BRIGHTNESS, + }; + + input.payload[0] = (u8)value; + + return bitland_mifs_wmi_call(data, &input, NULL); +} + +static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev) +{ + struct bitland_mifs_wmi_data *data = + container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_GET, + .reserved2 = 0, + .function = WMI_FN_RGB_KB_BRIGHTNESS, + }; + struct bitland_mifs_output res; + int ret; + + ret = bitland_mifs_wmi_call(data, &input, &res); + if (ret) + return ret; + + return res.data[0]; +} + +static const char *const gpu_mode_strings[] = { + "hybrid", + "discrete", + "uma", +}; + +/* GPU Mode: 0:Hybrid, 1:Discrete, 2:UMA */ +static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_GET, + .reserved2 = 0, + .function = WMI_FN_GPU_MODE, + }; + struct bitland_mifs_output res; + u8 mode_val; + int ret; + + ret = bitland_mifs_wmi_call(data, &input, &res); + if (ret) + return ret; + + mode_val = res.data[0]; + if (mode_val >= ARRAY_SIZE(gpu_mode_strings)) + return -EPROTO; + + return sysfs_emit(buf, "%s\n", gpu_mode_strings[mode_val]); +} + +static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_SET, + .reserved2 = 0, + .function = WMI_FN_GPU_MODE, + }; + int val; + int ret; + + val = sysfs_match_string(gpu_mode_strings, buf); + if (val < 0) + return -EINVAL; + + input.payload[0] = (u8)val; + + ret = bitland_mifs_wmi_call(data, &input, NULL); + if (ret) + return ret; + + return count; +} + +static const char *const kb_mode_strings[] = { + "off", /* 0 */ + "cyclic", /* 1 */ + "fixed", /* 2 */ + "custom", /* 3 */ +}; + +static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_GET, + .reserved2 = 0, + .function = WMI_FN_RGB_KB_MODE, + }; + struct bitland_mifs_output res; + u8 mode_val; + int ret; + + ret = bitland_mifs_wmi_call(data, &input, &res); + if (ret) + return ret; + + mode_val = res.data[0]; + if (mode_val >= ARRAY_SIZE(kb_mode_strings)) + return -EPROTO; + + return sysfs_emit(buf, "%s\n", kb_mode_strings[mode_val]); +} + +static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_SET, + .reserved2 = 0, + .function = WMI_FN_RGB_KB_MODE, + }; + // the wmi value (0, 1, 2 or 3) + int val; + int ret; + + val = sysfs_match_string(kb_mode_strings, buf); + if (val < 0) + return -EINVAL; + + input.payload[0] = (u8)val; + + ret = bitland_mifs_wmi_call(data, &input, NULL); + if (ret) + return ret; + + return count; +} + +/* Fan Boost: 0:Normal, 1:Max Speed */ +static ssize_t fan_boost_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); + struct bitland_mifs_input input = { + .reserved1 = 0, + .operation = WMI_METHOD_SET, + .reserved2 = 0, + .function = WMI_FN_MAX_FAN_SWITCH, + }; + bool val; + int ret; + + if (kstrtobool(buf, &val)) + return -EINVAL; + + input.payload[0] = 0; /* CPU/GPU Fan */ + input.payload[1] = val; + + ret = bitland_mifs_wmi_call(data, &input, NULL); + if (ret) + return ret; + + return count; +} + +static const DEVICE_ATTR_RW(gpu_mode); +static const DEVICE_ATTR_RW(kb_mode); +static const DEVICE_ATTR_WO(fan_boost); + +static const struct attribute *const laptop_attrs[] = { + &dev_attr_gpu_mode.attr, + &dev_attr_kb_mode.attr, + &dev_attr_fan_boost.attr, + NULL, +}; +ATTRIBUTE_GROUPS(laptop); + +static const struct key_entry bitland_mifs_wmi_keymap[] = { + { KE_KEY, WMI_EVENT_OPEN_APP, { KEY_PROG1 } }, + { KE_KEY, WMI_EVENT_CALCULATOR_START, { KEY_CALC } }, + { KE_KEY, WMI_EVENT_BROWSER_START, { KEY_WWW } }, + { KE_IGNORE, WMI_EVENT_FN_J, { KEY_RESERVED } }, + { KE_IGNORE, WMI_EVENT_FN_F, { KEY_RESERVED } }, + { KE_IGNORE, WMI_EVENT_FN_0, { KEY_RESERVED } }, + { KE_IGNORE, WMI_EVENT_FN_1, { KEY_RESERVED } }, + { KE_IGNORE, WMI_EVENT_FN_2, { KEY_RESERVED } }, + { KE_IGNORE, WMI_EVENT_FN_3, { KEY_RESERVED } }, + { KE_IGNORE, WMI_EVENT_FN_4, { KEY_RESERVED } }, + { KE_IGNORE, WMI_EVENT_FN_5, { KEY_RESERVED } }, + { KE_END, 0 } +}; + +static void bitland_notifier_unregister(void *data) +{ + struct notifier_block *nb = data; + + blocking_notifier_chain_unregister(&bitland_notifier_list, nb); +} + +static int bitland_notifier_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct bitland_mifs_wmi_data *data_ctx = + container_of(nb, struct bitland_mifs_wmi_data, notifier); + struct bitland_fan_notify_data *fan_info; + u8 *brightness; + + switch (action) { + case BITLAND_NOTIFY_KBD_BRIGHTNESS: + brightness = data; + led_classdev_notify_brightness_hw_changed(&data_ctx->kbd_led, + *brightness); + break; + case BITLAND_NOTIFY_PLATFORM_PROFILE: + platform_profile_notify(data_ctx->pp_dev); + break; + case BITLAND_NOTIFY_HWMON: + fan_info = data; + + hwmon_notify_event(data_ctx->hwmon_dev, hwmon_fan, + hwmon_fan_input, fan_info->channel); + break; + } + + return NOTIFY_OK; +} + +static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct bitland_mifs_wmi_data *drv_data; + enum bitland_wmi_device_type dev_type = + (enum bitland_wmi_device_type)(unsigned long)context; + struct led_init_data init_data = { + .devicename = DRV_NAME, + .default_label = ":" LED_FUNCTION_KBD_BACKLIGHT, + .devname_mandatory = true, + }; + int ret; + + drv_data = devm_kzalloc(&wdev->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + drv_data->wdev = wdev; + + ret = devm_mutex_init(&wdev->dev, &drv_data->lock); + if (ret) + return ret; + + dev_set_drvdata(&wdev->dev, drv_data); + + if (dev_type == BITLAND_WMI_EVENT) { + /* Register input device for hotkeys */ + drv_data->input_dev = devm_input_allocate_device(&wdev->dev); + if (!drv_data->input_dev) + return -ENOMEM; + + drv_data->input_dev->name = "Bitland MIFS WMI hotkeys"; + drv_data->input_dev->phys = "wmi/input0"; + drv_data->input_dev->id.bustype = BUS_HOST; + drv_data->input_dev->dev.parent = &wdev->dev; + + ret = sparse_keymap_setup(drv_data->input_dev, + bitland_mifs_wmi_keymap, NULL); + if (ret) + return ret; + + return input_register_device(drv_data->input_dev); + } + + /* Register platform profile */ + drv_data->pp_dev = devm_platform_profile_register(&wdev->dev, DRV_NAME, drv_data, + &laptop_profile_ops); + if (IS_ERR(drv_data->pp_dev)) + return PTR_ERR(drv_data->pp_dev); + + /* Register hwmon */ + drv_data->hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev, + "bitland_mifs", + drv_data, + &laptop_chip_info, + NULL); + if (IS_ERR(drv_data->hwmon_dev)) + return PTR_ERR(drv_data->hwmon_dev); + + /* Register keyboard LED */ + drv_data->kbd_led.max_brightness = 3; + drv_data->kbd_led.brightness_set_blocking = laptop_kbd_led_set; + drv_data->kbd_led.brightness_get = laptop_kbd_led_get; + drv_data->kbd_led.brightness = laptop_kbd_led_get(&drv_data->kbd_led); + drv_data->kbd_led.flags = LED_CORE_SUSPENDRESUME | + LED_BRIGHT_HW_CHANGED | + LED_REJECT_NAME_CONFLICT; + ret = devm_led_classdev_register_ext(&wdev->dev, &drv_data->kbd_led, &init_data); + if (ret) + return ret; + + drv_data->notifier.notifier_call = bitland_notifier_callback; + ret = blocking_notifier_chain_register(&bitland_notifier_list, &drv_data->notifier); + if (ret) + return ret; + + return devm_add_action_or_reset(&wdev->dev, + bitland_notifier_unregister, + &drv_data->notifier); +} + +static void bitland_mifs_wmi_notify(struct wmi_device *wdev, + const struct wmi_buffer *buffer) +{ + struct bitland_mifs_wmi_data *data = dev_get_drvdata(&wdev->dev); + const struct bitland_mifs_event *event = buffer->data; + struct bitland_fan_notify_data fan_data; + u8 brightness; + + /* Validate event type */ + if (event->event_type != WMI_EVENT_TYPE_HOTKEY) + return; + + dev_dbg(&wdev->dev, + "WMI event: id=0x%02x value_low=0x%02x value_high=0x%02x\n", + event->event_id, event->value_low, event->value_high); + + switch (event->event_id) { + case WMI_EVENT_KBD_BRIGHTNESS: + brightness = event->value_low; + blocking_notifier_call_chain(&bitland_notifier_list, + BITLAND_NOTIFY_KBD_BRIGHTNESS, + &brightness); + break; + + case WMI_EVENT_PERFORMANCE_PLAN: + blocking_notifier_call_chain(&bitland_notifier_list, + BITLAND_NOTIFY_PLATFORM_PROFILE, + NULL); + break; + + case WMI_EVENT_OPEN_APP: + case WMI_EVENT_CALCULATOR_START: + case WMI_EVENT_BROWSER_START: { + guard(mutex)(&data->lock); + if (!sparse_keymap_report_event(data->input_dev, + event->event_id, 1, true)) + dev_warn(&wdev->dev, "Unknown key pressed: 0x%02x\n", + event->event_id); + break; + } + + /* + * The device has 3 fans (CPU, GPU, SYS), + * but there are only the CPU and GPU fan has events + */ + case WMI_EVENT_CPU_FAN_SPEED: + case WMI_EVENT_GPU_FAN_SPEED: + if (event->event_id == WMI_EVENT_CPU_FAN_SPEED) + fan_data.channel = 0; + else + fan_data.channel = 1; + + /* Fan speed is 16-bit value (value_low is LSB, value_high is MSB) */ + fan_data.speed = (event->value_high << 8) | event->value_low; + blocking_notifier_call_chain(&bitland_notifier_list, + BITLAND_NOTIFY_HWMON, + &fan_data); + break; + + case WMI_EVENT_AIRPLANE_MODE: + case WMI_EVENT_TOUCHPAD_STATE: + case WMI_EVENT_FNLOCK_STATE: + case WMI_EVENT_KBD_MODE: + case WMI_EVENT_CAPSLOCK_STATE: + case WMI_EVENT_NUMLOCK_STATE: + case WMI_EVENT_SCROLLLOCK_STATE: + case WMI_EVENT_REFRESH_RATE: + case WMI_EVENT_WIN_KEY_LOCK: + /* These events are informational or handled by firmware */ + dev_dbg(&wdev->dev, "State change event: id=%d value=%d\n", + event->event_id, event->value_low); + break; + + default: + dev_dbg(&wdev->dev, "Unknown event: id=0x%02x value=0x%02x\n", + event->event_id, event->value_low); + break; + } +} + +static const struct wmi_device_id bitland_mifs_wmi_id_table[] = { + { BITLAND_MIFS_GUID, (void *)BITLAND_WMI_CONTROL }, + { BITLAND_EVENT_GUID, (void *)BITLAND_WMI_EVENT }, + {} +}; +MODULE_DEVICE_TABLE(wmi, bitland_mifs_wmi_id_table); + +static struct wmi_driver bitland_mifs_wmi_driver = { + .no_singleton = true, + .driver = { + .name = DRV_NAME, + .dev_groups = laptop_groups, + .pm = pm_sleep_ptr(&bitland_mifs_wmi_pm_ops), + }, + .id_table = bitland_mifs_wmi_id_table, + .min_event_size = sizeof(struct bitland_mifs_event), + .probe = bitland_mifs_wmi_probe, + .notify_new = bitland_mifs_wmi_notify, +}; + +module_wmi_driver(bitland_mifs_wmi_driver); + +MODULE_AUTHOR("Mingyou Chen <qby140326@gmail.com>"); +MODULE_DESCRIPTION("Bitland MIFS (MiInterface) WMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index e6eed3d65580..05890815795d 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -13,6 +13,7 @@ #include <linux/input.h> #include <linux/rfkill.h> #include <linux/sysfs.h> +#include <linux/platform_device.h> struct cmpc_accel { int sensitivity; @@ -38,8 +39,8 @@ struct cmpc_accel { typedef void (*input_device_init)(struct input_dev *dev); -static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name, - input_device_init idev_init) +static int cmpc_add_notify_device(struct device *dev, char *name, + input_device_init idev_init) { struct input_dev *inputdev; int error; @@ -48,22 +49,20 @@ static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name, if (!inputdev) return -ENOMEM; inputdev->name = name; - inputdev->dev.parent = &acpi->dev; + inputdev->dev.parent = dev; idev_init(inputdev); error = input_register_device(inputdev); if (error) { input_free_device(inputdev); return error; } - dev_set_drvdata(&acpi->dev, inputdev); + dev_set_drvdata(dev, inputdev); return 0; } -static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi) +static void cmpc_remove_notify_device(struct device *dev) { - struct input_dev *inputdev = dev_get_drvdata(&acpi->dev); - input_unregister_device(inputdev); - return 0; + input_unregister_device(dev_get_drvdata(dev)); } /* @@ -179,15 +178,17 @@ static acpi_status cmpc_get_accel_v4(acpi_handle handle, return status; } -static void cmpc_accel_handler_v4(struct acpi_device *dev, u32 event) +static void cmpc_accel_handler_v4(acpi_handle handle, u32 event, void *data) { + struct device *dev = data; + if (event == 0x81) { int16_t x, y, z; acpi_status status; - status = cmpc_get_accel_v4(dev->handle, &x, &y, &z); + status = cmpc_get_accel_v4(ACPI_HANDLE(dev), &x, &y, &z); if (ACPI_SUCCESS(status)) { - struct input_dev *inputdev = dev_get_drvdata(&dev->dev); + struct input_dev *inputdev = dev_get_drvdata(dev); input_report_abs(inputdev, ABS_X, x); input_report_abs(inputdev, ABS_Y, y); @@ -317,18 +318,17 @@ static struct device_attribute cmpc_accel_g_select_attr_v4 = { static int cmpc_accel_open_v4(struct input_dev *input) { - struct acpi_device *acpi; + acpi_handle handle = ACPI_HANDLE(input->dev.parent); struct cmpc_accel *accel; - acpi = to_acpi_device(input->dev.parent); accel = dev_get_drvdata(&input->dev); if (!accel) return -ENXIO; - cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); - cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select); + cmpc_accel_set_sensitivity_v4(handle, accel->sensitivity); + cmpc_accel_set_g_select_v4(handle, accel->g_select); - if (ACPI_SUCCESS(cmpc_start_accel_v4(acpi->handle))) { + if (ACPI_SUCCESS(cmpc_start_accel_v4(handle))) { accel->inputdev_state = CMPC_ACCEL_DEV_STATE_OPEN; return 0; } @@ -337,13 +337,11 @@ static int cmpc_accel_open_v4(struct input_dev *input) static void cmpc_accel_close_v4(struct input_dev *input) { - struct acpi_device *acpi; struct cmpc_accel *accel; - acpi = to_acpi_device(input->dev.parent); accel = dev_get_drvdata(&input->dev); - cmpc_stop_accel_v4(acpi->handle); + cmpc_stop_accel_v4(ACPI_HANDLE(input->dev.parent)); accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; } @@ -367,7 +365,7 @@ static int cmpc_accel_suspend_v4(struct device *dev) accel = dev_get_drvdata(&inputdev->dev); if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) - return cmpc_stop_accel_v4(to_acpi_device(dev)->handle); + return cmpc_stop_accel_v4(ACPI_HANDLE(dev)); return 0; } @@ -381,12 +379,12 @@ static int cmpc_accel_resume_v4(struct device *dev) accel = dev_get_drvdata(&inputdev->dev); if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) { - cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle, - accel->sensitivity); - cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle, - accel->g_select); + acpi_handle handle = ACPI_HANDLE(dev); - if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle))) + cmpc_accel_set_sensitivity_v4(handle, accel->sensitivity); + cmpc_accel_set_g_select_v4(handle, accel->g_select); + + if (ACPI_FAILURE(cmpc_start_accel_v4(handle))) return -EIO; } @@ -394,18 +392,30 @@ static int cmpc_accel_resume_v4(struct device *dev) } #endif -static int cmpc_accel_add_v4(struct acpi_device *acpi) +static int cmpc_accel_probe_v4(struct platform_device *pdev) { int error; struct input_dev *inputdev; struct cmpc_accel *accel; + struct acpi_device *acpi; - accel = kmalloc_obj(*accel); + acpi = ACPI_COMPANION(&pdev->dev); + if (!acpi) + return -ENODEV; + + accel = devm_kzalloc(&pdev->dev, sizeof(*accel), GFP_KERNEL); if (!accel) return -ENOMEM; accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED; + error = cmpc_add_notify_device(&pdev->dev, "cmpc_accel_v4", cmpc_accel_idev_init_v4); + if (error) + return error; + + inputdev = dev_get_drvdata(&pdev->dev); + dev_set_drvdata(&acpi->dev, inputdev); + accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity); @@ -420,30 +430,35 @@ static int cmpc_accel_add_v4(struct acpi_device *acpi) if (error) goto failed_g_select; - error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel_v4", - cmpc_accel_idev_init_v4); + error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY, + cmpc_accel_handler_v4, &pdev->dev); if (error) - goto failed_input; + goto failed_notify_handler; - inputdev = dev_get_drvdata(&acpi->dev); dev_set_drvdata(&inputdev->dev, accel); return 0; -failed_input: +failed_notify_handler: device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); failed_g_select: device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); failed_sensitivity: - kfree(accel); + dev_set_drvdata(&acpi->dev, NULL); + cmpc_remove_notify_device(&pdev->dev); return error; } -static void cmpc_accel_remove_v4(struct acpi_device *acpi) +static void cmpc_accel_remove_v4(struct platform_device *pdev) { - device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); + struct acpi_device *acpi = ACPI_COMPANION(&pdev->dev); + + acpi_dev_remove_notify_handler(acpi, ACPI_DEVICE_NOTIFY, + cmpc_accel_handler_v4); device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); - cmpc_remove_acpi_notify_device(acpi); + device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); + dev_set_drvdata(&acpi->dev, NULL); + cmpc_remove_notify_device(&pdev->dev); } static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4, @@ -454,16 +469,14 @@ static const struct acpi_device_id cmpc_accel_device_ids_v4[] = { {"", 0} }; -static struct acpi_driver cmpc_accel_acpi_driver_v4 = { - .name = "cmpc_accel_v4", - .class = "cmpc_accel_v4", - .ids = cmpc_accel_device_ids_v4, - .ops = { - .add = cmpc_accel_add_v4, - .remove = cmpc_accel_remove_v4, - .notify = cmpc_accel_handler_v4, +static struct platform_driver cmpc_accel_acpi_driver_v4 = { + .probe = cmpc_accel_probe_v4, + .remove = cmpc_accel_remove_v4, + .driver = { + .name = "cmpc_accel_v4", + .acpi_match_table = cmpc_accel_device_ids_v4, + .pm = &cmpc_accel_pm, }, - .drv.pm = &cmpc_accel_pm, }; @@ -543,15 +556,17 @@ static acpi_status cmpc_get_accel(acpi_handle handle, return status; } -static void cmpc_accel_handler(struct acpi_device *dev, u32 event) +static void cmpc_accel_handler(acpi_handle handle, u32 event, void *data) { + struct device *dev = data; + if (event == 0x81) { unsigned char x, y, z; acpi_status status; - status = cmpc_get_accel(dev->handle, &x, &y, &z); + status = cmpc_get_accel(ACPI_HANDLE(dev), &x, &y, &z); if (ACPI_SUCCESS(status)) { - struct input_dev *inputdev = dev_get_drvdata(&dev->dev); + struct input_dev *inputdev = dev_get_drvdata(dev); input_report_abs(inputdev, ABS_X, x); input_report_abs(inputdev, ABS_Y, y); @@ -618,20 +633,14 @@ static struct device_attribute cmpc_accel_sensitivity_attr = { static int cmpc_accel_open(struct input_dev *input) { - struct acpi_device *acpi; - - acpi = to_acpi_device(input->dev.parent); - if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle))) + if (ACPI_SUCCESS(cmpc_start_accel(ACPI_HANDLE(input->dev.parent)))) return 0; return -EIO; } static void cmpc_accel_close(struct input_dev *input) { - struct acpi_device *acpi; - - acpi = to_acpi_device(input->dev.parent); - cmpc_stop_accel(acpi->handle); + cmpc_stop_accel(ACPI_HANDLE(input->dev.parent)); } static void cmpc_accel_idev_init(struct input_dev *inputdev) @@ -644,16 +653,28 @@ static void cmpc_accel_idev_init(struct input_dev *inputdev) inputdev->close = cmpc_accel_close; } -static int cmpc_accel_add(struct acpi_device *acpi) +static int cmpc_accel_probe(struct platform_device *pdev) { int error; struct input_dev *inputdev; struct cmpc_accel *accel; + struct acpi_device *acpi; - accel = kmalloc_obj(*accel); + acpi = ACPI_COMPANION(&pdev->dev); + if (!acpi) + return -ENODEV; + + accel = devm_kzalloc(&pdev->dev, sizeof(*accel), GFP_KERNEL); if (!accel) return -ENOMEM; + error = cmpc_add_notify_device(&pdev->dev, "cmpc_accel", cmpc_accel_idev_init); + if (error) + return error; + + inputdev = dev_get_drvdata(&pdev->dev); + dev_set_drvdata(&acpi->dev, inputdev); + accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT; cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity); @@ -661,27 +682,32 @@ static int cmpc_accel_add(struct acpi_device *acpi) if (error) goto failed_file; - error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel", - cmpc_accel_idev_init); + error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY, + cmpc_accel_handler, &pdev->dev); if (error) - goto failed_input; + goto failed_notify_handler; - inputdev = dev_get_drvdata(&acpi->dev); dev_set_drvdata(&inputdev->dev, accel); return 0; -failed_input: +failed_notify_handler: device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); failed_file: - kfree(accel); + dev_set_drvdata(&acpi->dev, NULL); + cmpc_remove_notify_device(&pdev->dev); return error; } -static void cmpc_accel_remove(struct acpi_device *acpi) +static void cmpc_accel_remove(struct platform_device *pdev) { + struct acpi_device *acpi = ACPI_COMPANION(&pdev->dev); + + acpi_dev_remove_notify_handler(acpi, ACPI_DEVICE_NOTIFY, + cmpc_accel_handler); device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); - cmpc_remove_acpi_notify_device(acpi); + dev_set_drvdata(&acpi->dev, NULL); + cmpc_remove_notify_device(&pdev->dev); } static const struct acpi_device_id cmpc_accel_device_ids[] = { @@ -689,15 +715,13 @@ static const struct acpi_device_id cmpc_accel_device_ids[] = { {"", 0} }; -static struct acpi_driver cmpc_accel_acpi_driver = { - .name = "cmpc_accel", - .class = "cmpc_accel", - .ids = cmpc_accel_device_ids, - .ops = { - .add = cmpc_accel_add, - .remove = cmpc_accel_remove, - .notify = cmpc_accel_handler, - } +static struct platform_driver cmpc_accel_acpi_driver = { + .probe = cmpc_accel_probe, + .remove = cmpc_accel_remove, + .driver = { + .name = "cmpc_accel", + .acpi_match_table = cmpc_accel_device_ids, + }, }; @@ -722,13 +746,14 @@ static acpi_status cmpc_get_tablet(acpi_handle handle, return status; } -static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) +static void cmpc_tablet_handler(acpi_handle handle, u32 event, void *data) { + struct device *dev = data; unsigned long long val = 0; - struct input_dev *inputdev = dev_get_drvdata(&dev->dev); + struct input_dev *inputdev = dev_get_drvdata(dev); if (event == 0x81) { - if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) { + if (ACPI_SUCCESS(cmpc_get_tablet(ACPI_HANDLE(dev), &val))) { input_report_switch(inputdev, SW_TABLET_MODE, !val); input_sync(inputdev); } @@ -737,28 +762,44 @@ static void cmpc_tablet_handler(struct acpi_device *dev, u32 event) static void cmpc_tablet_idev_init(struct input_dev *inputdev) { + acpi_handle handle = ACPI_HANDLE(inputdev->dev.parent); unsigned long long val = 0; - struct acpi_device *acpi; set_bit(EV_SW, inputdev->evbit); set_bit(SW_TABLET_MODE, inputdev->swbit); - acpi = to_acpi_device(inputdev->dev.parent); - if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) { + if (ACPI_SUCCESS(cmpc_get_tablet(handle, &val))) { input_report_switch(inputdev, SW_TABLET_MODE, !val); input_sync(inputdev); } } -static int cmpc_tablet_add(struct acpi_device *acpi) +static int cmpc_tablet_probe(struct platform_device *pdev) { - return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet", - cmpc_tablet_idev_init); + struct acpi_device *acpi; + int error; + + acpi = ACPI_COMPANION(&pdev->dev); + if (!acpi) + return -ENODEV; + + error = cmpc_add_notify_device(&pdev->dev, "cmpc_tablet", cmpc_tablet_idev_init); + if (error) + return error; + + error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY, + cmpc_tablet_handler, &pdev->dev); + if (error) + cmpc_remove_notify_device(&pdev->dev); + + return error; } -static void cmpc_tablet_remove(struct acpi_device *acpi) +static void cmpc_tablet_remove(struct platform_device *pdev) { - cmpc_remove_acpi_notify_device(acpi); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, cmpc_tablet_handler); + cmpc_remove_notify_device(&pdev->dev); } #ifdef CONFIG_PM_SLEEP @@ -767,7 +808,7 @@ static int cmpc_tablet_resume(struct device *dev) struct input_dev *inputdev = dev_get_drvdata(dev); unsigned long long val = 0; - if (ACPI_SUCCESS(cmpc_get_tablet(to_acpi_device(dev)->handle, &val))) { + if (ACPI_SUCCESS(cmpc_get_tablet(ACPI_HANDLE(dev), &val))) { input_report_switch(inputdev, SW_TABLET_MODE, !val); input_sync(inputdev); } @@ -782,16 +823,14 @@ static const struct acpi_device_id cmpc_tablet_device_ids[] = { {"", 0} }; -static struct acpi_driver cmpc_tablet_acpi_driver = { - .name = "cmpc_tablet", - .class = "cmpc_tablet", - .ids = cmpc_tablet_device_ids, - .ops = { - .add = cmpc_tablet_add, - .remove = cmpc_tablet_remove, - .notify = cmpc_tablet_handler, +static struct platform_driver cmpc_tablet_acpi_driver = { + .probe = cmpc_tablet_probe, + .remove = cmpc_tablet_remove, + .driver = { + .name = "cmpc_tablet", + .acpi_match_table = cmpc_tablet_device_ids, + .pm = &cmpc_tablet_pm, }, - .drv.pm = &cmpc_tablet_pm, }; @@ -958,11 +997,16 @@ struct ipml200_dev { struct rfkill *rf; }; -static int cmpc_ipml_add(struct acpi_device *acpi) +static int cmpc_ipml_probe(struct platform_device *pdev) { int retval; struct ipml200_dev *ipml; struct backlight_properties props; + acpi_handle handle; + + handle = ACPI_HANDLE(&pdev->dev); + if (!handle) + return -ENODEV; ipml = kmalloc_obj(*ipml); if (ipml == NULL) @@ -971,16 +1015,16 @@ static int cmpc_ipml_add(struct acpi_device *acpi) memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = 7; - ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev, - acpi->handle, &cmpc_bl_ops, + ipml->bd = backlight_device_register("cmpc_bl", &pdev->dev, + handle, &cmpc_bl_ops, &props); if (IS_ERR(ipml->bd)) { retval = PTR_ERR(ipml->bd); goto out_bd; } - ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN, - &cmpc_rfkill_ops, acpi->handle); + ipml->rf = rfkill_alloc("cmpc_rfkill", &pdev->dev, RFKILL_TYPE_WLAN, + &cmpc_rfkill_ops, handle); /* * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV). * This is OK, however, since all other uses of the device will not @@ -994,7 +1038,7 @@ static int cmpc_ipml_add(struct acpi_device *acpi) } } - dev_set_drvdata(&acpi->dev, ipml); + platform_set_drvdata(pdev, ipml); return 0; out_bd: @@ -1002,11 +1046,11 @@ out_bd: return retval; } -static void cmpc_ipml_remove(struct acpi_device *acpi) +static void cmpc_ipml_remove(struct platform_device *pdev) { struct ipml200_dev *ipml; - ipml = dev_get_drvdata(&acpi->dev); + ipml = platform_get_drvdata(pdev); backlight_device_unregister(ipml->bd); @@ -1023,14 +1067,13 @@ static const struct acpi_device_id cmpc_ipml_device_ids[] = { {"", 0} }; -static struct acpi_driver cmpc_ipml_acpi_driver = { - .name = "cmpc", - .class = "cmpc", - .ids = cmpc_ipml_device_ids, - .ops = { - .add = cmpc_ipml_add, - .remove = cmpc_ipml_remove - } +static struct platform_driver cmpc_ipml_acpi_driver = { + .probe = cmpc_ipml_probe, + .remove = cmpc_ipml_remove, + .driver = { + .name = "cmpc", + .acpi_match_table = cmpc_ipml_device_ids, + }, }; @@ -1053,14 +1096,15 @@ static int cmpc_keys_codes[] = { KEY_MAX }; -static void cmpc_keys_handler(struct acpi_device *dev, u32 event) +static void cmpc_keys_handler(acpi_handle handle, u32 event, void *data) { + struct device *dev = data; struct input_dev *inputdev; int code = KEY_MAX; if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes)) code = cmpc_keys_codes[event & 0x0F]; - inputdev = dev_get_drvdata(&dev->dev); + inputdev = dev_get_drvdata(dev); input_report_key(inputdev, code, !(event & 0x10)); input_sync(inputdev); } @@ -1074,15 +1118,32 @@ static void cmpc_keys_idev_init(struct input_dev *inputdev) set_bit(cmpc_keys_codes[i], inputdev->keybit); } -static int cmpc_keys_add(struct acpi_device *acpi) +static int cmpc_keys_probe(struct platform_device *pdev) { - return cmpc_add_acpi_notify_device(acpi, "cmpc_keys", - cmpc_keys_idev_init); + struct acpi_device *acpi; + int error; + + acpi = ACPI_COMPANION(&pdev->dev); + if (!acpi) + return -ENODEV; + + error = cmpc_add_notify_device(&pdev->dev, "cmpc_keys", cmpc_keys_idev_init); + if (error) + return error; + + error = acpi_dev_install_notify_handler(acpi, ACPI_DEVICE_NOTIFY, + cmpc_keys_handler, &pdev->dev); + if (error) + cmpc_remove_notify_device(&pdev->dev); + + return error; } -static void cmpc_keys_remove(struct acpi_device *acpi) +static void cmpc_keys_remove(struct platform_device *pdev) { - cmpc_remove_acpi_notify_device(acpi); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, cmpc_keys_handler); + cmpc_remove_notify_device(&pdev->dev); } static const struct acpi_device_id cmpc_keys_device_ids[] = { @@ -1090,15 +1151,13 @@ static const struct acpi_device_id cmpc_keys_device_ids[] = { {"", 0} }; -static struct acpi_driver cmpc_keys_acpi_driver = { - .name = "cmpc_keys", - .class = "cmpc_keys", - .ids = cmpc_keys_device_ids, - .ops = { - .add = cmpc_keys_add, - .remove = cmpc_keys_remove, - .notify = cmpc_keys_handler, - } +static struct platform_driver cmpc_keys_acpi_driver = { + .probe = cmpc_keys_probe, + .remove = cmpc_keys_remove, + .driver = { + .name = "cmpc_keys", + .acpi_match_table = cmpc_keys_device_ids, + }, }; @@ -1110,39 +1169,39 @@ static int cmpc_init(void) { int r; - r = acpi_bus_register_driver(&cmpc_keys_acpi_driver); + r = platform_driver_register(&cmpc_keys_acpi_driver); if (r) goto failed_keys; - r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver); + r = platform_driver_register(&cmpc_ipml_acpi_driver); if (r) goto failed_bl; - r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver); + r = platform_driver_register(&cmpc_tablet_acpi_driver); if (r) goto failed_tablet; - r = acpi_bus_register_driver(&cmpc_accel_acpi_driver); + r = platform_driver_register(&cmpc_accel_acpi_driver); if (r) goto failed_accel; - r = acpi_bus_register_driver(&cmpc_accel_acpi_driver_v4); + r = platform_driver_register(&cmpc_accel_acpi_driver_v4); if (r) goto failed_accel_v4; return r; failed_accel_v4: - acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); + platform_driver_unregister(&cmpc_accel_acpi_driver); failed_accel: - acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); + platform_driver_unregister(&cmpc_tablet_acpi_driver); failed_tablet: - acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); + platform_driver_unregister(&cmpc_ipml_acpi_driver); failed_bl: - acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); + platform_driver_unregister(&cmpc_keys_acpi_driver); failed_keys: return r; @@ -1150,11 +1209,11 @@ failed_keys: static void cmpc_exit(void) { - acpi_bus_unregister_driver(&cmpc_accel_acpi_driver_v4); - acpi_bus_unregister_driver(&cmpc_accel_acpi_driver); - acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver); - acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver); - acpi_bus_unregister_driver(&cmpc_keys_acpi_driver); + platform_driver_unregister(&cmpc_accel_acpi_driver_v4); + platform_driver_unregister(&cmpc_accel_acpi_driver); + platform_driver_unregister(&cmpc_tablet_acpi_driver); + platform_driver_unregister(&cmpc_ipml_acpi_driver); + platform_driver_unregister(&cmpc_keys_acpi_driver); } module_init(cmpc_init); diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index 738c108c2163..9f0016056d7e 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -276,4 +276,9 @@ config DELL_WMI_SYSMAN To compile this driver as a module, choose M here: the module will be called dell-wmi-sysman. +config DELL_DW5826E_RESET + tristate "Dell DW5826e PLDR reset support" + depends on ACPI + help + This adds support for the Dell DW5826e PLDR reset via ACPI endif # X86_PLATFORM_DRIVERS_DELL diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile index c7501c25e627..470874c6e6e6 100644 --- a/drivers/platform/x86/dell/Makefile +++ b/drivers/platform/x86/dell/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o obj-$(CONFIG_DELL_WMI_DDV) += dell-wmi-ddv.o obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ +obj-$(CONFIG_DELL_DW5826E_RESET) += dell-dw5826e-reset.o diff --git a/drivers/platform/x86/dell/alienware-wmi-base.c b/drivers/platform/x86/dell/alienware-wmi-base.c index 64562b92314f..19cadf21203a 100644 --- a/drivers/platform/x86/dell/alienware-wmi-base.c +++ b/drivers/platform/x86/dell/alienware-wmi-base.c @@ -12,8 +12,13 @@ #include <linux/cleanup.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> #include <linux/dmi.h> #include <linux/leds.h> + +#include <asm/byteorder.h> + #include "alienware-wmi.h" MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); @@ -150,22 +155,22 @@ u8 alienware_interface; int alienware_wmi_command(struct wmi_device *wdev, u32 method_id, void *in_args, size_t in_size, u32 *out_data) { - struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; - struct acpi_buffer in = {in_size, in_args}; - acpi_status ret; + struct wmi_buffer out, in = { + .data = in_args, + .length = in_size, + }; + int ret; - ret = wmidev_evaluate_method(wdev, 0, method_id, &in, out_data ? &out : NULL); - if (ACPI_FAILURE(ret)) - return -EIO; + if (!out_data) + return wmidev_invoke_procedure(wdev, 0, method_id, &in); - union acpi_object *obj __free(kfree) = out.pointer; + ret = wmidev_invoke_method(wdev, 0, method_id, &in, &out, + sizeof(*out_data)); + if (ret) + return ret; - if (out_data) { - if (obj && obj->type == ACPI_TYPE_INTEGER) - *out_data = (u32)obj->integer.value; - else - return -ENOMSG; - } + __le32 *data __free(kfree) = out.data; + *out_data = le32_to_cpu(*data); return 0; } diff --git a/drivers/platform/x86/dell/dell-dw5826e-reset.c b/drivers/platform/x86/dell/dell-dw5826e-reset.c new file mode 100644 index 000000000000..64e6e9b1d7a0 --- /dev/null +++ b/drivers/platform/x86/dell/dell-dw5826e-reset.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * dell-dw5826e-reset.c - Dell DW5826e reset driver + * + * Copyright (C) 2026 Jackbb Wu <jackbb.wu@compal.com> + */ + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/uuid.h> + +#define PALC_DSM_FN_TRIGGER_PLDR 1 + +static guid_t palc_dsm_guid = + GUID_INIT(0x5a1a4bba, 0x8006, 0x487e, 0xbe, 0x0a, 0xac, 0xf5, 0xd8, 0xfd, 0xfe, 0x59); + +static int trigger_palc_pldr(struct device *dev, acpi_handle handle) +{ + union acpi_object *obj; + int ret = 0; + + obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, 1, PALC_DSM_FN_TRIGGER_PLDR, NULL); + if (!obj) { + dev_err(dev, "Failed to evaluate _DSM\n"); + return -EIO; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(dev, "Unexpected _DSM return type: %d\n", obj->type); + ret = -EINVAL; + } + + ACPI_FREE(obj); + return ret; +} + +static ssize_t wwan_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + acpi_handle handle = ACPI_HANDLE(dev); + int ret; + + ret = trigger_palc_pldr(dev, handle); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(wwan_reset); + +static struct attribute *palc_attrs[] = { + &dev_attr_wwan_reset.attr, + NULL +}; +ATTRIBUTE_GROUPS(palc); + +static int palc_probe(struct platform_device *pdev) +{ + acpi_handle handle; + + handle = ACPI_HANDLE(&pdev->dev); + if (!handle) + return -ENODEV; + + if (!acpi_check_dsm(handle, &palc_dsm_guid, 1, BIT(PALC_DSM_FN_TRIGGER_PLDR))) + return -ENODEV; + + return 0; +} + +static const struct acpi_device_id palc_acpi_ids[] = { + { "PALC0001", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, palc_acpi_ids); + +static struct platform_driver palc_driver = { + .driver = { + .name = "dell-dw5826e-reset", + .acpi_match_table = palc_acpi_ids, + .dev_groups = palc_groups, + }, + .probe = palc_probe, +}; +module_platform_driver(palc_driver); + +MODULE_DESCRIPTION("Dell DW5826e reset driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("JackBB Wu"); diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c index 57748c3ea24f..89e85c7f7132 100644 --- a/drivers/platform/x86/dell/dell-laptop.c +++ b/drivers/platform/x86/dell/dell-laptop.c @@ -224,6 +224,15 @@ static const struct dmi_system_id dell_quirks[] __initconst = { }, { .callback = dmi_matched, + .ident = "Dell Inspiron N5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N5110"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, .ident = "Dell Vostro 3360", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), @@ -2551,7 +2560,12 @@ fail_backlight: if (mute_led_registered) led_classdev_unregister(&mute_led_cdev); fail_led: + dell_laptop_unregister_notifier(&dell_laptop_notifier); + debugfs_remove_recursive(dell_laptop_dir); dell_battery_exit(); + kbd_led_exit(); + if (quirks && quirks->touchpad_led) + touchpad_led_exit(); dell_cleanup_rfkill(); fail_rfkill: platform_device_del(platform_device); diff --git a/drivers/platform/x86/dell/dell-rbtn.c b/drivers/platform/x86/dell/dell-rbtn.c index a415c432d4c3..180b8c6720e6 100644 --- a/drivers/platform/x86/dell/dell-rbtn.c +++ b/drivers/platform/x86/dell/dell-rbtn.c @@ -9,6 +9,7 @@ #include <linux/acpi.h> #include <linux/rfkill.h> #include <linux/input.h> +#include <linux/platform_device.h> #include "dell-rbtn.h" @@ -109,9 +110,9 @@ static const struct rfkill_ops rbtn_ops = { .set_block = rbtn_rfkill_set_block, }; -static int rbtn_rfkill_init(struct acpi_device *device) +static int rbtn_rfkill_init(struct device *dev) { - struct rbtn_data *rbtn_data = device->driver_data; + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); int ret; if (rbtn_data->rfkill) @@ -122,8 +123,8 @@ static int rbtn_rfkill_init(struct acpi_device *device) * but rfkill interface does not support "ANY" type * so "WLAN" type is used */ - rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, - RFKILL_TYPE_WLAN, &rbtn_ops, device); + rbtn_data->rfkill = rfkill_alloc("dell-rbtn", dev, RFKILL_TYPE_WLAN, + &rbtn_ops, ACPI_COMPANION(dev)); if (!rbtn_data->rfkill) return -ENOMEM; @@ -137,9 +138,9 @@ static int rbtn_rfkill_init(struct acpi_device *device) return 0; } -static void rbtn_rfkill_exit(struct acpi_device *device) +static void rbtn_rfkill_exit(struct device *dev) { - struct rbtn_data *rbtn_data = device->driver_data; + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); if (!rbtn_data->rfkill) return; @@ -149,12 +150,12 @@ static void rbtn_rfkill_exit(struct acpi_device *device) rbtn_data->rfkill = NULL; } -static void rbtn_rfkill_event(struct acpi_device *device) +static void rbtn_rfkill_event(struct device *dev) { - struct rbtn_data *rbtn_data = device->driver_data; + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); if (rbtn_data->rfkill) - rbtn_rfkill_query(rbtn_data->rfkill, device); + rbtn_rfkill_query(rbtn_data->rfkill, ACPI_COMPANION(dev)); } @@ -205,9 +206,9 @@ static void rbtn_input_event(struct rbtn_data *rbtn_data) * acpi driver */ -static int rbtn_add(struct acpi_device *device); -static void rbtn_remove(struct acpi_device *device); -static void rbtn_notify(struct acpi_device *device, u32 event); +static int rbtn_probe(struct platform_device *pdev); +static void rbtn_remove(struct platform_device *pdev); +static void rbtn_notify(acpi_handle handle, u32 event, void *data); static const struct acpi_device_id rbtn_ids[] = { { "DELRBTN", 0 }, @@ -251,8 +252,7 @@ static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) static int rbtn_suspend(struct device *dev) { - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = acpi_driver_data(device); + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); rbtn_data->suspended = true; @@ -261,8 +261,7 @@ static int rbtn_suspend(struct device *dev) static int rbtn_resume(struct device *dev) { - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = acpi_driver_data(device); + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); acpi_status status; /* @@ -286,14 +285,13 @@ static int rbtn_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); -static struct acpi_driver rbtn_driver = { - .name = "dell-rbtn", - .ids = rbtn_ids, - .drv.pm = &rbtn_pm_ops, - .ops = { - .add = rbtn_add, - .remove = rbtn_remove, - .notify = rbtn_notify, +static struct platform_driver rbtn_driver = { + .probe = rbtn_probe, + .remove = rbtn_remove, + .driver = { + .name = "dell-rbtn", + .acpi_match_table = rbtn_ids, + .pm = &rbtn_pm_ops, }, }; @@ -308,8 +306,7 @@ static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); static int rbtn_inc_count(struct device *dev, void *data) { - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = device->driver_data; + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); int *count = data; if (rbtn_data->type == RBTN_SLIDER) @@ -320,17 +317,16 @@ static int rbtn_inc_count(struct device *dev, void *data) static int rbtn_switch_dev(struct device *dev, void *data) { - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = device->driver_data; + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); bool enable = data; if (rbtn_data->type != RBTN_SLIDER) return 0; if (enable) - rbtn_rfkill_init(device); + rbtn_rfkill_init(dev); else - rbtn_rfkill_exit(device); + rbtn_rfkill_exit(dev); return 0; } @@ -342,7 +338,7 @@ int dell_rbtn_notifier_register(struct notifier_block *nb) int ret; count = 0; - ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, + ret = driver_for_each_device(&rbtn_driver.driver, NULL, &count, rbtn_inc_count); if (ret || count == 0) return -ENODEV; @@ -354,7 +350,7 @@ int dell_rbtn_notifier_register(struct notifier_block *nb) return ret; if (auto_remove_rfkill && first) - ret = driver_for_each_device(&rbtn_driver.drv, NULL, + ret = driver_for_each_device(&rbtn_driver.driver, NULL, (void *)false, rbtn_switch_dev); return ret; @@ -370,7 +366,7 @@ int dell_rbtn_notifier_unregister(struct notifier_block *nb) return ret; if (auto_remove_rfkill && !rbtn_chain_head.head) - ret = driver_for_each_device(&rbtn_driver.drv, NULL, + ret = driver_for_each_device(&rbtn_driver.driver, NULL, (void *)true, rbtn_switch_dev); return ret; @@ -382,30 +378,52 @@ EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); * acpi driver functions */ -static int rbtn_add(struct acpi_device *device) +static void rbtn_cleanup(struct device *dev) +{ + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + rbtn_input_exit(rbtn_data); + break; + case RBTN_SLIDER: + rbtn_rfkill_exit(dev); + break; + default: + break; + } +} + +static int rbtn_probe(struct platform_device *pdev) { struct rbtn_data *rbtn_data; + struct acpi_device *device; enum rbtn_type type; int ret = 0; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + type = rbtn_check(device); if (type == RBTN_UNKNOWN) { - dev_info(&device->dev, "Unknown device type\n"); + dev_info(&pdev->dev, "Unknown device type\n"); return -EINVAL; } - rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); + rbtn_data = devm_kzalloc(&pdev->dev, sizeof(*rbtn_data), GFP_KERNEL); if (!rbtn_data) return -ENOMEM; ret = rbtn_acquire(device, true); if (ret < 0) { - dev_err(&device->dev, "Cannot enable device\n"); + dev_err(&pdev->dev, "Cannot enable device\n"); return ret; } + platform_set_drvdata(pdev, rbtn_data); + rbtn_data->type = type; - device->driver_data = rbtn_data; switch (rbtn_data->type) { case RBTN_TOGGLE: @@ -415,51 +433,54 @@ static int rbtn_add(struct acpi_device *device) if (auto_remove_rfkill && rbtn_chain_head.head) ret = 0; else - ret = rbtn_rfkill_init(device); + ret = rbtn_rfkill_init(&pdev->dev); break; default: ret = -EINVAL; break; } if (ret) - rbtn_acquire(device, false); + goto err; + + ret = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + rbtn_notify, &pdev->dev); + if (ret) + goto err_cleanup; + return 0; + +err_cleanup: + rbtn_cleanup(&pdev->dev); +err: + rbtn_acquire(device, false); return ret; } -static void rbtn_remove(struct acpi_device *device) +static void rbtn_remove(struct platform_device *pdev) { - struct rbtn_data *rbtn_data = device->driver_data; - - switch (rbtn_data->type) { - case RBTN_TOGGLE: - rbtn_input_exit(rbtn_data); - break; - case RBTN_SLIDER: - rbtn_rfkill_exit(device); - break; - default: - break; - } + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, rbtn_notify); + rbtn_cleanup(&pdev->dev); rbtn_acquire(device, false); } -static void rbtn_notify(struct acpi_device *device, u32 event) +static void rbtn_notify(acpi_handle handle, u32 event, void *data) { - struct rbtn_data *rbtn_data = device->driver_data; + struct device *dev = data; + struct rbtn_data *rbtn_data = dev_get_drvdata(dev); /* * Some BIOSes send a notification at resume. * Ignore it to prevent unwanted input events. */ if (rbtn_data->suspended) { - dev_dbg(&device->dev, "ACPI notification ignored\n"); + dev_dbg(dev, "ACPI notification ignored\n"); return; } if (event != 0x80) { - dev_info(&device->dev, "Received unknown event (0x%x)\n", + dev_info(dev, "Received unknown event (0x%x)\n", event); return; } @@ -469,20 +490,15 @@ static void rbtn_notify(struct acpi_device *device, u32 event) rbtn_input_event(rbtn_data); break; case RBTN_SLIDER: - rbtn_rfkill_event(device); - atomic_notifier_call_chain(&rbtn_chain_head, event, device); + rbtn_rfkill_event(dev); + atomic_notifier_call_chain(&rbtn_chain_head, event, NULL); break; default: break; } } - -/* - * module functions - */ - -module_acpi_driver(rbtn_driver); +module_platform_driver(rbtn_driver); module_param(auto_remove_rfkill, bool, 0444); diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c index 4b7c54b76228..d8c55ec6acd4 100644 --- a/drivers/platform/x86/dell/dell-smbios-base.c +++ b/drivers/platform/x86/dell/dell-smbios-base.c @@ -40,7 +40,7 @@ struct smbios_device { struct list_head list; struct device *device; int priority; - int (*call_fn)(struct calling_interface_buffer *arg); + smbios_callback_fn_t call_fn; }; struct smbios_call { @@ -146,7 +146,7 @@ int dell_smbios_error(int value) } EXPORT_SYMBOL_GPL(dell_smbios_error); -int dell_smbios_register_device(struct device *d, int priority, void *call_fn) +int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn) { struct smbios_device *priv; @@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer) goto out_smbios_call; } - ret = selected->call_fn(buffer); + ret = selected->call_fn(selected->device, buffer); out_smbios_call: mutex_unlock(&smbios_mutex); diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c index 7055e2c40f34..fc4a674c692e 100644 --- a/drivers/platform/x86/dell/dell-smbios-smm.c +++ b/drivers/platform/x86/dell/dell-smbios-smm.c @@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy) } } -static int dell_smbios_smm_call(struct calling_interface_buffer *input) +static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input) { struct smi_cmd command; size_t size; @@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input) } /* When enabled this indicates that SMM won't work */ -static bool test_wsmt_enabled(void) +static bool test_wsmt_enabled(struct device *dev) { struct calling_interface_token *wsmt; @@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void) memset(buffer, 0, sizeof(struct calling_interface_buffer)); buffer->input[0] = wsmt->location; buffer->output[0] = 99; - dell_smbios_smm_call(buffer); + dell_smbios_smm_call(dev, buffer); if (buffer->output[0] == 99) return true; @@ -109,7 +109,7 @@ int init_dell_smbios_smm(void) dmi_walk(find_cmd_address, NULL); - if (test_wsmt_enabled()) { + if (test_wsmt_enabled(&platform_device->dev)) { pr_debug("Disabling due to WSMT enabled\n"); ret = -ENODEV; goto fail_wsmt; diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c index a7dca8c59d60..f9443ddfff55 100644 --- a/drivers/platform/x86/dell/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -6,21 +6,20 @@ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/dmi.h> #include <linux/fs.h> -#include <linux/list.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/rwsem.h> #include <linux/uaccess.h> #include <linux/wmi.h> #include <uapi/linux/wmi.h> #include "dell-smbios.h" #include "dell-wmi-descriptor.h" -static DEFINE_MUTEX(call_mutex); -static DEFINE_MUTEX(list_mutex); static int wmi_supported; struct misc_bios_flags_structure { @@ -32,104 +31,79 @@ struct misc_bios_flags_structure { #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" struct wmi_smbios_priv { + struct mutex call_lock; /* Protects the content of the SMBIOS buffer */ struct dell_wmi_smbios_buffer *buf; - struct list_head list; struct wmi_device *wdev; struct device *child; u64 req_buf_size; struct miscdevice char_dev; }; -static LIST_HEAD(wmi_list); -static inline struct wmi_smbios_priv *get_first_smbios_priv(void) -{ - return list_first_entry_or_null(&wmi_list, - struct wmi_smbios_priv, - list); -} +static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */ +static struct wmi_smbios_priv *chardev_priv; static int run_smbios_call(struct wmi_device *wdev) { - struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; - struct wmi_smbios_priv *priv; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - - priv = dev_get_drvdata(&wdev->dev); - input.length = priv->req_buf_size - sizeof(u64); - input.pointer = &priv->buf->std; + struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); + const struct wmi_buffer input = { + .length = priv->req_buf_size - sizeof(u64), + .data = &priv->buf->std, + }; + struct wmi_buffer output; + int ret; dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n", priv->buf->std.cmd_class, priv->buf->std.cmd_select, priv->buf->std.input[0], priv->buf->std.input[1], priv->buf->std.input[2], priv->buf->std.input[3]); - status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); - if (ACPI_FAILURE(status)) - return -EIO; - obj = (union acpi_object *)output.pointer; - if (obj->type != ACPI_TYPE_BUFFER) { - dev_dbg(&wdev->dev, "received type: %d\n", obj->type); - if (obj->type == ACPI_TYPE_INTEGER) - dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n", - obj->integer.value); - kfree(output.pointer); - return -EIO; - } - memcpy(input.pointer, obj->buffer.pointer, obj->buffer.length); + /* + * The output buffer returned by the WMI method should have at least the size + * of the input buffer. + */ + ret = wmidev_invoke_method(wdev, 0, 1, &input, &output, input.length); + if (ret < 0) + return ret; + + memcpy(input.data, output.data, input.length); + kfree(output.data); dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n", priv->buf->std.output[0], priv->buf->std.output[1], priv->buf->std.output[2], priv->buf->std.output[3]); - kfree(output.pointer); return 0; } -static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) +static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer) { - struct wmi_smbios_priv *priv; + struct wmi_smbios_priv *priv = dev_get_drvdata(dev); size_t difference; size_t size; int ret; - mutex_lock(&call_mutex); - priv = get_first_smbios_priv(); - if (!priv) { - ret = -ENODEV; - goto out_wmi_call; - } - size = sizeof(struct calling_interface_buffer); difference = priv->req_buf_size - sizeof(u64) - size; + guard(mutex)(&priv->call_lock); + memset(&priv->buf->ext, 0, difference); memcpy(&priv->buf->std, buffer, size); ret = run_smbios_call(priv->wdev); memcpy(buffer, &priv->buf->std, size); -out_wmi_call: - mutex_unlock(&call_mutex); return ret; } -static int dell_smbios_wmi_open(struct inode *inode, struct file *filp) -{ - struct wmi_smbios_priv *priv; - - priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev); - filp->private_data = priv; - - return nonseekable_open(inode, filp); -} - static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) { - struct wmi_smbios_priv *priv = filp->private_data; + guard(rwsem_read)(&chardev_lock); + + if (!chardev_priv) + return -ENODEV; - return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size, - sizeof(priv->req_buf_size)); + return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size, + sizeof(chardev_priv->req_buf_size)); } static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, @@ -173,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg; - struct wmi_smbios_priv *priv = filp->private_data; - long ret; if (cmd != DELL_WMI_SMBIOS_CMD) return -ENOIOCTLCMD; - mutex_lock(&call_mutex); - ret = dell_smbios_wmi_do_ioctl(priv, input); - mutex_unlock(&call_mutex); + guard(rwsem_read)(&chardev_lock); - return ret; + if (!chardev_priv) + return -ENODEV; + + guard(mutex)(&chardev_priv->call_lock); + + return dell_smbios_wmi_do_ioctl(chardev_priv, input); } static const struct file_operations dell_smbios_wmi_fops = { .owner = THIS_MODULE, - .open = dell_smbios_wmi_open, .read = dell_smbios_wmi_read, .unlocked_ioctl = dell_smbios_wmi_ioctl, .compat_ioctl = compat_ptr_ioctl, @@ -201,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data) misc_deregister(char_dev); } +static void dell_smbios_wmi_clear_chardev(void *data) +{ + guard(rwsem_write)(&chardev_lock); + + chardev_priv = NULL; +} + static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv) { int ret; + scoped_guard(rwsem_write, &chardev_lock) { + /* We can only have a single chardev at a time */ + if (chardev_priv) + return -EBUSY; + + chardev_priv = priv; + } + + ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL); + if (ret < 0) + return ret; + priv->char_dev.minor = MISC_DYNAMIC_MINOR; priv->char_dev.name = "wmi/dell-smbios"; priv->char_dev.fops = &dell_smbios_wmi_fops; @@ -260,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) if (!priv->buf) return -ENOMEM; - ret = dell_smbios_wmi_register_chardev(priv); + ret = devm_mutex_init(&wdev->dev, &priv->call_lock); if (ret) return ret; - ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); + ret = dell_smbios_wmi_register_chardev(priv); if (ret) return ret; - mutex_lock(&list_mutex); - list_add_tail(&priv->list, &wmi_list); - mutex_unlock(&list_mutex); - - return 0; + return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); } static void dell_smbios_wmi_remove(struct wmi_device *wdev) { - struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); - - mutex_lock(&call_mutex); - mutex_lock(&list_mutex); - list_del(&priv->list); - mutex_unlock(&list_mutex); dell_smbios_unregister_device(&wdev->dev); - mutex_unlock(&call_mutex); } static const struct wmi_device_id dell_smbios_wmi_id_table[] = { @@ -322,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { .probe = dell_smbios_wmi_probe, .remove = dell_smbios_wmi_remove, .id_table = dell_smbios_wmi_id_table, + .no_singleton = true, }; int init_dell_smbios_wmi(void) diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h index f421b8533a9e..1e64803bc00d 100644 --- a/drivers/platform/x86/dell/dell-smbios.h +++ b/drivers/platform/x86/dell/dell-smbios.h @@ -47,6 +47,8 @@ struct notifier_block; +typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer); + struct calling_interface_token { u16 tokenID; u16 location; @@ -64,7 +66,7 @@ struct calling_interface_structure { struct calling_interface_token tokens[]; } __packed; -int dell_smbios_register_device(struct device *d, int priority, void *call_fn); +int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn); void dell_smbios_unregister_device(struct device *d); int dell_smbios_error(int value); diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index e7a411ae9ca1..38a6b3ae2f75 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -13,6 +13,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/compiler_attributes.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> @@ -414,7 +415,8 @@ static void dell_wmi_switch_event(struct input_dev **subdev, input_sync(*subdev); } -static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16 *buffer, int remaining) +static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __le16 *buffer, + int remaining) { struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); const struct key_entry *key; @@ -446,15 +448,15 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16 } else if (type == 0x0011 && code == 0xe070 && remaining > 0) { dell_wmi_switch_event(&priv->tabletswitch_dev, "Dell tablet mode switch", - SW_TABLET_MODE, !buffer[0]); + SW_TABLET_MODE, !le16_to_cpu(buffer[0])); return 1; } else if (type == 0x0012 && code == 0x000c && remaining > 0) { /* Eprivacy toggle, switch to "on" key entry for on events */ - if (buffer[0] == 2) + if (le16_to_cpu(buffer[0]) == 2) key++; used = 1; } else if (type == 0x0012 && code == 0x000d && remaining > 0) { - value = (buffer[2] == 2); + value = (le16_to_cpu(buffer[0]) == 2); used = 1; } @@ -463,24 +465,17 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16 return used; } -static void dell_wmi_notify(struct wmi_device *wdev, - union acpi_object *obj) +static void dell_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *buffer) { struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - u16 *buffer_entry, *buffer_end; - acpi_size buffer_size; + __le16 *buffer_entry, *buffer_end; + size_t buffer_size; int len, i; - if (obj->type != ACPI_TYPE_BUFFER) { - pr_warn("bad response type %x\n", obj->type); - return; - } + pr_debug("Received WMI event (%*ph)\n", (int)buffer->length, buffer->data); - pr_debug("Received WMI event (%*ph)\n", - obj->buffer.length, obj->buffer.pointer); - - buffer_entry = (u16 *)obj->buffer.pointer; - buffer_size = obj->buffer.length/2; + buffer_entry = buffer->data; + buffer_size = buffer->length / 2; buffer_end = buffer_entry + buffer_size; /* @@ -496,12 +491,12 @@ static void dell_wmi_notify(struct wmi_device *wdev, * one event on devices with WMI interface version 0. */ if (priv->interface_version == 0 && buffer_entry < buffer_end) - if (buffer_end > buffer_entry + buffer_entry[0] + 1) - buffer_end = buffer_entry + buffer_entry[0] + 1; + if (buffer_end > buffer_entry + le16_to_cpu(buffer_entry[0]) + 1) + buffer_end = buffer_entry + le16_to_cpu(buffer_entry[0]) + 1; while (buffer_entry < buffer_end) { - len = buffer_entry[0]; + len = le16_to_cpu(buffer_entry[0]); if (len == 0) break; @@ -514,11 +509,11 @@ static void dell_wmi_notify(struct wmi_device *wdev, pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); - switch (buffer_entry[1]) { + switch (le16_to_cpu(buffer_entry[1])) { case 0x0000: /* One key pressed or event occurred */ if (len > 2) - dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[2], + dell_wmi_process_key(wdev, le16_to_cpu(buffer_entry[1]), + le16_to_cpu(buffer_entry[2]), buffer_entry + 3, len - 3); /* Extended data is currently ignored */ @@ -526,22 +521,23 @@ static void dell_wmi_notify(struct wmi_device *wdev, case 0x0010: /* Sequence of keys pressed */ case 0x0011: /* Sequence of events occurred */ for (i = 2; i < len; ++i) - i += dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[i], + i += dell_wmi_process_key(wdev, le16_to_cpu(buffer_entry[1]), + le16_to_cpu(buffer_entry[i]), buffer_entry + i, len - i - 1); break; case 0x0012: - if ((len > 4) && dell_privacy_process_event(buffer_entry[1], buffer_entry[3], - buffer_entry[4])) + if ((len > 4) && dell_privacy_process_event(le16_to_cpu(buffer_entry[1]), + le16_to_cpu(buffer_entry[3]), + le16_to_cpu(buffer_entry[4]))) /* dell_privacy_process_event has handled the event */; else if (len > 2) - dell_wmi_process_key(wdev, buffer_entry[1], buffer_entry[2], + dell_wmi_process_key(wdev, le16_to_cpu(buffer_entry[1]), + le16_to_cpu(buffer_entry[2]), buffer_entry + 3, len - 3); break; default: /* Unknown event */ - pr_info("Unknown WMI event type 0x%x\n", - (int)buffer_entry[1]); + pr_info("Unknown WMI event type 0x%x\n", le16_to_cpu(buffer_entry[1])); break; } @@ -825,9 +821,10 @@ static struct wmi_driver dell_wmi_driver = { .name = "dell-wmi", }, .id_table = dell_wmi_id_table, + .min_event_size = sizeof(__le16), .probe = dell_wmi_probe, .remove = dell_wmi_remove, - .notify = dell_wmi_notify, + .notify_new = dell_wmi_notify, }; static int __init dell_wmi_init(void) @@ -846,9 +843,22 @@ static int __init dell_wmi_init(void) err = dell_privacy_register_driver(); if (err) - return err; + goto out_smbios; + + err = wmi_driver_register(&dell_wmi_driver); + if (err) + goto out_privacy; + + return 0; - return wmi_driver_register(&dell_wmi_driver); +out_privacy: + dell_privacy_unregister_driver(); + +out_smbios: + if (wmi_requires_smbios_request) + dell_wmi_events_set_enabled(false); + + return err; } late_initcall(dell_wmi_init); diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index 62e3d060f038..f8903ced461b 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -7,8 +7,9 @@ #define pr_format(fmt) KBUILD_MODNAME ": " fmt -#include <linux/acpi.h> #include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/compiler_attributes.h> #include <linux/debugfs.h> #include <linux/device.h> #include <linux/device/driver.h> @@ -99,6 +100,11 @@ enum dell_ddv_method { DELL_DDV_THERMAL_SENSOR_INFORMATION = 0x22, }; +struct dell_wmi_buffer { + __le32 raw_size; + u8 raw_data[]; +} __packed; + struct fan_sensor_entry { u8 type; __le16 rpm; @@ -126,7 +132,7 @@ struct dell_wmi_ddv_sensors { bool active; struct mutex lock; /* protect caching */ unsigned long timestamp; - union acpi_object *obj; + struct dell_wmi_buffer *buffer; u64 entries; }; @@ -158,105 +164,86 @@ static const char * const fan_dock_labels[] = { "Docking Chipset Fan", }; -static int dell_wmi_ddv_query_type(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg, - union acpi_object **result, acpi_object_type type) +static int dell_wmi_ddv_query(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg, + struct wmi_buffer *output, size_t min_size) { - struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; - const struct acpi_buffer in = { - .length = sizeof(arg), - .pointer = &arg, + __le32 arg2 = cpu_to_le32(arg); + const struct wmi_buffer input = { + .length = sizeof(arg2), + .data = &arg2, }; - union acpi_object *obj; - acpi_status ret; - - ret = wmidev_evaluate_method(wdev, 0x0, method, &in, &out); - if (ACPI_FAILURE(ret)) - return -EIO; - obj = out.pointer; - if (!obj) - return -ENODATA; - - if (obj->type != type) { - kfree(obj); - return -ENOMSG; - } - - *result = obj; - - return 0; + return wmidev_invoke_method(wdev, 0x0, method, &input, output, min_size); } static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg, u32 *res) { - union acpi_object *obj; + struct wmi_buffer output; int ret; - ret = dell_wmi_ddv_query_type(wdev, method, arg, &obj, ACPI_TYPE_INTEGER); + ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(__le32)); if (ret < 0) return ret; - if (obj->integer.value <= U32_MAX) - *res = (u32)obj->integer.value; - else - ret = -ERANGE; + __le32 *argr __free(kfree) = output.data; - kfree(obj); + *res = le32_to_cpu(*argr); - return ret; + return 0; } static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, - u32 arg, union acpi_object **result) + u32 arg, struct dell_wmi_buffer **result) { - union acpi_object *obj; - u64 buffer_size; + struct wmi_buffer output; + size_t buffer_size; int ret; - ret = dell_wmi_ddv_query_type(wdev, method, arg, &obj, ACPI_TYPE_PACKAGE); + ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); if (ret < 0) return ret; - if (obj->package.count != 2 || - obj->package.elements[0].type != ACPI_TYPE_INTEGER || - obj->package.elements[1].type != ACPI_TYPE_BUFFER) { - ret = -ENOMSG; - - goto err_free; - } - - buffer_size = obj->package.elements[0].integer.value; + struct dell_wmi_buffer *buffer __free(kfree) = output.data; - if (!buffer_size) { - ret = -ENODATA; - - goto err_free; - } + if (!le32_to_cpu(buffer->raw_size)) + return -ENODATA; - if (buffer_size > obj->package.elements[1].buffer.length) { + buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); + if (buffer_size > output.length) { dev_warn(&wdev->dev, - FW_WARN "WMI buffer size (%llu) exceeds ACPI buffer size (%d)\n", - buffer_size, obj->package.elements[1].buffer.length); - ret = -EMSGSIZE; - - goto err_free; + FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", + buffer_size, output.length); + return -EMSGSIZE; } - *result = obj; + *result = no_free_ptr(buffer); return 0; - -err_free: - kfree(obj); - - return ret; } -static int dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, - u32 arg, union acpi_object **result) +static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, + u32 arg, char *buf, size_t length) { - return dell_wmi_ddv_query_type(wdev, method, arg, result, ACPI_TYPE_STRING); + struct wmi_buffer output; + size_t str_size; + int ret; + + ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct wmi_string)); + if (ret < 0) + return ret; + + struct wmi_string *str __free(kfree) = output.data; + + str_size = sizeof(*str) + le16_to_cpu(str->length); + if (str_size > output.length) { + dev_warn(&wdev->dev, + FW_WARN "WMI string size (%zu) exceeds WMI buffer size (%zu)\n", + str_size, output.length); + return -EMSGSIZE; + } + + return wmi_string_to_utf8s(str, buf, length); } /* @@ -265,28 +252,26 @@ static int dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_meth static int dell_wmi_ddv_update_sensors(struct wmi_device *wdev, enum dell_ddv_method method, struct dell_wmi_ddv_sensors *sensors, size_t entry_size) { + struct dell_wmi_buffer *buffer; u64 buffer_size, rem, entries; - union acpi_object *obj; - u8 *buffer; int ret; - if (sensors->obj) { + if (sensors->buffer) { if (time_before(jiffies, sensors->timestamp + HZ)) return 0; - kfree(sensors->obj); - sensors->obj = NULL; + kfree(sensors->buffer); + sensors->buffer = NULL; } - ret = dell_wmi_ddv_query_buffer(wdev, method, 0, &obj); + ret = dell_wmi_ddv_query_buffer(wdev, method, 0, &buffer); if (ret < 0) return ret; /* buffer format sanity check */ - buffer_size = obj->package.elements[0].integer.value; - buffer = obj->package.elements[1].buffer.pointer; + buffer_size = le32_to_cpu(buffer->raw_size); entries = div64_u64_rem(buffer_size, entry_size, &rem); - if (rem != 1 || buffer[buffer_size - 1] != 0xff) { + if (rem != 1 || buffer->raw_data[buffer_size - 1] != 0xff) { ret = -ENOMSG; goto err_free; } @@ -296,14 +281,14 @@ static int dell_wmi_ddv_update_sensors(struct wmi_device *wdev, enum dell_ddv_me goto err_free; } - sensors->obj = obj; + sensors->buffer = buffer; sensors->entries = entries; sensors->timestamp = jiffies; return 0; err_free: - kfree(obj); + kfree(buffer); return ret; } @@ -328,7 +313,7 @@ static int dell_wmi_ddv_fan_read_channel(struct dell_wmi_ddv_data *data, u32 att if (channel >= data->fans.entries) return -ENXIO; - entry = (struct fan_sensor_entry *)data->fans.obj->package.elements[1].buffer.pointer; + entry = (struct fan_sensor_entry *)data->fans.buffer->raw_data; switch (attr) { case hwmon_fan_input: *val = get_unaligned_le16(&entry[channel].rpm); @@ -354,7 +339,7 @@ static int dell_wmi_ddv_temp_read_channel(struct dell_wmi_ddv_data *data, u32 at if (channel >= data->temps.entries) return -ENXIO; - entry = (struct thermal_sensor_entry *)data->temps.obj->package.elements[1].buffer.pointer; + entry = (struct thermal_sensor_entry *)data->temps.buffer->raw_data; switch (attr) { case hwmon_temp_input: *val = entry[channel].now * 1000; @@ -411,7 +396,7 @@ static int dell_wmi_ddv_fan_read_string(struct dell_wmi_ddv_data *data, int chan if (channel >= data->fans.entries) return -ENXIO; - entry = (struct fan_sensor_entry *)data->fans.obj->package.elements[1].buffer.pointer; + entry = (struct fan_sensor_entry *)data->fans.buffer->raw_data; type = entry[channel].type; switch (type) { case 0x00 ... 0x07: @@ -442,7 +427,7 @@ static int dell_wmi_ddv_temp_read_string(struct dell_wmi_ddv_data *data, int cha if (channel >= data->temps.entries) return -ENXIO; - entry = (struct thermal_sensor_entry *)data->temps.obj->package.elements[1].buffer.pointer; + entry = (struct thermal_sensor_entry *)data->temps.buffer->raw_data; switch (entry[channel].type) { case 0x00: *str = "CPU"; @@ -553,8 +538,8 @@ static void dell_wmi_ddv_hwmon_cache_invalidate(struct dell_wmi_ddv_sensors *sen return; mutex_lock(&sensors->lock); - kfree(sensors->obj); - sensors->obj = NULL; + kfree(sensors->buffer); + sensors->buffer = NULL; mutex_unlock(&sensors->lock); } @@ -564,7 +549,7 @@ static void dell_wmi_ddv_hwmon_cache_destroy(void *data) sensors->active = false; mutex_destroy(&sensors->lock); - kfree(sensors->obj); + kfree(sensors->buffer); } static struct hwmon_channel_info *dell_wmi_ddv_channel_init(struct wmi_device *wdev, @@ -750,7 +735,7 @@ static void dell_wmi_battery_invalidate(struct dell_wmi_ddv_data *data, static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dell_wmi_ddv_data *data = container_of(attr, struct dell_wmi_ddv_data, eppid_attr); - union acpi_object *obj; + ssize_t count; u32 index; int ret; @@ -758,19 +743,19 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha if (ret < 0) return ret; - ret = dell_wmi_ddv_query_string(data->wdev, DELL_DDV_BATTERY_EPPID, index, &obj); - if (ret < 0) - return ret; - - if (obj->string.length != DELL_EPPID_LENGTH && obj->string.length != DELL_EPPID_EXT_LENGTH) - dev_info_once(&data->wdev->dev, FW_INFO "Suspicious ePPID length (%d)\n", - obj->string.length); + count = dell_wmi_ddv_query_string(data->wdev, DELL_DDV_BATTERY_EPPID, index, buf, + PAGE_SIZE); + if (count < 0) + return count; - ret = sysfs_emit(buf, "%s\n", obj->string.pointer); + if (count != DELL_EPPID_LENGTH && count != DELL_EPPID_EXT_LENGTH) + dev_info_once(&data->wdev->dev, FW_INFO "Suspicious ePPID length (%zd)\n", count); - kfree(obj); + ret = sysfs_emit_at(buf, count, "\n"); + if (ret < 0) + return ret; - return ret; + return count + ret; } static int dell_wmi_ddv_get_health(struct dell_wmi_ddv_data *data, u32 index, @@ -994,19 +979,15 @@ static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method m { struct device *dev = seq->private; struct dell_wmi_ddv_data *data = dev_get_drvdata(dev); - union acpi_object *obj; - u64 size; - u8 *buf; + struct dell_wmi_buffer *buffer; int ret; - ret = dell_wmi_ddv_query_buffer(data->wdev, method, 0, &obj); + ret = dell_wmi_ddv_query_buffer(data->wdev, method, 0, &buffer); if (ret < 0) return ret; - size = obj->package.elements[0].integer.value; - buf = obj->package.elements[1].buffer.pointer; - ret = seq_write(seq, buf, size); - kfree(obj); + ret = seq_write(seq, buffer->raw_data, le32_to_cpu(buffer->raw_size)); + kfree(buffer); return ret; } diff --git a/drivers/platform/x86/dell/dell-wmi-descriptor.c b/drivers/platform/x86/dell/dell-wmi-descriptor.c index c2a180202719..179d5678b3ad 100644 --- a/drivers/platform/x86/dell/dell-wmi-descriptor.c +++ b/drivers/platform/x86/dell/dell-wmi-descriptor.c @@ -7,14 +7,35 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/acpi.h> +#include <linux/cleanup.h> +#include <linux/compiler_attributes.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/types.h> #include <linux/wmi.h> #include "dell-wmi-descriptor.h" #define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" +/* + * Descriptor buffer is 128 byte long and contains: + * + * Name Offset Length Value + * Vendor Signature 0 4 "DELL" + * Object Signature 4 4 " WMI" + * WMI Interface Version 8 4 <version> + * WMI buffer length 12 4 <length> + * WMI hotfix number 16 4 <hotfix> + */ +struct descriptor { + /* Both fields are NOT null-terminated */ + char vendor_signature[4] __nonstring; + char object_signature[4] __nonstring; + __le32 interface_version; + __le32 buffer_length; + __le32 hotfix_number; +} __packed; + struct descriptor_priv { struct list_head list; u32 interface_version; @@ -88,77 +109,46 @@ bool dell_wmi_get_hotfix(u32 *hotfix) } EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); -/* - * Descriptor buffer is 128 byte long and contains: - * - * Name Offset Length Value - * Vendor Signature 0 4 "DELL" - * Object Signature 4 4 " WMI" - * WMI Interface Version 8 4 <version> - * WMI buffer length 12 4 <length> - * WMI hotfix number 16 4 <hotfix> - */ -static int dell_wmi_descriptor_probe(struct wmi_device *wdev, - const void *context) +static int dell_wmi_descriptor_probe(struct wmi_device *wdev, const void *context) { - union acpi_object *obj = NULL; struct descriptor_priv *priv; - u32 *buffer; + struct wmi_buffer buffer; int ret; - obj = wmidev_block_query(wdev, 0); - if (!obj) { - dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); - ret = -EIO; - goto out; - } - - if (obj->type != ACPI_TYPE_BUFFER) { - dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); - ret = -EINVAL; + ret = wmidev_query_block(wdev, 0, &buffer, sizeof(struct descriptor)); + if (ret < 0) { descriptor_valid = ret; - goto out; + return ret; } - /* Although it's not technically a failure, this would lead to - * unexpected behavior - */ - if (obj->buffer.length != 128) { - dev_err(&wdev->dev, - "Dell descriptor buffer has unexpected length (%d)\n", - obj->buffer.length); - ret = -EINVAL; - descriptor_valid = ret; - goto out; - } + struct descriptor *desc __free(kfree) = buffer.data; - buffer = (u32 *)obj->buffer.pointer; + if (strncmp(desc->vendor_signature, "DELL", sizeof(desc->vendor_signature))) { + dev_err(&wdev->dev, "Dell descriptor buffer has invalid vendor signature (%4ph)\n", + desc->vendor_signature); + descriptor_valid = -ENOMSG; + return -ENOMSG; + } - if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { - dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", - buffer); - ret = -EINVAL; - descriptor_valid = ret; - goto out; + if (strncmp(desc->object_signature, " WMI", sizeof(desc->object_signature))) { + dev_err(&wdev->dev, "Dell descriptor buffer has invalid object signature (%4ph)\n", + desc->object_signature); + descriptor_valid = -ENOMSG; + return -ENOMSG; } descriptor_valid = 0; - if (buffer[2] != 0 && buffer[2] != 1) - dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", - (unsigned long) buffer[2]); - - priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv), - GFP_KERNEL); + if (le32_to_cpu(desc->interface_version) > 1) + dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%u)\n", + le32_to_cpu(desc->interface_version)); - if (!priv) { - ret = -ENOMEM; - goto out; - } + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; - priv->interface_version = buffer[2]; - priv->size = buffer[3]; - priv->hotfix = buffer[4]; - ret = 0; + priv->interface_version = le32_to_cpu(desc->interface_version); + priv->size = le32_to_cpu(desc->buffer_length); + priv->hotfix = le32_to_cpu(desc->hotfix_number); dev_set_drvdata(&wdev->dev, priv); mutex_lock(&list_mutex); list_add_tail(&priv->list, &wmi_list); @@ -169,8 +159,6 @@ static int dell_wmi_descriptor_probe(struct wmi_device *wdev, (unsigned long) priv->size, (unsigned long) priv->hotfix); -out: - kfree(obj); return ret; } diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c index ed099a431ea4..366e5b8dc868 100644 --- a/drivers/platform/x86/dell/dell-wmi-privacy.c +++ b/drivers/platform/x86/dell/dell-wmi-privacy.c @@ -9,11 +9,14 @@ #include <linux/acpi.h> #include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/compiler_attributes.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/list.h> #include <linux/leds.h> #include <linux/module.h> +#include <linux/types.h> #include <linux/wmi.h> #include "dell-wmi-privacy.h" @@ -26,6 +29,26 @@ #define led_to_priv(c) container_of(c, struct privacy_wmi_data, cdev) /* + * Describes the Device State class exposed by BIOS which can be consumed by + * various applications interested in knowing the Privacy feature capabilities. + * class DeviceState + * { + * [key, read] string InstanceName; + * [read] boolean ReadOnly; + * + * [WmiDataId(1), read] uint32 DevicesSupported; + * 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen + * + * [WmiDataId(2), read] uint32 CurrentState; + * 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen + * }; + */ +struct device_state { + __le32 devices_supported; + __le32 current_state; +} __packed; + +/* * The wmi_list is used to store the privacy_priv struct with mutex protecting */ static LIST_HEAD(wmi_list); @@ -69,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) { struct privacy_wmi_data *priv; - mutex_lock(&list_mutex); + guard(mutex)(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, struct privacy_wmi_data, list); - mutex_unlock(&list_mutex); return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); } @@ -185,60 +208,28 @@ static struct attribute *privacy_attrs[] = { }; ATTRIBUTE_GROUPS(privacy); -/* - * Describes the Device State class exposed by BIOS which can be consumed by - * various applications interested in knowing the Privacy feature capabilities. - * class DeviceState - * { - * [key, read] string InstanceName; - * [read] boolean ReadOnly; - * - * [WmiDataId(1), read] uint32 DevicesSupported; - * 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen - * - * [WmiDataId(2), read] uint32 CurrentState; - * 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen - * }; - */ static int get_current_status(struct wmi_device *wdev) { struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev); - union acpi_object *obj_present; - u32 *buffer; - int ret = 0; + struct wmi_buffer buffer; + int ret; if (!priv) { dev_err(&wdev->dev, "dell privacy priv is NULL\n"); return -EINVAL; } + /* check privacy support features and device states */ - obj_present = wmidev_block_query(wdev, 0); - if (!obj_present) { - dev_err(&wdev->dev, "failed to read Binary MOF\n"); - return -EIO; - } + ret = wmidev_query_block(wdev, 0, &buffer, sizeof(struct device_state)); + if (ret < 0) + return ret; - if (obj_present->type != ACPI_TYPE_BUFFER) { - dev_err(&wdev->dev, "Binary MOF is not a buffer!\n"); - ret = -EIO; - goto obj_free; - } - /* Although it's not technically a failure, this would lead to - * unexpected behavior - */ - if (obj_present->buffer.length != 8) { - dev_err(&wdev->dev, "Dell privacy buffer has unexpected length (%d)!\n", - obj_present->buffer.length); - ret = -EINVAL; - goto obj_free; - } - buffer = (u32 *)obj_present->buffer.pointer; - priv->features_present = buffer[0]; - priv->last_status = buffer[1]; + struct device_state *state __free(kfree) = buffer.data; -obj_free: - kfree(obj_present); - return ret; + priv->features_present = le32_to_cpu(state->devices_supported); + priv->last_status = le32_to_cpu(state->current_state); + + return 0; } static int dell_privacy_micmute_led_set(struct led_classdev *led_cdev, diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.h b/drivers/platform/x86/dell/dell-wmi-privacy.h index 50c9b943dd47..ca242e1fe53f 100644 --- a/drivers/platform/x86/dell/dell-wmi-privacy.h +++ b/drivers/platform/x86/dell/dell-wmi-privacy.h @@ -13,7 +13,7 @@ bool dell_privacy_has_mic_mute(void); bool dell_privacy_process_event(int type, int code, int status); int dell_privacy_register_driver(void); void dell_privacy_unregister_driver(void); -#else /* CONFIG_DELL_PRIVACY */ +#else /* CONFIG_DELL_WMI_PRIVACY */ static inline bool dell_privacy_has_mic_mute(void) { return false; @@ -32,5 +32,5 @@ static inline int dell_privacy_register_driver(void) static inline void dell_privacy_unregister_driver(void) { } -#endif /* CONFIG_DELL_PRIVACY */ +#endif /* CONFIG_DELL_WMI_PRIVACY */ #endif diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c index c2dd2de6bc20..154e115af4b4 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c @@ -13,8 +13,8 @@ #define SETBIOSDEFAULTS_METHOD_ID 0x03 #define SETATTRIBUTE_METHOD_ID 0x04 -static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size, - int method_id) +static int call_biosattributes_interface(struct wmi_device *wdev, u8 *in_args, + size_t size, int method_id) { struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; struct acpi_buffer input; @@ -51,7 +51,7 @@ int set_attribute(const char *a_name, const char *a_value) { size_t security_area_size, buffer_size; size_t a_name_size, a_value_size; - char *buffer = NULL, *start; + u8 *buffer = NULL, *start; int ret; mutex_lock(&wmi_priv.mutex); @@ -84,7 +84,6 @@ int set_attribute(const char *a_name, const char *a_value) if (ret < 0) goto out; - print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, SETATTRIBUTE_METHOD_ID); @@ -109,7 +108,7 @@ int set_bios_defaults(u8 deftype) { size_t security_area_size, buffer_size; size_t integer_area_size = sizeof(u8); - char *buffer = NULL; + u8 *buffer = NULL; u8 *defaultType; int ret; diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h index 817ee7ba07ca..eb48ced55823 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h @@ -107,7 +107,7 @@ enum { static int get_##type##_instance_id(struct kobject *kobj) \ { \ int i; \ - for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ + for (i = 0; i < wmi_priv.type##_instances_count; i++) { \ if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ return i; \ } \ @@ -189,9 +189,9 @@ void exit_bios_attr_set_interface(void); int init_bios_attr_set_interface(void); int map_wmi_error(int error_code); size_t calculate_string_buffer(const char *str); -size_t calculate_security_buffer(char *authentication); -void populate_security_buffer(char *buffer, char *authentication); -ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str); +size_t calculate_security_buffer(const char *authentication); +void populate_security_buffer(u8 *buffer, const char *authentication); +ssize_t populate_string_buffer(u8 *buffer, size_t buffer_len, const char *str); int set_new_password(const char *password_type, const char *new); int init_bios_attr_pass_interface(void); void exit_bios_attr_pass_interface(void); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c index 09996fbdc707..a85639d8a076 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c @@ -6,10 +6,32 @@ * Copyright (c) 2020 Dell Inc. */ +#include <linux/bug.h> + #include "dell-wmi-sysman.h" get_instance_id(enumeration); +static int append_enum_string(char *dest, const char *src) +{ + size_t dest_len = strlen(dest); + ssize_t copied; + + if (WARN_ON_ONCE(dest_len >= MAX_BUFF)) + return -EINVAL; + + copied = strscpy(dest + dest_len, src, MAX_BUFF - dest_len); + if (copied < 0) + return -EINVAL; + + dest_len += copied; + copied = strscpy(dest + dest_len, ";", MAX_BUFF - dest_len); + if (copied < 0) + return -EINVAL; + + return 0; +} + static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int instance_id = get_enumeration_instance_id(kobj); @@ -176,9 +198,9 @@ int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, return -EINVAL; if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING)) return -EINVAL; - strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, - enumeration_obj[next_obj++].string.pointer); - strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); + if (append_enum_string(wmi_priv.enumeration_data[instance_id].dell_value_modifier, + enumeration_obj[next_obj++].string.pointer)) + return -EINVAL; } if (next_obj >= enum_property_count) @@ -193,9 +215,9 @@ int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, return -EINVAL; if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING)) return -EINVAL; - strcat(wmi_priv.enumeration_data[instance_id].possible_values, - enumeration_obj[next_obj++].string.pointer); - strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); + if (append_enum_string(wmi_priv.enumeration_data[instance_id].possible_values, + enumeration_obj[next_obj++].string.pointer)) + return -EINVAL; } return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c index e586f7957946..026c134b3f97 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c @@ -8,7 +8,7 @@ #include <linux/wmi.h> #include "dell-wmi-sysman.h" -static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size) +static int call_password_interface(struct wmi_device *wdev, u8 *in_args, size_t size) { struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; struct acpi_buffer input; @@ -42,7 +42,7 @@ int set_new_password(const char *password_type, const char *new) { size_t password_type_size, current_password_size, new_size; size_t security_area_size, buffer_size; - char *buffer = NULL, *start; + u8 *buffer = NULL, *start; char *current_password; int ret; diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index 9dddab6c9397..ab46a023cc34 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -7,10 +7,13 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/align.h> #include <linux/fs.h> #include <linux/dmi.h> #include <linux/module.h> #include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> #include <linux/wmi.h> #include "dell-wmi-sysman.h" #include "../../firmware_attributes_class.h" @@ -33,7 +36,7 @@ static int reset_option = -1; * @buffer_len: length of the destination buffer * @str: the string to insert into buffer */ -ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str) +ssize_t populate_string_buffer(u8 *buffer, size_t buffer_len, const char *str) { u16 *length = (u16 *)buffer; u16 *target = length + 1; @@ -72,13 +75,9 @@ size_t calculate_string_buffer(const char *str) * * Currently only supported type is Admin password */ -size_t calculate_security_buffer(char *authentication) +size_t calculate_security_buffer(const char *authentication) { - if (strlen(authentication) > 0) { - return (sizeof(u32) * 2) + strlen(authentication) + - strlen(authentication) % 2; - } - return sizeof(u32) * 2; + return sizeof(u32) * 2 + ALIGN(strlen(authentication), 2); } /** @@ -88,18 +87,18 @@ size_t calculate_security_buffer(char *authentication) * * Currently only supported type is PLAIN TEXT */ -void populate_security_buffer(char *buffer, char *authentication) +void populate_security_buffer(u8 *buffer, const char *authentication) { - char *auth = buffer + sizeof(u32) * 2; + size_t seclen = strlen(authentication); + u8 *auth = buffer + sizeof(u32) * 2; u32 *sectype = (u32 *) buffer; - u32 *seclen = sectype + 1; + u32 *seclenp = sectype + 1; - *sectype = strlen(authentication) > 0 ? 1 : 0; - *seclen = strlen(authentication); + *sectype = !!seclen; + *seclenp = seclen; /* plain text */ - if (strlen(authentication) > 0) - memcpy(auth, authentication, *seclen); + memcpy(auth, authentication, seclen); } /** @@ -143,17 +142,17 @@ int map_wmi_error(int error_code) */ static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { - char *start = buf; + ssize_t len = 0; int i; for (i = 0; i < MAX_TYPES; i++) { if (i == reset_option) - buf += sprintf(buf, "[%s] ", reset_types[i]); + len += sysfs_emit_at(buf, len, "[%s] ", reset_types[i]); else - buf += sprintf(buf, "%s ", reset_types[i]); + len += sysfs_emit_at(buf, len, "%s ", reset_types[i]); } - buf += sprintf(buf, "\n"); - return buf-start; + len += sysfs_emit_at(buf, len, "\n"); + return len; } /** @@ -194,7 +193,7 @@ static ssize_t reset_bios_store(struct kobject *kobj, static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", wmi_priv.pending_changes); + return sysfs_emit(buf, "%d\n", wmi_priv.pending_changes); } static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios); @@ -220,35 +219,6 @@ static int create_attributes_level_sysfs_files(void) return 0; } -static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, - char *buf) -{ - struct kobj_attribute *kattr; - ssize_t ret = -EIO; - - kattr = container_of(attr, struct kobj_attribute, attr); - if (kattr->show) - ret = kattr->show(kobj, kattr, buf); - return ret; -} - -static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr, - const char *buf, size_t count) -{ - struct kobj_attribute *kattr; - ssize_t ret = -EIO; - - kattr = container_of(attr, struct kobj_attribute, attr); - if (kattr->store) - ret = kattr->store(kobj, kattr, buf, count); - return ret; -} - -static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = { - .show = wmi_sysman_attr_show, - .store = wmi_sysman_attr_store, -}; - static void attr_name_release(struct kobject *kobj) { kfree(kobj); @@ -256,7 +226,7 @@ static void attr_name_release(struct kobject *kobj) static const struct kobj_type attr_name_ktype = { .release = attr_name_release, - .sysfs_ops = &wmi_sysman_kobj_sysfs_ops, + .sysfs_ops = &kobj_sysfs_ops, }; /** @@ -343,7 +313,7 @@ static int alloc_attributes_data(int attr_type) * destroy_attribute_objs() - Free a kset of kobjects * @kset: The kset to destroy * - * Fress kobjects created for each attribute_name under attribute type kset + * Frees kobjects created for each attribute_name under attribute type kset. */ static void destroy_attribute_objs(struct kset *kset) { diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c index eb50f1d75d0c..768b15f406d3 100644 --- a/drivers/platform/x86/dell/dell_rbu.c +++ b/drivers/platform/x86/dell/dell_rbu.c @@ -30,6 +30,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/init.h> +#include <linux/kstrtox.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/string.h> @@ -561,9 +562,9 @@ static ssize_t image_type_write(struct file *filp, struct kobject *kobj, buffer[count] = '\0'; if (strstr(buffer, "mono")) - strcpy(image_type, "mono"); + strscpy(image_type, "mono"); else if (strstr(buffer, "packet")) - strcpy(image_type, "packet"); + strscpy(image_type, "packet"); else if (strstr(buffer, "init")) { /* * If due to the user error the driver gets in a bad @@ -619,9 +620,12 @@ static ssize_t packet_size_write(struct file *filp, struct kobject *kobj, char *buffer, loff_t pos, size_t count) { unsigned long temp; + + if (kstrtoul(buffer, 10, &temp)) + return -EINVAL; + spin_lock(&rbu_data.lock); packet_empty_list(); - sscanf(buffer, "%lu", &temp); if (temp < 0xffffffff) rbu_data.packetsize = temp; diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 974f55e0b36f..8a640c25830a 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -34,8 +34,6 @@ #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" #define EEEPC_LAPTOP_FILE "eeepc" -#define EEEPC_ACPI_CLASS "hotkey" -#define EEEPC_ACPI_DEVICE_NAME "Hotkey" #define EEEPC_ACPI_HID "ASUS010" MODULE_AUTHOR("Corentin Chary, Eric Cooper"); @@ -1204,17 +1202,17 @@ static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event) pr_info("Unknown key %x pressed\n", event); } -static void eeepc_acpi_notify(struct acpi_device *device, u32 event) +static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data) { - struct eeepc_laptop *eeepc = acpi_driver_data(device); + struct eeepc_laptop *eeepc = data; + struct acpi_device *device = eeepc->device; int old_brightness, new_brightness; u16 count; if (event > ACPI_MAX_SYS_NOTIFY) return; count = eeepc->event_count[event % 128]++; - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), event, + acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event, count); /* Brightness events are special */ @@ -1360,21 +1358,25 @@ static void eeepc_enable_camera(struct eeepc_laptop *eeepc) static bool eeepc_device_present; -static int eeepc_acpi_add(struct acpi_device *device) +static int eeepc_acpi_probe(struct platform_device *pdev) { + struct acpi_device *device; struct eeepc_laptop *eeepc; int result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + pr_notice(EEEPC_LAPTOP_NAME "\n"); eeepc = kzalloc_obj(struct eeepc_laptop); if (!eeepc) return -ENOMEM; eeepc->handle = device->handle; - strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); - strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS); - device->driver_data = eeepc; eeepc->device = device; + platform_set_drvdata(pdev, eeepc); + eeepc->hotplug_disabled = hotplug_disabled; eeepc_dmi_check(eeepc); @@ -1422,9 +1424,16 @@ static int eeepc_acpi_add(struct acpi_device *device) if (result) goto fail_rfkill; + result = acpi_dev_install_notify_handler(device, ACPI_ALL_NOTIFY, + eeepc_acpi_notify, eeepc); + if (result) + goto fail_acpi_notifier; + eeepc_device_present = true; return 0; +fail_acpi_notifier: + eeepc_rfkill_exit(eeepc); fail_rfkill: eeepc_led_exit(eeepc); fail_led: @@ -1440,10 +1449,12 @@ fail_platform: return result; } -static void eeepc_acpi_remove(struct acpi_device *device) +static void eeepc_acpi_remove(struct platform_device *pdev) { - struct eeepc_laptop *eeepc = acpi_driver_data(device); + struct eeepc_laptop *eeepc = platform_get_drvdata(pdev); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_ALL_NOTIFY, eeepc_acpi_notify); eeepc_backlight_exit(eeepc); eeepc_rfkill_exit(eeepc); eeepc_input_exit(eeepc); @@ -1460,15 +1471,12 @@ static const struct acpi_device_id eeepc_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, eeepc_device_ids); -static struct acpi_driver eeepc_acpi_driver = { - .name = EEEPC_LAPTOP_NAME, - .class = EEEPC_ACPI_CLASS, - .ids = eeepc_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = eeepc_acpi_add, - .remove = eeepc_acpi_remove, - .notify = eeepc_acpi_notify, +static struct platform_driver eeepc_acpi_driver = { + .probe = eeepc_acpi_probe, + .remove = eeepc_acpi_remove, + .driver = { + .name = EEEPC_LAPTOP_NAME, + .acpi_match_table = eeepc_device_ids, }, }; @@ -1481,7 +1489,7 @@ static int __init eeepc_laptop_init(void) if (result < 0) return result; - result = acpi_bus_register_driver(&eeepc_acpi_driver); + result = platform_driver_register(&eeepc_acpi_driver); if (result < 0) goto fail_acpi_driver; @@ -1493,7 +1501,7 @@ static int __init eeepc_laptop_init(void) return 0; fail_no_device: - acpi_bus_unregister_driver(&eeepc_acpi_driver); + platform_driver_unregister(&eeepc_acpi_driver); fail_acpi_driver: platform_driver_unregister(&platform_driver); return result; @@ -1501,7 +1509,7 @@ fail_acpi_driver: static void __exit eeepc_laptop_exit(void) { - acpi_bus_unregister_driver(&eeepc_acpi_driver); + platform_driver_unregister(&eeepc_acpi_driver); platform_driver_unregister(&platform_driver); } diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 931fbcdd21b8..ea543deef68f 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -56,7 +56,6 @@ #define FUJITSU_LCD_N_LEVELS 8 -#define ACPI_FUJITSU_CLASS "fujitsu" #define ACPI_FUJITSU_BL_HID "FUJ02B1" #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" @@ -144,11 +143,11 @@ struct fujitsu_laptop { bool charge_control_supported; }; -static struct acpi_device *fext; +static struct device *fext; /* Fujitsu ACPI interface function */ -static int call_fext_func(struct acpi_device *device, +static int call_fext_func(struct device *dev, int func, int op, int feature, int state) { union acpi_object params[4] = { @@ -158,18 +157,17 @@ static int call_fext_func(struct acpi_device *device, { .integer.type = ACPI_TYPE_INTEGER, .integer.value = state } }; struct acpi_object_list arg_list = { 4, params }; + acpi_handle handle = ACPI_HANDLE(dev); unsigned long long value; acpi_status status; - status = acpi_evaluate_integer(device->handle, "FUNC", &arg_list, - &value); + status = acpi_evaluate_integer(handle, "FUNC", &arg_list, &value); if (ACPI_FAILURE(status)) { - acpi_handle_err(device->handle, "Failed to evaluate FUNC\n"); + acpi_handle_err(handle, "Failed to evaluate FUNC\n"); return -ENODEV; } - acpi_handle_debug(device->handle, - "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n", + acpi_handle_debug(handle, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n", func, op, feature, state, (int)value); return value; } @@ -251,9 +249,9 @@ static struct acpi_battery_hook battery_hook = { * These functions are intended to be called from acpi_fujitsu_laptop_add and * acpi_fujitsu_laptop_remove. */ -static int fujitsu_battery_charge_control_add(struct acpi_device *device) +static int fujitsu_battery_charge_control_add(struct device *dev) { - struct fujitsu_laptop *priv = acpi_driver_data(device); + struct fujitsu_laptop *priv = dev_get_drvdata(dev); int s006_cc_return; priv->charge_control_supported = false; @@ -274,9 +272,9 @@ static int fujitsu_battery_charge_control_add(struct acpi_device *device) return 0; } -static void fujitsu_battery_charge_control_remove(struct acpi_device *device) +static void fujitsu_battery_charge_control_remove(struct device *dev) { - struct fujitsu_laptop *priv = acpi_driver_data(device); + struct fujitsu_laptop *priv = dev_get_drvdata(dev); if (priv->charge_control_supported) battery_hook_unregister(&battery_hook); @@ -284,15 +282,16 @@ static void fujitsu_battery_charge_control_remove(struct acpi_device *device) /* Hardware access for LCD brightness control */ -static int set_lcd_level(struct acpi_device *device, int level) +static int set_lcd_level(struct device *dev, int level) { - struct fujitsu_bl *priv = acpi_driver_data(device); + struct fujitsu_bl *priv = dev_get_drvdata(dev); + acpi_handle handle = ACPI_HANDLE(dev); acpi_status status; char *method; switch (use_alt_lcd_levels) { case -1: - if (acpi_has_method(device->handle, "SBL2")) + if (acpi_has_method(handle, "SBL2")) method = "SBL2"; else method = "SBLL"; @@ -305,16 +304,14 @@ static int set_lcd_level(struct acpi_device *device, int level) break; } - acpi_handle_debug(device->handle, "set lcd level via %s [%d]\n", method, - level); + acpi_handle_debug(handle, "set lcd level via %s [%d]\n", method, level); if (level < 0 || level >= priv->max_brightness) return -EINVAL; - status = acpi_execute_simple_method(device->handle, method, level); + status = acpi_execute_simple_method(handle, method, level); if (ACPI_FAILURE(status)) { - acpi_handle_err(device->handle, "Failed to evaluate %s\n", - method); + acpi_handle_err(handle, "Failed to evaluate %s\n", method); return -ENODEV; } @@ -323,15 +320,16 @@ static int set_lcd_level(struct acpi_device *device, int level) return 0; } -static int get_lcd_level(struct acpi_device *device) +static int get_lcd_level(struct device *dev) { - struct fujitsu_bl *priv = acpi_driver_data(device); + struct fujitsu_bl *priv = dev_get_drvdata(dev); + acpi_handle handle = ACPI_HANDLE(dev); unsigned long long state = 0; acpi_status status = AE_OK; - acpi_handle_debug(device->handle, "get lcd level via GBLL\n"); + acpi_handle_debug(handle, "get lcd level via GBLL\n"); - status = acpi_evaluate_integer(device->handle, "GBLL", NULL, &state); + status = acpi_evaluate_integer(handle, "GBLL", NULL, &state); if (ACPI_FAILURE(status)) return 0; @@ -340,15 +338,16 @@ static int get_lcd_level(struct acpi_device *device) return priv->brightness_level; } -static int get_max_brightness(struct acpi_device *device) +static int get_max_brightness(struct device *dev) { - struct fujitsu_bl *priv = acpi_driver_data(device); + struct fujitsu_bl *priv = dev_get_drvdata(dev); + acpi_handle handle = ACPI_HANDLE(dev); unsigned long long state = 0; acpi_status status = AE_OK; - acpi_handle_debug(device->handle, "get max lcd level via RBLL\n"); + acpi_handle_debug(handle, "get max lcd level via RBLL\n"); - status = acpi_evaluate_integer(device->handle, "RBLL", NULL, &state); + status = acpi_evaluate_integer(handle, "RBLL", NULL, &state); if (ACPI_FAILURE(status)) return -1; @@ -361,15 +360,13 @@ static int get_max_brightness(struct acpi_device *device) static int bl_get_brightness(struct backlight_device *b) { - struct acpi_device *device = bl_get_data(b); + struct device *dev = bl_get_data(b); - return b->props.power == BACKLIGHT_POWER_OFF ? 0 : get_lcd_level(device); + return b->props.power == BACKLIGHT_POWER_OFF ? 0 : get_lcd_level(dev); } static int bl_update_status(struct backlight_device *b) { - struct acpi_device *device = bl_get_data(b); - if (fext) { if (b->props.power == BACKLIGHT_POWER_OFF) call_fext_func(fext, FUNC_BACKLIGHT, 0x1, @@ -379,7 +376,7 @@ static int bl_update_status(struct backlight_device *b) BACKLIGHT_PARAM_POWER, BACKLIGHT_ON); } - return set_lcd_level(device, b->props.brightness); + return set_lcd_level(bl_get_data(b), b->props.brightness); } static const struct backlight_ops fujitsu_bl_ops = { @@ -455,19 +452,20 @@ static const struct key_entry keymap_backlight[] = { { KE_END, 0 } }; -static int acpi_fujitsu_bl_input_setup(struct acpi_device *device) +static int acpi_fujitsu_bl_input_setup(struct device *dev) { - struct fujitsu_bl *priv = acpi_driver_data(device); + struct fujitsu_bl *priv = dev_get_drvdata(dev); + struct acpi_device *device = ACPI_COMPANION(dev); int ret; - priv->input = devm_input_allocate_device(&device->dev); + priv->input = devm_input_allocate_device(dev); if (!priv->input) return -ENOMEM; snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0", acpi_device_hid(device)); - priv->input->name = acpi_device_name(device); + priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME; priv->input->phys = priv->phys; priv->input->id.bustype = BUS_HOST; priv->input->id.product = 0x06; @@ -479,9 +477,9 @@ static int acpi_fujitsu_bl_input_setup(struct acpi_device *device) return input_register_device(priv->input); } -static int fujitsu_backlight_register(struct acpi_device *device) +static int fujitsu_backlight_register(struct device *dev) { - struct fujitsu_bl *priv = acpi_driver_data(device); + struct fujitsu_bl *priv = dev_get_drvdata(dev); const struct backlight_properties props = { .brightness = priv->brightness_level, .max_brightness = priv->max_brightness - 1, @@ -489,9 +487,8 @@ static int fujitsu_backlight_register(struct acpi_device *device) }; struct backlight_device *bd; - bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop", - &device->dev, device, - &fujitsu_bl_ops, &props); + bd = devm_backlight_device_register(dev, "fujitsu-laptop", + dev, dev, &fujitsu_bl_ops, &props); if (IS_ERR(bd)) return PTR_ERR(bd); @@ -500,65 +497,80 @@ static int fujitsu_backlight_register(struct acpi_device *device) return 0; } -static int acpi_fujitsu_bl_add(struct acpi_device *device) +/* Brightness notify */ + +static void acpi_fujitsu_bl_notify(acpi_handle handle, u32 event, void *data) +{ + struct device *dev = data; + struct fujitsu_bl *priv = dev_get_drvdata(dev); + int oldb, newb; + + if (event != ACPI_FUJITSU_NOTIFY_CODE) { + acpi_handle_info(handle, "unsupported event [0x%x]\n", event); + sparse_keymap_report_event(priv->input, -1, 1, true); + return; + } + + oldb = priv->brightness_level; + get_lcd_level(dev); + newb = priv->brightness_level; + + acpi_handle_debug(handle, "brightness button event [%i -> %i]\n", + oldb, newb); + + if (oldb == newb) + return; + + if (!disable_brightness_adjust) + set_lcd_level(dev, newb); + + sparse_keymap_report_event(priv->input, oldb < newb, 1, true); +} + +static int acpi_fujitsu_bl_probe(struct platform_device *pdev) { + struct acpi_device *device; struct fujitsu_bl *priv; int ret; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) return -ENODEV; - priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; fujitsu_bl = priv; - strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); - device->driver_data = priv; - pr_info("ACPI: %s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); + platform_set_drvdata(pdev, priv); - if (get_max_brightness(device) <= 0) + pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME, + acpi_device_bid(device)); + + if (get_max_brightness(&pdev->dev) <= 0) priv->max_brightness = FUJITSU_LCD_N_LEVELS; - get_lcd_level(device); + get_lcd_level(&pdev->dev); - ret = acpi_fujitsu_bl_input_setup(device); + ret = acpi_fujitsu_bl_input_setup(&pdev->dev); if (ret) return ret; - return fujitsu_backlight_register(device); -} + ret = fujitsu_backlight_register(&pdev->dev); + if (ret) + return ret; -/* Brightness notify */ + return acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_fujitsu_bl_notify, &pdev->dev); +} -static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event) +static void acpi_fujitsu_bl_remove(struct platform_device *pdev) { - struct fujitsu_bl *priv = acpi_driver_data(device); - int oldb, newb; - - if (event != ACPI_FUJITSU_NOTIFY_CODE) { - acpi_handle_info(device->handle, "unsupported event [0x%x]\n", - event); - sparse_keymap_report_event(priv->input, -1, 1, true); - return; - } - - oldb = priv->brightness_level; - get_lcd_level(device); - newb = priv->brightness_level; - - acpi_handle_debug(device->handle, - "brightness button event [%i -> %i]\n", oldb, newb); - - if (oldb == newb) - return; - - if (!disable_brightness_adjust) - set_lcd_level(device, newb); - - sparse_keymap_report_event(priv->input, oldb < newb, 1, true); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, acpi_fujitsu_bl_notify); } /* ACPI device for hotkey handling */ @@ -653,19 +665,20 @@ static const struct dmi_system_id fujitsu_laptop_dmi_table[] = { {} }; -static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device) +static int acpi_fujitsu_laptop_input_setup(struct device *dev) { - struct fujitsu_laptop *priv = acpi_driver_data(device); + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + struct acpi_device *device = ACPI_COMPANION(dev); int ret; - priv->input = devm_input_allocate_device(&device->dev); + priv->input = devm_input_allocate_device(dev); if (!priv->input) return -ENOMEM; snprintf(priv->phys, sizeof(priv->phys), "%s/input0", acpi_device_hid(device)); - priv->input->name = acpi_device_name(device); + priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME; priv->input->phys = priv->phys; priv->input->id.bustype = BUS_HOST; @@ -677,9 +690,9 @@ static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device) return input_register_device(priv->input); } -static int fujitsu_laptop_platform_add(struct acpi_device *device) +static int fujitsu_laptop_platform_add(struct device *dev) { - struct fujitsu_laptop *priv = acpi_driver_data(device); + struct fujitsu_laptop *priv = dev_get_drvdata(dev); int ret; priv->pf_device = platform_device_alloc("fujitsu-laptop", PLATFORM_DEVID_NONE); @@ -707,9 +720,9 @@ err_put_platform_device: return ret; } -static void fujitsu_laptop_platform_remove(struct acpi_device *device) +static void fujitsu_laptop_platform_remove(struct device *dev) { - struct fujitsu_laptop *priv = acpi_driver_data(device); + struct fujitsu_laptop *priv = dev_get_drvdata(dev); sysfs_remove_group(&priv->pf_device->dev.kobj, &fujitsu_pf_attribute_group); @@ -719,7 +732,7 @@ static void fujitsu_laptop_platform_remove(struct acpi_device *device) static int logolamp_set(struct led_classdev *cdev, enum led_brightness brightness) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); + struct device *parent = cdev->dev->parent; int poweron = FUNC_LED_ON, always = FUNC_LED_ON; int ret; @@ -729,23 +742,23 @@ static int logolamp_set(struct led_classdev *cdev, if (brightness < LED_FULL) always = FUNC_LED_OFF; - ret = call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron); + ret = call_fext_func(parent, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron); if (ret < 0) return ret; - return call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always); + return call_fext_func(parent, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always); } static enum led_brightness logolamp_get(struct led_classdev *cdev) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); + struct device *parent = cdev->dev->parent; int ret; - ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0); + ret = call_fext_func(parent, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0); if (ret == FUNC_LED_ON) return LED_FULL; - ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0); + ret = call_fext_func(parent, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0); if (ret == FUNC_LED_ON) return LED_HALF; @@ -755,22 +768,21 @@ static enum led_brightness logolamp_get(struct led_classdev *cdev) static int kblamps_set(struct led_classdev *cdev, enum led_brightness brightness) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); + struct device *parent = cdev->dev->parent; if (brightness >= LED_FULL) - return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS, + return call_fext_func(parent, FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_ON); else - return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS, + return call_fext_func(parent, FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF); } static enum led_brightness kblamps_get(struct led_classdev *cdev) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); enum led_brightness brightness = LED_OFF; - if (call_fext_func(device, + if (call_fext_func(cdev->dev->parent, FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON) brightness = LED_FULL; @@ -780,22 +792,22 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev) static int radio_led_set(struct led_classdev *cdev, enum led_brightness brightness) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); + struct device *parent = cdev->dev->parent; if (brightness >= LED_FULL) - return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON, + return call_fext_func(parent, FUNC_FLAGS, 0x5, RADIO_LED_ON, RADIO_LED_ON); else - return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON, + return call_fext_func(parent, FUNC_FLAGS, 0x5, RADIO_LED_ON, 0x0); } static enum led_brightness radio_led_get(struct led_classdev *cdev) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); + struct device *parent = cdev->dev->parent; enum led_brightness brightness = LED_OFF; - if (call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON) + if (call_fext_func(parent, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON) brightness = LED_FULL; return brightness; @@ -804,60 +816,58 @@ static enum led_brightness radio_led_get(struct led_classdev *cdev) static int eco_led_set(struct led_classdev *cdev, enum led_brightness brightness) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); + struct device *parent = cdev->dev->parent; int curr; - curr = call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0); + curr = call_fext_func(parent, FUNC_LEDS, 0x2, ECO_LED, 0x0); if (brightness >= LED_FULL) - return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED, + return call_fext_func(parent, FUNC_LEDS, 0x1, ECO_LED, curr | ECO_LED_ON); else - return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED, + return call_fext_func(parent, FUNC_LEDS, 0x1, ECO_LED, curr & ~ECO_LED_ON); } static enum led_brightness eco_led_get(struct led_classdev *cdev) { - struct acpi_device *device = to_acpi_device(cdev->dev->parent); + struct device *parent = cdev->dev->parent; enum led_brightness brightness = LED_OFF; - if (call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON) + if (call_fext_func(parent, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON) brightness = LED_FULL; return brightness; } -static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) +static int acpi_fujitsu_laptop_leds_register(struct device *dev) { - struct fujitsu_laptop *priv = acpi_driver_data(device); + struct fujitsu_laptop *priv = dev_get_drvdata(dev); struct led_classdev *led; int ret; - if (call_fext_func(device, - FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { - led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); + if (call_fext_func(dev, FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; led->name = "fujitsu::logolamp"; led->brightness_set_blocking = logolamp_set; led->brightness_get = logolamp_get; - ret = devm_led_classdev_register(&device->dev, led); + ret = devm_led_classdev_register(dev, led); if (ret) return ret; } - if ((call_fext_func(device, - FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) && - (call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) { - led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); + if ((call_fext_func(dev, FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) && + (call_fext_func(dev, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) { + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; led->name = "fujitsu::kblamps"; led->brightness_set_blocking = kblamps_set; led->brightness_get = kblamps_get; - ret = devm_led_classdev_register(&device->dev, led); + ret = devm_led_classdev_register(dev, led); if (ret) return ret; } @@ -872,7 +882,7 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) * whether given model has a radio toggle button. */ if (priv->flags_supported & BIT(17)) { - led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; @@ -880,7 +890,7 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) led->brightness_set_blocking = radio_led_set; led->brightness_get = radio_led_get; led->default_trigger = "rfkill-any"; - ret = devm_led_classdev_register(&device->dev, led); + ret = devm_led_classdev_register(dev, led); if (ret) return ret; } @@ -890,17 +900,16 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) * bit 14 seems to indicate presence of said led as well. * Confirm by testing the status. */ - if ((call_fext_func(device, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) && - (call_fext_func(device, - FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) { - led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); + if ((call_fext_func(dev, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) && + (call_fext_func(dev, FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) { + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; led->name = "fujitsu::eco_led"; led->brightness_set_blocking = eco_led_set; led->brightness_get = eco_led_get; - ret = devm_led_classdev_register(&device->dev, led); + ret = devm_led_classdev_register(dev, led); if (ret) return ret; } @@ -908,21 +917,99 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) return 0; } -static int acpi_fujitsu_laptop_add(struct acpi_device *device) +static void acpi_fujitsu_laptop_press(struct device *dev, int scancode) +{ + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + int ret; + + ret = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode, + sizeof(scancode), &priv->fifo_lock); + if (ret != sizeof(scancode)) { + dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n", + scancode); + return; + } + sparse_keymap_report_event(priv->input, scancode, 1, false); + dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n", + scancode); +} + +static void acpi_fujitsu_laptop_release(struct device *dev) +{ + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + int scancode, ret; + + while (true) { + ret = kfifo_out_locked(&priv->fifo, (unsigned char *)&scancode, + sizeof(scancode), &priv->fifo_lock); + if (ret != sizeof(scancode)) + return; + sparse_keymap_report_event(priv->input, scancode, 0, false); + dev_dbg(&priv->input->dev, + "Pop scancode from ringbuffer [0x%x]\n", scancode); + } +} + +static void acpi_fujitsu_laptop_notify(acpi_handle handle, u32 event, void *data) +{ + struct device *dev = data; + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + unsigned long flags; + int scancode, i = 0; + unsigned int irb; + + if (event != ACPI_FUJITSU_NOTIFY_CODE) { + acpi_handle_info(handle, "Unsupported event [0x%x]\n", event); + sparse_keymap_report_event(priv->input, -1, 1, true); + return; + } + + if (priv->flags_supported) + priv->flags_state = call_fext_func(dev, FUNC_FLAGS, 0x4, 0x0, 0x0); + + while ((irb = call_fext_func(dev, FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 && + i++ < MAX_HOTKEY_RINGBUFFER_SIZE) { + scancode = irb & 0x4ff; + if (sparse_keymap_entry_from_scancode(priv->input, scancode)) + acpi_fujitsu_laptop_press(dev, scancode); + else if (scancode == 0) + acpi_fujitsu_laptop_release(dev); + else + acpi_handle_info(handle, "Unknown GIRB result [%x]\n", irb); + } + + /* + * First seen on the Skylake-based Lifebook E736/E746/E756), the + * touchpad toggle hotkey (Fn+F4) is handled in software. Other models + * have since added additional "soft keys". These are reported in the + * status flags queried using FUNC_FLAGS. + */ + if (priv->flags_supported & (FLAG_SOFTKEYS)) { + flags = call_fext_func(dev, FUNC_FLAGS, 0x1, 0x0, 0x0); + flags &= (FLAG_SOFTKEYS); + for_each_set_bit(i, &flags, BITS_PER_LONG) + sparse_keymap_report_event(priv->input, BIT(i), 1, true); + } +} + +static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) { struct fujitsu_laptop *priv; + struct acpi_device *device; int ret, i = 0; - priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); - fext = device; + fext = &pdev->dev; - strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); - device->driver_data = priv; + platform_set_drvdata(pdev, priv); /* kfifo */ spin_lock_init(&priv->fifo_lock); @@ -931,17 +1018,16 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device) if (ret) return ret; - pr_info("ACPI: %s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); + pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME, + acpi_device_bid(device)); - while (call_fext_func(device, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 && + while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 && i++ < MAX_HOTKEY_RINGBUFFER_SIZE) ; /* No action, result is discarded */ acpi_handle_debug(device->handle, "Discarded %i ringbuffer entries\n", i); - priv->flags_supported = call_fext_func(device, FUNC_FLAGS, 0x0, 0x0, - 0x0); + priv->flags_supported = call_fext_func(fext, FUNC_FLAGS, 0x0, 0x0, 0x0); /* Make sure our bitmask of supported functions is cleared if the RFKILL function block is not implemented, like on the S7020. */ @@ -949,12 +1035,12 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device) priv->flags_supported = 0; if (priv->flags_supported) - priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, + priv->flags_state = call_fext_func(fext, FUNC_FLAGS, 0x4, 0x0, 0x0); /* Suspect this is a keymap of the application panel, print it */ acpi_handle_info(device->handle, "BTNI: [0x%x]\n", - call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0)); + call_fext_func(fext, FUNC_BUTTONS, 0x0, 0x0, 0x0)); /* Sync backlight power status */ if (fujitsu_bl && fujitsu_bl->bl_device && @@ -966,117 +1052,49 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device) fujitsu_bl->bl_device->props.power = BACKLIGHT_POWER_ON; } - ret = acpi_fujitsu_laptop_input_setup(device); + ret = acpi_fujitsu_laptop_input_setup(fext); if (ret) goto err_free_fifo; - ret = acpi_fujitsu_laptop_leds_register(device); + ret = acpi_fujitsu_laptop_leds_register(fext); if (ret) goto err_free_fifo; - ret = fujitsu_laptop_platform_add(device); + ret = fujitsu_laptop_platform_add(fext); if (ret) goto err_free_fifo; - ret = fujitsu_battery_charge_control_add(device); + ret = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_fujitsu_laptop_notify, fext); + if (ret) + goto err_platform_remove; + + ret = fujitsu_battery_charge_control_add(fext); if (ret < 0) pr_warn("Unable to register battery charge control: %d\n", ret); return 0; +err_platform_remove: + fujitsu_laptop_platform_remove(fext); err_free_fifo: kfifo_free(&priv->fifo); return ret; } -static void acpi_fujitsu_laptop_remove(struct acpi_device *device) +static void acpi_fujitsu_laptop_remove(struct platform_device *pdev) { - struct fujitsu_laptop *priv = acpi_driver_data(device); - - fujitsu_battery_charge_control_remove(device); - - fujitsu_laptop_platform_remove(device); - - kfifo_free(&priv->fifo); -} + struct fujitsu_laptop *priv = platform_get_drvdata(pdev); -static void acpi_fujitsu_laptop_press(struct acpi_device *device, int scancode) -{ - struct fujitsu_laptop *priv = acpi_driver_data(device); - int ret; + fujitsu_battery_charge_control_remove(&pdev->dev); - ret = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode, - sizeof(scancode), &priv->fifo_lock); - if (ret != sizeof(scancode)) { - dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n", - scancode); - return; - } - sparse_keymap_report_event(priv->input, scancode, 1, false); - dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n", - scancode); -} + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), ACPI_DEVICE_NOTIFY, + acpi_fujitsu_laptop_notify); -static void acpi_fujitsu_laptop_release(struct acpi_device *device) -{ - struct fujitsu_laptop *priv = acpi_driver_data(device); - int scancode, ret; + fujitsu_laptop_platform_remove(&pdev->dev); - while (true) { - ret = kfifo_out_locked(&priv->fifo, (unsigned char *)&scancode, - sizeof(scancode), &priv->fifo_lock); - if (ret != sizeof(scancode)) - return; - sparse_keymap_report_event(priv->input, scancode, 0, false); - dev_dbg(&priv->input->dev, - "Pop scancode from ringbuffer [0x%x]\n", scancode); - } -} - -static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event) -{ - struct fujitsu_laptop *priv = acpi_driver_data(device); - unsigned long flags; - int scancode, i = 0; - unsigned int irb; - - if (event != ACPI_FUJITSU_NOTIFY_CODE) { - acpi_handle_info(device->handle, "Unsupported event [0x%x]\n", - event); - sparse_keymap_report_event(priv->input, -1, 1, true); - return; - } - - if (priv->flags_supported) - priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, - 0x0); - - while ((irb = call_fext_func(device, - FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 && - i++ < MAX_HOTKEY_RINGBUFFER_SIZE) { - scancode = irb & 0x4ff; - if (sparse_keymap_entry_from_scancode(priv->input, scancode)) - acpi_fujitsu_laptop_press(device, scancode); - else if (scancode == 0) - acpi_fujitsu_laptop_release(device); - else - acpi_handle_info(device->handle, - "Unknown GIRB result [%x]\n", irb); - } - - /* - * First seen on the Skylake-based Lifebook E736/E746/E756), the - * touchpad toggle hotkey (Fn+F4) is handled in software. Other models - * have since added additional "soft keys". These are reported in the - * status flags queried using FUNC_FLAGS. - */ - if (priv->flags_supported & (FLAG_SOFTKEYS)) { - flags = call_fext_func(device, FUNC_FLAGS, 0x1, 0x0, 0x0); - flags &= (FLAG_SOFTKEYS); - for_each_set_bit(i, &flags, BITS_PER_LONG) - sparse_keymap_report_event(priv->input, BIT(i), 1, true); - } + kfifo_free(&priv->fifo); } /* Initialization */ @@ -1086,14 +1104,13 @@ static const struct acpi_device_id fujitsu_bl_device_ids[] = { {"", 0}, }; -static struct acpi_driver acpi_fujitsu_bl_driver = { - .name = ACPI_FUJITSU_BL_DRIVER_NAME, - .class = ACPI_FUJITSU_CLASS, - .ids = fujitsu_bl_device_ids, - .ops = { - .add = acpi_fujitsu_bl_add, - .notify = acpi_fujitsu_bl_notify, - }, +static struct platform_driver acpi_fujitsu_bl_driver = { + .probe = acpi_fujitsu_bl_probe, + .remove = acpi_fujitsu_bl_remove, + .driver = { + .name = ACPI_FUJITSU_BL_DRIVER_NAME, + .acpi_match_table = fujitsu_bl_device_ids, + }, }; static const struct acpi_device_id fujitsu_laptop_device_ids[] = { @@ -1101,15 +1118,13 @@ static const struct acpi_device_id fujitsu_laptop_device_ids[] = { {"", 0}, }; -static struct acpi_driver acpi_fujitsu_laptop_driver = { - .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME, - .class = ACPI_FUJITSU_CLASS, - .ids = fujitsu_laptop_device_ids, - .ops = { - .add = acpi_fujitsu_laptop_add, - .remove = acpi_fujitsu_laptop_remove, - .notify = acpi_fujitsu_laptop_notify, - }, +static struct platform_driver acpi_fujitsu_laptop_driver = { + .probe = acpi_fujitsu_laptop_probe, + .remove = acpi_fujitsu_laptop_remove, + .driver = { + .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME, + .acpi_match_table = fujitsu_laptop_device_ids, + }, }; static const struct acpi_device_id fujitsu_ids[] __used = { @@ -1123,7 +1138,7 @@ static int __init fujitsu_init(void) { int ret; - ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver); + ret = platform_driver_register(&acpi_fujitsu_bl_driver); if (ret) return ret; @@ -1135,7 +1150,7 @@ static int __init fujitsu_init(void) /* Register laptop driver */ - ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver); + ret = platform_driver_register(&acpi_fujitsu_laptop_driver); if (ret) goto err_unregister_platform_driver; @@ -1146,18 +1161,18 @@ static int __init fujitsu_init(void) err_unregister_platform_driver: platform_driver_unregister(&fujitsu_pf_driver); err_unregister_acpi: - acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); + platform_driver_unregister(&acpi_fujitsu_bl_driver); return ret; } static void __exit fujitsu_cleanup(void) { - acpi_bus_unregister_driver(&acpi_fujitsu_laptop_driver); + platform_driver_unregister(&acpi_fujitsu_laptop_driver); platform_driver_unregister(&fujitsu_pf_driver); - acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); + platform_driver_unregister(&acpi_fujitsu_bl_driver); pr_info("driver unloaded\n"); } diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c index 17f08ce7552d..e1f5dc86dc4d 100644 --- a/drivers/platform/x86/fujitsu-tablet.c +++ b/drivers/platform/x86/fujitsu-tablet.c @@ -18,11 +18,10 @@ #include <linux/input.h> #include <linux/delay.h> #include <linux/dmi.h> +#include <linux/platform_device.h> #define MODULENAME "fujitsu-tablet" -#define ACPI_FUJITSU_CLASS "fujitsu" - #define INVERT_TABLET_MODE_BIT 0x01 #define INVERT_DOCK_STATE_BIT 0x02 #define FORCE_TABLET_MODE_IF_UNDOCK 0x04 @@ -159,6 +158,7 @@ static struct { struct fujitsu_config config; unsigned long prev_keymask; + char name[17]; char phys[21]; int irq; @@ -442,27 +442,25 @@ static acpi_status fujitsu_walk_resources(struct acpi_resource *res, void *data) } } -static int acpi_fujitsu_add(struct acpi_device *adev) +static int acpi_fujitsu_probe(struct platform_device *pdev) { + struct acpi_device *adev; acpi_status status; int error; + adev = ACPI_COMPANION(&pdev->dev); if (!adev) - return -EINVAL; + return -ENODEV; status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS, fujitsu_walk_resources, NULL); if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) return -ENODEV; - sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev)); - sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS); - - snprintf(fujitsu.phys, sizeof(fujitsu.phys), - "%s/input0", acpi_device_hid(adev)); + scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev)); + scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev)); - error = input_fujitsu_setup(&adev->dev, - acpi_device_name(adev), fujitsu.phys); + error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys); if (error) return error; @@ -484,7 +482,7 @@ static int acpi_fujitsu_add(struct acpi_device *adev) return 0; } -static void acpi_fujitsu_remove(struct acpi_device *adev) +static void acpi_fujitsu_remove(struct platform_device *pdev) { free_irq(fujitsu.irq, fujitsu_interrupt); release_region(fujitsu.io_base, fujitsu.io_length); @@ -501,15 +499,14 @@ static int acpi_fujitsu_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(acpi_fujitsu_pm, NULL, acpi_fujitsu_resume); -static struct acpi_driver acpi_fujitsu_driver = { - .name = MODULENAME, - .class = "hotkey", - .ids = fujitsu_ids, - .ops = { - .add = acpi_fujitsu_add, - .remove = acpi_fujitsu_remove, +static struct platform_driver acpi_fujitsu_driver = { + .probe = acpi_fujitsu_probe, + .remove = acpi_fujitsu_remove, + .driver = { + .name = MODULENAME, + .acpi_match_table = fujitsu_ids, + .pm = &acpi_fujitsu_pm, }, - .drv.pm = &acpi_fujitsu_pm, }; static int __init fujitsu_module_init(void) @@ -518,7 +515,7 @@ static int __init fujitsu_module_init(void) dmi_check_system(dmi_ids); - error = acpi_bus_register_driver(&acpi_fujitsu_driver); + error = platform_driver_register(&acpi_fujitsu_driver); if (error) return error; @@ -527,7 +524,7 @@ static int __init fujitsu_module_init(void) static void __exit fujitsu_module_exit(void) { - acpi_bus_unregister_driver(&acpi_fujitsu_driver); + platform_driver_unregister(&acpi_fujitsu_driver); } module_init(fujitsu_module_init); diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 27fd6cd21529..309634c1cc20 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -85,7 +85,7 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_ * bytes. */ conv_dst_size = size; - if (size > dst_size) + if (size >= dst_size) conv_dst_size = dst_size - 1; /* @@ -661,12 +661,17 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type, int ret = 0; /* Take action appropriate to each ACPI TYPE */ - if (obj->package.count < min_elements) { - pr_err("ACPI-package does not have enough elements: %d < %d\n", - obj->package.count, min_elements); + if (obj->package.count < COMMON_ELEM_CNT) { + pr_err("ACPI-package is missing common elements: %d < %d\n", + obj->package.count, COMMON_ELEM_CNT); goto pack_attr_exit; } + if (obj->package.count < min_elements) { + pr_warn("ACPI-package has fewer elements than expected: %d < %d, parsing available elements\n", + obj->package.count, min_elements); + } + elements = obj->package.elements; /* sanity checking */ @@ -731,26 +736,31 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type, switch (attr_type) { case HPWMI_STRING_TYPE: ret = hp_populate_string_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_INTEGER_TYPE: ret = hp_populate_integer_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_ENUMERATION_TYPE: ret = hp_populate_enumeration_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_ORDERED_LIST_TYPE: ret = hp_populate_ordered_list_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_PASSWORD_TYPE: ret = hp_populate_password_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h index f1eec0e4ba07..ac57d6eab4c3 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -279,6 +279,9 @@ enum hp_wmi_data_elements { PSWD_ENCODINGS = 13, PSWD_IS_SET = 14, PSWD_ELEM_CNT = 15, + + /* Minimum elements shared by all attribute types (NAME..SECURITY_LEVEL) */ + COMMON_ELEM_CNT = SECURITY_LEVEL + 1, }; #define GET_INSTANCE_ID(type) \ @@ -401,6 +404,7 @@ int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int hp_alloc_string_data(void); void hp_exit_string_attributes(void); int hp_populate_string_package_data(union acpi_object *str_obj, + int str_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -411,6 +415,7 @@ int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int hp_alloc_integer_data(void); void hp_exit_integer_attributes(void); int hp_populate_integer_package_data(union acpi_object *integer_obj, + int integer_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -421,6 +426,7 @@ int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int hp_alloc_enumeration_data(void); void hp_exit_enumeration_attributes(void); int hp_populate_enumeration_package_data(union acpi_object *enum_obj, + int enum_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -432,6 +438,7 @@ int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr, int hp_alloc_ordered_list_data(void); void hp_exit_ordered_list_attributes(void); int hp_populate_ordered_list_package_data(union acpi_object *order_obj, + int order_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -440,6 +447,7 @@ int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id, struct kobject *attr_name_kobj); int hp_populate_password_package_data(union acpi_object *password_obj, + int password_obj_count, int instance_id, struct kobject *attr_name_kobj); int hp_alloc_password_data(void); diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index af4d1920d488..72a4c2107d7e 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -96,8 +96,8 @@ int hp_alloc_enumeration_data(void) if (!bioscfg_drv.enumeration_instances_count) return -EINVAL; - bioscfg_drv.enumeration_data = kvcalloc(bioscfg_drv.enumeration_instances_count, - sizeof(*bioscfg_drv.enumeration_data), GFP_KERNEL); + bioscfg_drv.enumeration_data = kvzalloc_objs(*bioscfg_drv.enumeration_data, + bioscfg_drv.enumeration_instances_count); if (!bioscfg_drv.enumeration_data) { bioscfg_drv.enumeration_instances_count = 0; @@ -163,10 +163,11 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum /* Check that both expected and read object type match */ if (expected_enum_types[eloc] != enum_obj[elem].type) { - pr_err("Error expected type %d for elem %d, but got type %d instead\n", - expected_enum_types[eloc], elem, enum_obj[elem].type); + pr_warn("Unexpected element type at elem %d: expected %d, got %d, skipping\n", + elem, expected_enum_types[eloc], enum_obj[elem].type); kfree(str_value); - return -EIO; + str_value = NULL; + continue; } /* Assign appropriate element value to corresponding field */ @@ -227,6 +228,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: @@ -280,6 +283,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum kfree(str_value); str_value = NULL; } + if (size) + elem += (size < MAX_VALUES_SIZE ? size : MAX_VALUES_SIZE) - 1; break; default: pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem); @@ -300,10 +305,12 @@ exit_enumeration_package: * Populate all properties of an instance under enumeration attribute * * @enum_obj: ACPI object with enumeration data + * @enum_obj_count: Number of elements in @enum_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ int hp_populate_enumeration_package_data(union acpi_object *enum_obj, + int enum_obj_count, int instance_id, struct kobject *attr_name_kobj) { @@ -312,7 +319,7 @@ int hp_populate_enumeration_package_data(union acpi_object *enum_obj, enum_data->attr_name_kobj = attr_name_kobj; hp_populate_enumeration_elements_from_package(enum_obj, - enum_obj->package.count, + enum_obj_count, instance_id); hp_update_attribute_permissions(enum_data->common.is_readonly, &enumeration_current_val); diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c index d96e160953e3..a27907066448 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -243,6 +243,8 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_ kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: @@ -275,10 +277,12 @@ exit_integer_package: * Populate all properties of an instance under integer attribute * * @integer_obj: ACPI object with integer data + * @integer_obj_count: Number of elements in @integer_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ int hp_populate_integer_package_data(union acpi_object *integer_obj, + int integer_obj_count, int instance_id, struct kobject *attr_name_kobj) { @@ -286,7 +290,7 @@ int hp_populate_integer_package_data(union acpi_object *integer_obj, integer_data->attr_name_kobj = attr_name_kobj; hp_populate_integer_elements_from_package(integer_obj, - integer_obj->package.count, + integer_obj_count, instance_id); hp_update_attribute_permissions(integer_data->common.is_readonly, &integer_current_val); diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c index f09489a085c8..5bf8d40bdf81 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -145,7 +145,7 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord if (!order_obj) return -EINVAL; - for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) { + for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT && elem < order_obj_count; elem++, eloc++) { switch (order_obj[elem].type) { case ACPI_TYPE_STRING: @@ -232,6 +232,8 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: @@ -261,7 +263,9 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord * Ordered list data is stored in hex and comma separated format * Convert the data and split it to show each element */ - ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len); + ret = hp_convert_hexstr_to_str(order_obj[elem].string.pointer, + order_obj[elem].string.length, + &tmpstr, &tmp_len); if (ret) goto exit_list; @@ -298,10 +302,12 @@ exit_list: * Populate all properties of an instance under ordered_list attribute * * @order_obj: ACPI object with ordered_list data + * @order_obj_count: Number of elements in @order_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ -int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id, +int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int order_obj_count, + int instance_id, struct kobject *attr_name_kobj) { struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id]; @@ -309,7 +315,7 @@ int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int inst ordered_list_data->attr_name_kobj = attr_name_kobj; hp_populate_ordered_list_elements_from_package(order_obj, - order_obj->package.count, + order_obj_count, instance_id); hp_update_attribute_permissions(ordered_list_data->common.is_readonly, &ordered_list_current_val); diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index 4d79eb8056a5..a9e178637416 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -66,7 +66,7 @@ static int validate_password_input(int instance_id, const char *buf) struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; length = strlen(buf); - if (buf[length - 1] == '\n') + if (length > 0 && buf[length - 1] == '\n') length--; if (length > MAX_PASSWD_SIZE) @@ -123,7 +123,7 @@ static ssize_t new_password_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { - return store_password_instance(kobj, buf, count, true); + return store_password_instance(kobj, buf, count, false); } static struct kobj_attribute password_new_password = __ATTR_WO(new_password); @@ -321,6 +321,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: password_data->common.security_level = int_value; @@ -351,6 +353,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor case PSWD_ENCODINGS: size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE); for (pos_values = 0; pos_values < size; pos_values++) { + if (elem + pos_values >= password_obj_count) { + pr_err("Error elem-objects package is too small\n"); + return -EINVAL; + } + ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer, password_obj[elem + pos_values].string.length, &str_value, &value_len); @@ -362,6 +369,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor str_value = NULL; } + if (size) + elem += size - 1; break; case PSWD_IS_SET: password_data->is_enabled = int_value; @@ -385,10 +394,12 @@ exit_package: * Populate all properties for an instance under password attribute * * @password_obj: ACPI object with password data + * @password_obj_count: Number of elements in @password_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ -int hp_populate_password_package_data(union acpi_object *password_obj, int instance_id, +int hp_populate_password_package_data(union acpi_object *password_obj, int password_obj_count, + int instance_id, struct kobject *attr_name_kobj) { struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; @@ -396,7 +407,7 @@ int hp_populate_password_package_data(union acpi_object *password_obj, int insta password_data->attr_name_kobj = attr_name_kobj; hp_populate_password_elements_from_package(password_obj, - password_obj->package.count, + password_obj_count, instance_id); hp_friendly_user_name_update(password_data->common.path, diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c index 2b00a14792e9..4d94e48c1a4c 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c @@ -238,7 +238,7 @@ static ssize_t sk_store(struct kobject *kobj, ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK, HPWMI_SECUREPLATFORM, (void *)bioscfg_drv.spm_data.signing_key, - count, 0); + length, 0); if (!ret) { bioscfg_drv.spm_data.mechanism = SIGNING_KEY; @@ -274,7 +274,7 @@ static ssize_t kek_store(struct kobject *kobj, ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK, HPWMI_SECUREPLATFORM, (void *)bioscfg_drv.spm_data.endorsement_key, - count, 0); + length, 0); if (!ret) { bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY; diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c index fe5a9a3a4ef1..f3dfba270f7e 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -233,6 +233,8 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: @@ -263,10 +265,12 @@ exit_string_package: * Populate all properties of an instance under string attribute * * @string_obj: ACPI object with string data + * @string_obj_count: Number of elements in @string_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ int hp_populate_string_package_data(union acpi_object *string_obj, + int string_obj_count, int instance_id, struct kobject *attr_name_kobj) { @@ -275,7 +279,7 @@ int hp_populate_string_package_data(union acpi_object *string_obj, string_data->attr_name_kobj = attr_name_kobj; hp_populate_string_elements_from_package(string_obj, - string_obj->package.count, + string_obj_count, instance_id); hp_update_attribute_permissions(string_data->common.is_readonly, diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 68ede7e5757a..615b4cf6fc45 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -14,6 +14,8 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/array_size.h> +#include <linux/bits.h> #include <linux/cleanup.h> #include <linux/compiler_attributes.h> #include <linux/dmi.h> @@ -23,6 +25,7 @@ #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/kernel.h> +#include <linux/kstrtox.h> #include <linux/limits.h> #include <linux/minmax.h> #include <linux/module.h> @@ -48,6 +51,7 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4"); enum hp_ec_offsets { HP_EC_OFFSET_UNKNOWN = 0x00, + HP_NO_THERMAL_PROFILE_OFFSET = 0x01, HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET = 0x59, HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET = 0x62, HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET = 0x63, @@ -57,8 +61,12 @@ enum hp_ec_offsets { #define HP_FAN_SPEED_AUTOMATIC 0x00 #define HP_POWER_LIMIT_DEFAULT 0x00 #define HP_POWER_LIMIT_NO_CHANGE 0xFF - -#define ACPI_AC_CLASS "ac_adapter" +#define HPWMI_MUX_MODE_UMA BIT(0) +#define HPWMI_MUX_MODE_HYBRID BIT(1) +#define HPWMI_MUX_MODE_DISCRETE BIT(2) +#define HPWMI_MUX_MODE_OPTIMUS BIT(3) +#define HPWMI_MUX_MODE_MASK GENMASK(6, 0) +#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE) #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required @@ -120,11 +128,66 @@ static const struct thermal_profile_params omen_v1_thermal_params = { .ec_tp_offset = HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET, }; -/* - * A generic pointer for the currently-active board's thermal profile - * parameters. - */ -static struct thermal_profile_params *active_thermal_profile_params; +static const struct thermal_profile_params omen_v1_legacy_thermal_params = { + .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE, + .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, + .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, + .ec_tp_offset = HP_OMEN_EC_THERMAL_PROFILE_OFFSET, +}; + +static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { + .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE, + .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, + .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT, + .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, +}; + +struct hp_wmi_fan_profile_params { + int (*get_fan_speed)(int fan); + bool fan_table; +}; + +struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; + const struct hp_wmi_fan_profile_params *fan_profile; +}; + +static int hp_wmi_get_fan_speed_victus_s(int fan); + +static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { + .get_fan_speed = hp_wmi_get_fan_speed_victus_s, + .fan_table = true, +}; + +static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, + .fan_profile = &victus_s_fan_profile_params, +}; + +static const struct hp_wmi_board_params *active_board_params; + +static const struct thermal_profile_params *hp_wmi_thermal_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->thermal_profile; +} /* DMI board names of devices that should use the omen specific path for * thermal profiles. @@ -144,8 +207,9 @@ static const char * const omen_thermal_profile_boards[] = { "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", "88F7", "88FD", "88FE", "88FF", "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", - "8A15", "8A42", + "8A15", "8A42", "8A43", "8BAD", + "8C58", "8E41", }; @@ -173,43 +237,111 @@ static const char * const victus_thermal_profile_boards[] = { "8A25", }; -/* DMI Board names of Victus 16-r and Victus 16-s laptops */ -static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { +/* DMI board-specific feature data for Omen and Victus laptops. */ +static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, + .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, + .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") }, + .driver_data = (void *)&omen_v1_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, + .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, + .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, + .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, + .driver_data = (void *)&omen_v1_legacy_board_params, }, {}, }; @@ -274,6 +406,7 @@ enum hp_wmi_commandtype { HPWMI_POSTCODEERROR_QUERY = 0x2a, HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, + HPWMI_GRAPHICS_MUX_QUERY = 0x52, }; struct victus_power_limits { @@ -380,6 +513,11 @@ static const struct key_entry hp_wmi_keymap[] = { { KE_KEY, 0x21a9, { KEY_TOUCHPAD_OFF } }, { KE_KEY, 0x121a9, { KEY_TOUCHPAD_ON } }, { KE_KEY, 0x231b, { KEY_HELP } }, + { KE_IGNORE, 0x21ab, }, /* FnLock on */ + { KE_IGNORE, 0x121ab, }, /* FnLock off */ + { KE_IGNORE, 0x30021aa, }, /* kbd backlight: level 2 -> off */ + { KE_IGNORE, 0x33221aa, }, /* kbd backlight: off -> level 1 */ + { KE_IGNORE, 0x36421aa, }, /* kbd backlight: level 1 -> level 2*/ { KE_END, 0 } }; @@ -434,23 +572,24 @@ enum pwm_modes { }; struct hp_wmi_hwmon_priv { + struct mutex lock; /* protects mode, pwm */ u8 min_rpm; u8 max_rpm; - u8 gpu_delta; u8 mode; - u8 pwm; + u8 cpu_pwm; + u8 gpu_pwm; struct delayed_work keep_alive_dwork; }; struct victus_s_fan_table_header { + u8 num_fans; u8 unknown; - u8 num_entries; } __packed; struct victus_s_fan_table_entry { u8 cpu_rpm; u8 gpu_rpm; - u8 unknown; + u8 noise_db; } __packed; struct victus_s_fan_table { @@ -765,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) return enabled; } -static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) +static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv) { u8 fan_speed[2]; - int gpu_speed, ret; + int ret; - fan_speed[CPU_FAN] = speed; - fan_speed[GPU_FAN] = speed; + if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC) + fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC; + else + fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv); - /* - * GPU fan speed is always a little higher than CPU fan speed, we fetch - * this delta value from the fan table during hwmon init. - * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to - * automatic mode. - */ - if (speed != HP_FAN_SPEED_AUTOMATIC) { - gpu_speed = speed + priv->gpu_delta; - fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); - } + if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC) + fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC; + else + fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv); ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) @@ -799,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) { - return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); + priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC; + priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC; + return hp_wmi_fan_speed_set(priv); } static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) @@ -1060,12 +1197,134 @@ static int camera_shutter_input_setup(void) return err; } +static const u8 mux_bitmask_map[] = { + [0] = HPWMI_MUX_MODE_HYBRID, + [1] = HPWMI_MUX_MODE_DISCRETE, + [2] = HPWMI_MUX_MODE_OPTIMUS, + [3] = HPWMI_MUX_MODE_UMA, +}; + +static int hp_wmi_get_mux_supported_modes(u8 *supported) +{ + u8 legacy_buffer[4] = {}; + u8 buffer[128] = {}; + u32 req_packet = 0; + int ret; + + if (!supported) + return -EINVAL; + + /* Try modern BIOS design data query (128-byte buffer) */ + ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, + buffer, zero_if_sup(req_packet), sizeof(buffer)); + if (ret == 0) { + *supported = buffer[7]; + return 0; + } + + /* + * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub. + * If the modern query is not supported, check if the MUX query endpoint + * responds to a read request. If it succeeds, the hardware has MUX + * capability but lacks the mode map, defaulting to Hybrid + Discrete. + */ + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, + legacy_buffer, sizeof(legacy_buffer), 0); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + *supported = HPWMI_MUX_LEGACY_MASK; + return 0; +} + +static int hp_wmi_get_mux_mode(u8 *mode) +{ + u8 buffer[4] = {}; + int ret; + + if (!mode) + return -EINVAL; + + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, + buffer, sizeof(buffer), sizeof(buffer)); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + /* Mask the highest bit, which might be used as a BIOS status flag */ + *mode = buffer[0] & HPWMI_MUX_MODE_MASK; + return 0; +} + +static int hp_wmi_set_mux_mode(u8 mode) +{ + u8 buffer[4] = { mode, 0x00, 0x00, 0x00 }; + int ret; + + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE, + buffer, sizeof(buffer), sizeof(buffer)); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + return 0; +} + +static ssize_t gpu_mux_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 mode; + int ret; + + ret = hp_wmi_get_mux_mode(&mode); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", mode); +} + +static ssize_t gpu_mux_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + u32 requested; + u8 supported; + int ret; + + ret = kstrtou32(buf, 0, &requested); + if (ret) + return ret; + if (requested >= ARRAY_SIZE(mux_bitmask_map)) + return -EINVAL; + + ret = hp_wmi_get_mux_supported_modes(&supported); + if (ret) + return ret; + + /* Verify if the requested mode is allowed by the hardware mask */ + if (!(supported & mux_bitmask_map[requested])) + return -EOPNOTSUPP; + + ret = hp_wmi_set_mux_mode(requested); + if (ret) + return ret; + + return count; +} + 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 DEVICE_ATTR_RW(gpu_mux_mode); static struct attribute *hp_wmi_attrs[] = { &dev_attr_display.attr, @@ -1074,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { &dev_attr_dock.attr, &dev_attr_tablet.attr, &dev_attr_postcode.attr, + &dev_attr_gpu_mux_mode.attr, NULL, }; ATTRIBUTE_GROUPS(hp_wmi); @@ -1725,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) return is_victus_s_board; } +static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->fan_profile; +} + +static bool hp_wmi_fan_control_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->get_fan_speed; +} + +static bool hp_wmi_fan_table_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->fan_table; +} + +static int hp_wmi_get_active_fan_speed(int fan) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + if (!params || !params->get_fan_speed) + return -EOPNOTSUPP; + + return params->get_fan_speed(fan); +} + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, bool *ppab_enable, u8 *dstate, @@ -1810,8 +2102,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil u8 current_dstate, current_gpu_slowdown_temp, tp; const struct thermal_profile_params *params; - params = active_thermal_profile_params; - if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { *profile = active_platform_profile; return 0; } @@ -1821,10 +2117,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil return ret; /* - * We cannot use active_thermal_profile_params here, because boards - * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically - * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen - * Gaming Hub on windows. Hence accept both of these values. + * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, + * but logically it should have tp == 0x30 || tp == 0x31, as + * corrected by the Omen Gaming Hub on windows. Hence accept both + * of these values. */ if (tp == victus_s_thermal_params.performance || tp == omen_v1_thermal_params.performance) { @@ -1859,12 +2155,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) { - struct thermal_profile_params *params; + const struct thermal_profile_params *params; bool gpu_ctgp_enable, gpu_ppab_enable; u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ int err, tp; - params = active_thermal_profile_params; + params = hp_wmi_thermal_profile(); if (!params) return -ENODEV; @@ -2130,6 +2426,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { static int thermal_profile_setup(struct platform_device *device) { const struct platform_profile_ops *ops; + const struct thermal_profile_params *params; int err, tp; if (is_omen_thermal_profile()) { @@ -2161,12 +2458,17 @@ static int thermal_profile_setup(struct platform_device *device) ops = &platform_profile_victus_ops; } else if (is_victus_s_thermal_profile()) { + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + /* * For an unknown EC layout board, platform_profile_victus_s_get_ec(), * behaves like a wrapper around active_platform_profile, to avoid using * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. */ - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { active_platform_profile = PLATFORM_PROFILE_BALANCED; } else { err = platform_profile_victus_s_get_ec(&active_platform_profile); @@ -2334,40 +2636,43 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) switch (priv->mode) { case PWM_MODE_MAX: - if (is_victus_s_thermal_profile()) - hp_wmi_get_fan_count_userdefine_trigger(); + if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; + } ret = hp_wmi_fan_speed_max_set(1); if (ret < 0) return ret; - schedule_delayed_work(&priv->keep_alive_dwork, - secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; - ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); + ret = hp_wmi_fan_speed_set(priv); if (ret < 0) return ret; - schedule_delayed_work(&priv->keep_alive_dwork, - secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_AUTO: - if (is_victus_s_thermal_profile()) { - hp_wmi_get_fan_count_userdefine_trigger(); + if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; ret = hp_wmi_fan_speed_max_reset(priv); } else { ret = hp_wmi_fan_speed_max_set(0); } if (ret < 0) return ret; - cancel_delayed_work_sync(&priv->keep_alive_dwork); + cancel_delayed_work(&priv->keep_alive_dwork); return 0; default: /* shouldn't happen */ return -EINVAL; } - - return 0; } static umode_t hp_wmi_hwmon_is_visible(const void *data, @@ -2376,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, { switch (type) { case hwmon_pwm: - if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) + if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) return 0; return 0644; case hwmon_fan: - if (is_victus_s_thermal_profile()) { - if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) + if (hp_wmi_fan_control_supported()) { + if (hp_wmi_get_active_fan_speed(channel) >= 0) return 0444; } else { if (hp_wmi_get_fan_speed(channel) >= 0) @@ -2400,12 +2705,13 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, { struct hp_wmi_hwmon_priv *priv; int rpm, ret; + u8 mode; priv = dev_get_drvdata(dev); switch (type) { case hwmon_fan: - if (is_victus_s_thermal_profile()) - ret = hp_wmi_get_fan_speed_victus_s(channel); + if (hp_wmi_fan_control_supported()) + ret = hp_wmi_get_active_fan_speed(channel); else ret = hp_wmi_get_fan_speed(channel); if (ret < 0) @@ -2414,20 +2720,22 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, return 0; case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; - rpm = hp_wmi_get_fan_speed_victus_s(channel); + rpm = hp_wmi_get_active_fan_speed(channel); if (rpm < 0) return rpm; *val = rpm_to_pwm(rpm / 100, priv); return 0; } - switch (priv->mode) { + scoped_guard(mutex, &priv->lock) + mode = priv->mode; + switch (mode) { case PWM_MODE_MAX: case PWM_MODE_MANUAL: case PWM_MODE_AUTO: - *val = priv->mode; + *val = mode; return 0; default: /* shouldn't happen */ @@ -2442,13 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct hp_wmi_hwmon_priv *priv; - int rpm; + int cpu_rpm, gpu_rpm; priv = dev_get_drvdata(dev); + guard(mutex)(&priv->lock); switch (type) { case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + int rpm; + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* PWM input is invalid when not in manual mode */ if (priv->mode != PWM_MODE_MANUAL) @@ -2457,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, /* ensure PWM input is within valid fan speeds */ rpm = pwm_to_rpm(val, priv); rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); - priv->pwm = rpm_to_pwm(rpm, priv); + if (channel == CPU_FAN) + priv->cpu_pwm = rpm_to_pwm(rpm, priv); + else if (channel == GPU_FAN) + priv->gpu_pwm = rpm_to_pwm(rpm, priv); return hp_wmi_apply_fan_settings(priv); } switch (val) { @@ -2465,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, priv->mode = PWM_MODE_MAX; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* * When switching to manual mode, set fan speed to * current RPM values to ensure a smooth transition. */ - rpm = hp_wmi_get_fan_speed_victus_s(channel); - if (rpm < 0) - return rpm; - priv->pwm = rpm_to_pwm(rpm / 100, priv); + cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN); + if (cpu_rpm < 0) + return cpu_rpm; + gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN); + if (gpu_rpm < 0) + return gpu_rpm; + priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv); + priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv); priv->mode = PWM_MODE_MANUAL; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_AUTO: @@ -2490,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, static const struct hwmon_channel_info * const info[] = { HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT), NULL }; @@ -2509,28 +2826,36 @@ static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work) { struct delayed_work *dwork; struct hp_wmi_hwmon_priv *priv; + int ret; dwork = to_delayed_work(work); priv = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork); + + guard(mutex)(&priv->lock); /* * Re-apply the current hwmon context settings. * NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling. */ - hp_wmi_apply_fan_settings(priv); + ret = hp_wmi_apply_fan_settings(priv); + if (ret) + pr_warn_ratelimited("keep-alive failed to refresh fan settings: %d\n", + ret); } static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) { u8 fan_data[128] = { 0 }; struct victus_s_fan_table *fan_table; - u8 min_rpm, max_rpm, gpu_delta; - int ret; + u8 min_rpm, max_rpm; + u8 cpu_rpm, gpu_rpm, noise_db; + int i, num_entries, ret; + size_t header_size, entry_size; /* Default behaviour on hwmon init is automatic mode */ priv->mode = PWM_MODE_AUTO; - /* Bypass all non-Victus S devices */ - if (!is_victus_s_thermal_profile()) + /* Bypass devices without fan control support. */ + if (!hp_wmi_fan_table_supported()) return 0; ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, @@ -2539,17 +2864,38 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) return ret; fan_table = (struct victus_s_fan_table *)fan_data; - if (fan_table->header.num_entries == 0 || - sizeof(struct victus_s_fan_table_header) + - sizeof(struct victus_s_fan_table_entry) * fan_table->header.num_entries > sizeof(fan_data)) + if (fan_table->header.num_fans == 0) + return -EINVAL; + + header_size = sizeof(struct victus_s_fan_table_header); + entry_size = sizeof(struct victus_s_fan_table_entry); + num_entries = (sizeof(fan_data) - header_size) / entry_size; + min_rpm = U8_MAX; + max_rpm = 0; + + for (i = 0 ; i < num_entries ; i++) { + cpu_rpm = fan_table->entries[i].cpu_rpm; + gpu_rpm = fan_table->entries[i].gpu_rpm; + noise_db = fan_table->entries[i].noise_db; + + /* + * On some devices, the fan table is truncated with an all-zero row, + * hence we stop parsing here. + */ + if (cpu_rpm == 0 && gpu_rpm == 0 && noise_db == 0) + break; + + if (cpu_rpm < min_rpm) + min_rpm = cpu_rpm; + if (cpu_rpm > max_rpm) + max_rpm = cpu_rpm; + } + + if (min_rpm == U8_MAX || max_rpm == 0) return -EINVAL; - min_rpm = fan_table->entries[0].cpu_rpm; - max_rpm = fan_table->entries[fan_table->header.num_entries - 1].cpu_rpm; - gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; priv->min_rpm = min_rpm; priv->max_rpm = max_rpm; - priv->gpu_delta = gpu_delta; return 0; } @@ -2565,6 +2911,10 @@ static int hp_wmi_hwmon_init(void) if (!priv) return -ENOMEM; + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + ret = hp_wmi_setup_fan_settings(priv); if (ret) return ret; @@ -2578,29 +2928,32 @@ static int hp_wmi_hwmon_init(void) INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler); platform_set_drvdata(hp_wmi_platform_dev, priv); - hp_wmi_apply_fan_settings(priv); + ret = hp_wmi_apply_fan_settings(priv); + if (ret) + dev_warn(dev, "Failed to apply initial fan settings: %d\n", ret); return 0; } -static void __init setup_active_thermal_profile_params(void) +static void __init setup_active_board_params(void) { const struct dmi_system_id *id; + const struct thermal_profile_params *params; - /* - * Currently only victus_s devices use the - * active_thermal_profile_params - */ - id = dmi_first_match(victus_s_thermal_profile_boards); + id = dmi_first_match(hp_wmi_feature_boards); if (id) { + active_board_params = id->driver_data; + params = hp_wmi_thermal_profile(); + if (!params) + return; + /* * Marking this boolean is required to ensure that * is_victus_s_thermal_profile() behaves like a valid * wrapper. */ is_victus_s_board = true; - active_thermal_profile_params = id->driver_data; - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", dmi_get_system_info(DMI_BOARD_NAME)); } @@ -2635,10 +2988,10 @@ static int __init hp_wmi_init(void) } /* - * Setup active board's thermal profile parameters before - * starting platform driver probe. + * Setup active board feature data before starting platform + * driver probe. */ - setup_active_thermal_profile_params(); + setup_active_board_params(); err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); if (err) goto err_unregister_device; diff --git a/drivers/platform/x86/hp/hp_accel.c b/drivers/platform/x86/hp/hp_accel.c index 10d5af18d639..39b73dc473f1 100644 --- a/drivers/platform/x86/hp/hp_accel.c +++ b/drivers/platform/x86/hp/hp_accel.c @@ -300,6 +300,9 @@ static int lis3lv02d_probe(struct platform_device *device) int ret; lis3_dev.bus_priv = ACPI_COMPANION(&device->dev); + if (!lis3_dev.bus_priv) + return -ENODEV; + lis3_dev.init = lis3lv02d_acpi_init; lis3_dev.read = lis3lv02d_acpi_read; lis3_dev.write = lis3lv02d_acpi_write; diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 93cca17fdf58..d6aaf14d66a5 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/dmi.h> @@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev) /* Fn lock */ +/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */ +#define FN_LOCK_ACPI_OFF 1 +#define FN_LOCK_ACPI_ON 2 +#define FN_LOCK_ACPI_STAT_OK 0 + +/* + * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct + * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control + * Fn-lock via EC registers 0x6B (read) and 0x6C (write). + * + * GFRS response buffer layout: + * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success) + * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on) + * + * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)): + * Value is read from byte[2] of the integer argument, so it must be + * passed as (value << 16): + * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C) + * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C) + */ + +static int huawei_acpi_fn_lock_get(int *on) +{ + union acpi_object acpi_arg; + struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + acpi_arg.type = ACPI_TYPE_INTEGER; + acpi_arg.integer.value = 0; + + status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + union acpi_object *obj __free(kfree) = output.pointer; + if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) + return -ENODATA; + + /* byte[0] = STAT, byte[1] = fn-lock state */ + if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) + return -EIO; + + switch (obj->buffer.pointer[1]) { + case FN_LOCK_ACPI_OFF: + if (on) + *on = 0; + break; + case FN_LOCK_ACPI_ON: + if (on) + *on = 1; + break; + default: + return -ENODATA; + } + + return 0; +} + +static int huawei_acpi_fn_lock_set(int on) +{ + union acpi_object acpi_arg; + struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + /* + * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02). + * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off) + * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on) + */ + acpi_arg.type = ACPI_TYPE_INTEGER; + acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16; + + status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + union acpi_object *obj __free(kfree) = output.pointer; + if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1) + return -ENODATA; + + if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) + return -EIO; + + return 0; +} + static int huawei_wmi_fn_lock_get(int *on) { u8 ret[0x100] = { 0 }; int err, i; + /* Newer models: use direct ACPI \GFRS method */ + if (acpi_has_method(NULL, "\\GFRS")) + return huawei_acpi_fn_lock_get(on); + + /* WMI fallback */ err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); if (err) return err; @@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on) { union hwmi_arg arg; + /* Newer models: use direct ACPI \SFRS method */ + if (acpi_has_method(NULL, "\\SFRS")) + return huawei_acpi_fn_lock_set(on); + + /* WMI fallback */ arg.cmd = FN_LOCK_SET; arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. diff --git a/drivers/platform/x86/intel/atomisp2/led.c b/drivers/platform/x86/intel/atomisp2/led.c index 10077a61d8c5..344ea57d9736 100644 --- a/drivers/platform/x86/intel/atomisp2/led.c +++ b/drivers/platform/x86/intel/atomisp2/led.c @@ -11,7 +11,6 @@ #include <linux/gpio/machine.h> #include <linux/leds.h> #include <linux/module.h> -#include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/workqueue.h> diff --git a/drivers/platform/x86/intel/atomisp2/pm.c b/drivers/platform/x86/intel/atomisp2/pm.c index 805fc0d8515c..ad1f99140962 100644 --- a/drivers/platform/x86/intel/atomisp2/pm.c +++ b/drivers/platform/x86/intel/atomisp2/pm.c @@ -13,7 +13,6 @@ #include <linux/delay.h> #include <linux/module.h> -#include <linux/mod_devicetable.h> #include <linux/pci.h> #include <linux/pm_runtime.h> #include <asm/iosf_mbi.h> diff --git a/drivers/platform/x86/intel/bxtwc_tmu.c b/drivers/platform/x86/intel/bxtwc_tmu.c index 99437b2ccc25..b3666704d85b 100644 --- a/drivers/platform/x86/intel/bxtwc_tmu.c +++ b/drivers/platform/x86/intel/bxtwc_tmu.c @@ -10,7 +10,6 @@ */ #include <linux/module.h> -#include <linux/mod_devicetable.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/mfd/intel_soc_pmic.h> diff --git a/drivers/platform/x86/intel/ehl_pse_io.c b/drivers/platform/x86/intel/ehl_pse_io.c index 861e14808b35..f75f3a8cc6b0 100644 --- a/drivers/platform/x86/intel/ehl_pse_io.c +++ b/drivers/platform/x86/intel/ehl_pse_io.c @@ -12,7 +12,6 @@ #include <linux/errno.h> #include <linux/gfp_types.h> #include <linux/ioport.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/sizes.h> diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index 95c405c8bac0..acd42e639fa4 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -7,11 +7,13 @@ */ #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/dmi.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/platform_device.h> #include <linux/string_choices.h> #include <linux/suspend.h> @@ -156,6 +158,13 @@ static const struct dmi_system_id button_array_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 4"), }, }, + { + .ident = "HP ProBook x360 440 G1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 440 G1"), + }, + }, { } }; @@ -230,6 +239,7 @@ static const struct dmi_system_id dmi_auto_add_switch[] = { }; struct intel_hid_priv { + struct mutex mutex; /* Avoid notify_handler() racing with itself */ struct input_dev *input_dev; struct input_dev *array; struct input_dev *switches; @@ -438,6 +448,14 @@ static int intel_hid_pl_suspend_handler(struct device *device) return 0; } +static int intel_hid_pl_freeze_handler(struct device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(device); + + priv->wakeup_mode = false; + return intel_hid_pl_suspend_handler(device); +} + static int intel_hid_pl_resume_handler(struct device *device) { intel_hid_pm_complete(device); @@ -452,7 +470,7 @@ static int intel_hid_pl_resume_handler(struct device *device) static const struct dev_pm_ops intel_hid_pl_pm_ops = { .prepare = intel_hid_pm_prepare, .complete = intel_hid_pm_complete, - .freeze = intel_hid_pl_suspend_handler, + .freeze = intel_hid_pl_freeze_handler, .thaw = intel_hid_pl_resume_handler, .restore = intel_hid_pl_resume_handler, .suspend = intel_hid_pl_suspend_handler, @@ -557,6 +575,8 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) struct key_entry *ke; int err; + guard(mutex)(&priv->mutex); + /* * Some convertible have unreliable VGBS return which could cause incorrect * SW_TABLET_MODE report, in these cases we enable support when receiving @@ -680,12 +700,16 @@ static bool button_array_present(struct platform_device *device) static int intel_hid_probe(struct platform_device *device) { - acpi_handle handle = ACPI_HANDLE(&device->dev); unsigned long long mode, dummy; struct intel_hid_priv *priv; + acpi_handle handle; acpi_status status; int err; + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + intel_hid_init_dsm(handle); if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) { @@ -708,6 +732,10 @@ static int intel_hid_probe(struct platform_device *device) return -ENOMEM; dev_set_drvdata(&device->dev, priv); + err = devm_mutex_init(&device->dev, &priv->mutex); + if (err) + return err; + /* See dual_accel_detect.h for more info on the dual_accel check. */ if (enable_sw_tablet_mode == TABLET_SW_AUTO) { if (dmi_check_system(dmi_vgbs_allow_list)) diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c index 88822023a149..f506155f35d4 100644 --- a/drivers/platform/x86/intel/int1092/intel_sar.c +++ b/drivers/platform/x86/intel/int1092/intel_sar.c @@ -91,8 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob item->package.count <= data->total_dev_mode) return AE_ERROR; - data->device_mode_info = kmalloc_objs(struct wwan_device_mode_info, - data->total_dev_mode); + data->device_mode_info = devm_kcalloc(&context->sar_device->dev, + data->total_dev_mode, + sizeof(*data->device_mode_info), + GFP_KERNEL); if (!data->device_mode_info) return AE_ERROR; @@ -245,21 +247,26 @@ static void sar_get_data(int reg, struct wwan_sar_context *context) static int sar_probe(struct platform_device *device) { struct wwan_sar_context *context; + acpi_handle handle; int reg; int result; - context = kzalloc_obj(*context); + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + + context = devm_kzalloc(&device->dev, sizeof(*context), GFP_KERNEL); if (!context) return -ENOMEM; context->sar_device = device; - context->handle = ACPI_HANDLE(&device->dev); + context->handle = handle; dev_set_drvdata(&device->dev, context); result = guid_parse(SAR_DSM_UUID, &context->guid); if (result) { dev_err(&device->dev, "SAR UUID parse error: %d\n", result); - goto r_free; + return result; } for (reg = 0; reg < MAX_REGULATORY; reg++) @@ -267,43 +274,29 @@ static int sar_probe(struct platform_device *device) if (sar_get_device_mode(device) != AE_OK) { dev_err(&device->dev, "Failed to get device mode\n"); - result = -EIO; - goto r_free; + return -EIO; } result = sysfs_create_group(&device->dev.kobj, &intcsar_group); if (result) { dev_err(&device->dev, "sysfs creation failed\n"); - goto r_free; + return result; } if (acpi_install_notify_handler(ACPI_HANDLE(&device->dev), ACPI_DEVICE_NOTIFY, sar_notify, (void *)device) != AE_OK) { dev_err(&device->dev, "Failed acpi_install_notify_handler\n"); - result = -EIO; - goto r_sys; + sysfs_remove_group(&device->dev.kobj, &intcsar_group); + return -EIO; } return 0; - -r_sys: - sysfs_remove_group(&device->dev.kobj, &intcsar_group); -r_free: - kfree(context); - return result; } static void sar_remove(struct platform_device *device) { - struct wwan_sar_context *context = dev_get_drvdata(&device->dev); - int reg; - acpi_remove_notify_handler(ACPI_HANDLE(&device->dev), ACPI_DEVICE_NOTIFY, sar_notify); sysfs_remove_group(&device->dev.kobj, &intcsar_group); - for (reg = 0; reg < MAX_REGULATORY; reg++) - kfree(context->config_data[reg].device_mode_info); - - kfree(context); } static struct platform_driver sar_driver = { diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 1c65ce87cde0..6c729fcfce5d 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -123,10 +123,31 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472, return desc; } +/* + * Other vana-supply users (e.g. ST, Toshiba, Sony sensors) can be added to + * this array instead of adding new quirk table entries. + */ +static const char * const power_enable_hids_vana[] = { + "SONY471A", /* imx471 on Lenovo X9-14 and X9-15 */ + "TBE20A0", /* imx471 on Lenovo X1 Carbon G14 */ + NULL +}; + +static const char * const power_enable_hids_vdd[] = { + "INT33F0", /* mt9m114 */ + NULL +}; + +static const char * const power_enable_hids_enable[] = { + "INT347E", /* ov7251 */ + NULL +}; + /** * struct int3472_gpio_map - Map GPIOs to whatever is expected by the * sensor driver (as in DT bindings) - * @hid: The ACPI HID of the device without the instance number e.g. INT347E + * @hids: NULL-terminated array of ACPI HIDs of the devices without the + * instance number e.g. INT347E * @type_from: The GPIO type from ACPI ?SDT * @type_to: The assigned GPIO type, typically same as @type_from * @enable_time_us: Enable time in usec for GPIOs mapped to regulators @@ -135,7 +156,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472, * GPIO_ACTIVE_HIGH otherwise */ struct int3472_gpio_map { - const char *hid; + const char * const *hids; u8 type_from; u8 type_to; bool polarity_low; @@ -145,27 +166,45 @@ struct int3472_gpio_map { static const struct int3472_gpio_map int3472_gpio_map[] = { { /* mt9m114 designs declare a powerdown pin which controls the regulators */ - .hid = "INT33F0", + .hids = power_enable_hids_vdd, .type_from = INT3472_GPIO_TYPE_POWERDOWN, .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, .con_id = "vdd", .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, }, { /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */ - .hid = "INT347E", + .hids = power_enable_hids_enable, .type_from = INT3472_GPIO_TYPE_RESET, .type_to = INT3472_GPIO_TYPE_RESET, .con_id = "enable", }, { /* ov08x40's handshake pin needs a 45 ms delay on some HP laptops */ - .hid = "OVTI08F4", + .hids = (const char * const[]) { "OVTI08F4", NULL }, .type_from = INT3472_GPIO_TYPE_HANDSHAKE, .type_to = INT3472_GPIO_TYPE_HANDSHAKE, .con_id = "dvdd", .enable_time_us = 45 * USEC_PER_MSEC, }, + { /* Sensors which expect "vana" as con_id for power enable */ + .hids = power_enable_hids_vana, + .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, + .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, + .con_id = "vana", + .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, + }, }; +static bool int3472_gpio_map_hids_match(struct acpi_device *adev, + const char * const *hids) +{ + for (unsigned int i = 0; hids[i]; i++) { + if (acpi_dev_hid_uid_match(adev, hids[i], NULL)) + return true; + } + + return false; +} + static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type, const char **con_id, unsigned long *gpio_flags, unsigned int *enable_time_us) @@ -182,7 +221,7 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 if (*type != int3472_gpio_map[i].type_from) continue; - if (!acpi_dev_hid_uid_match(adev, int3472_gpio_map[i].hid, NULL)) + if (!int3472_gpio_map_hids_match(adev, int3472_gpio_map[i].hids)) continue; dev_dbg(int3472->dev, "mapping type 0x%02x pin to 0x%02x %s\n", @@ -212,7 +251,11 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 *gpio_flags = GPIO_ACTIVE_HIGH; break; case INT3472_GPIO_TYPE_PRIVACY_LED: - *con_id = "privacy-led"; + *con_id = "privacy"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; + case INT3472_GPIO_TYPE_STROBE: + *con_id = "ir_flood"; *gpio_flags = GPIO_ACTIVE_HIGH; break; case INT3472_GPIO_TYPE_HOTPLUG_DETECT: @@ -252,6 +295,7 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 * * 0x00 Reset * 0x01 Power down + * 0x02 Strobe * 0x0b Power enable * 0x0c Clock enable * 0x0d Privacy LED @@ -336,6 +380,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_CLK_ENABLE: case INT3472_GPIO_TYPE_PRIVACY_LED: + case INT3472_GPIO_TYPE_STROBE: case INT3472_GPIO_TYPE_POWER_ENABLE: case INT3472_GPIO_TYPE_DOVDD: case INT3472_GPIO_TYPE_HANDSHAKE: @@ -354,7 +399,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_PRIVACY_LED: - ret = skl_int3472_register_pled(int3472, gpio); + case INT3472_GPIO_TYPE_STROBE: + ret = skl_int3472_register_led(int3472, gpio, con_id); if (ret) err_msg = "Failed to register LED\n"; @@ -429,7 +475,7 @@ void int3472_discrete_cleanup(struct int3472_discrete_device *int3472) gpiod_remove_lookup_table(&int3472->gpios); skl_int3472_unregister_clock(int3472); - skl_int3472_unregister_pled(int3472); + skl_int3472_unregister_leds(int3472); skl_int3472_unregister_regulator(int3472); } EXPORT_SYMBOL_NS_GPL(int3472_discrete_cleanup, "INTEL_INT3472_DISCRETE"); diff --git a/drivers/platform/x86/intel/int3472/led.c b/drivers/platform/x86/intel/int3472/led.c index b1d84b968112..9b2573cc347b 100644 --- a/drivers/platform/x86/intel/int3472/led.c +++ b/drivers/platform/x86/intel/int3472/led.c @@ -6,55 +6,58 @@ #include <linux/leds.h> #include <linux/platform_data/x86/int3472.h> -static int int3472_pled_set(struct led_classdev *led_cdev, - enum led_brightness brightness) +static int int3472_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { - struct int3472_discrete_device *int3472 = - container_of(led_cdev, struct int3472_discrete_device, pled.classdev); + struct int3472_led *led = container_of(led_cdev, struct int3472_led, classdev); - gpiod_set_value_cansleep(int3472->pled.gpio, brightness); + gpiod_set_value_cansleep(led->gpio, brightness); return 0; } -int skl_int3472_register_pled(struct int3472_discrete_device *int3472, struct gpio_desc *gpio) +int skl_int3472_register_led(struct int3472_discrete_device *int3472, struct gpio_desc *gpio, + const char *con_id) { + struct int3472_led *led; char *p; int ret; - if (int3472->pled.classdev.dev) - return -EBUSY; + if (int3472->n_leds >= INT3472_MAX_LEDS) + return -ENOSPC; - int3472->pled.gpio = gpio; + led = &int3472->leds[int3472->n_leds]; + led->gpio = gpio; /* Generate the name, replacing the ':' in the ACPI devname with '_' */ - snprintf(int3472->pled.name, sizeof(int3472->pled.name), - "%s::privacy_led", acpi_dev_name(int3472->sensor)); - p = strchr(int3472->pled.name, ':'); + snprintf(led->name, sizeof(led->name), + "%s::%s_led", acpi_dev_name(int3472->sensor), con_id); + p = strchr(led->name, ':'); if (p) *p = '_'; - int3472->pled.classdev.name = int3472->pled.name; - int3472->pled.classdev.max_brightness = 1; - int3472->pled.classdev.brightness_set_blocking = int3472_pled_set; + led->classdev.name = led->name; + led->classdev.max_brightness = 1; + led->classdev.brightness_set_blocking = int3472_led_set; - ret = led_classdev_register(int3472->dev, &int3472->pled.classdev); + ret = led_classdev_register(int3472->dev, &led->classdev); if (ret) return ret; - int3472->pled.lookup.provider = int3472->pled.name; - int3472->pled.lookup.dev_id = int3472->sensor_name; - int3472->pled.lookup.con_id = "privacy"; - led_add_lookup(&int3472->pled.lookup); + led->lookup.provider = led->name; + led->lookup.dev_id = int3472->sensor_name; + led->lookup.con_id = con_id; + led_add_lookup(&led->lookup); + int3472->n_leds++; return 0; } -void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472) +void skl_int3472_unregister_leds(struct int3472_discrete_device *int3472) { - if (IS_ERR_OR_NULL(int3472->pled.classdev.dev)) - return; + for (unsigned int i = 0; i < int3472->n_leds; i++) { + struct int3472_led *led = &int3472->leds[i]; - led_remove_lookup(&int3472->pled.lookup); - led_classdev_unregister(&int3472->pled.classdev); - gpiod_put(int3472->pled.gpio); + led_remove_lookup(&led->lookup); + led_classdev_unregister(&led->classdev); + gpiod_put(led->gpio); + } } diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index a496075c0d2a..a77ed32abe55 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -197,6 +197,7 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); cells[2].name = "tps68470-gpio"; + cells[2].swnode = board_data->tps68470_gpio_swnode; for (i = 0; i < board_data->n_gpiod_lookups; i++) gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); diff --git a/drivers/platform/x86/intel/int3472/tps68470.h b/drivers/platform/x86/intel/int3472/tps68470.h index 35915e701593..3bbaade96c57 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.h +++ b/drivers/platform/x86/intel/int3472/tps68470.h @@ -17,6 +17,7 @@ struct tps68470_regulator_platform_data; struct int3472_tps68470_board_data { const char *dev_name; const struct tps68470_regulator_platform_data *tps68470_regulator_pdata; + const struct software_node *tps68470_gpio_swnode; unsigned int n_gpiod_lookups; struct gpiod_lookup_table *tps68470_gpio_lookup_tables[]; }; diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c index 71357a036292..c53542465fb4 100644 --- a/drivers/platform/x86/intel/int3472/tps68470_board_data.c +++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c @@ -12,6 +12,7 @@ #include <linux/dmi.h> #include <linux/gpio/machine.h> #include <linux/platform_data/tps68470.h> +#include <linux/property.h> #include <linux/regulator/machine.h> #include "tps68470.h" @@ -150,8 +151,6 @@ static const struct regulator_init_data dell_7212_tps68470_core_reg_init_data = .apply_uV = 1, .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, - .num_consumer_supplies = 0, - .consumer_supplies = NULL, }; static const struct regulator_init_data dell_7212_tps68470_ana_reg_init_data = { @@ -161,8 +160,6 @@ static const struct regulator_init_data dell_7212_tps68470_ana_reg_init_data = { .apply_uV = 1, .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, - .num_consumer_supplies = 0, - .consumer_supplies = NULL, }; static const struct regulator_init_data dell_7212_tps68470_vcm_reg_init_data = { @@ -172,8 +169,6 @@ static const struct regulator_init_data dell_7212_tps68470_vcm_reg_init_data = { .apply_uV = 1, .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, - .num_consumer_supplies = 0, - .consumer_supplies = NULL, }; static const struct regulator_init_data dell_7212_tps68470_vio_reg_init_data = { @@ -183,8 +178,6 @@ static const struct regulator_init_data dell_7212_tps68470_vio_reg_init_data = { .apply_uV = 1, .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, - .num_consumer_supplies = 0, - .consumer_supplies = NULL, }; static const struct regulator_init_data dell_7212_tps68470_vsio_reg_init_data = { @@ -232,6 +225,150 @@ static const struct tps68470_regulator_platform_data dell_7212_tps68470_pdata = }, }; +/* Settings for MSI Prestige 14 AI+ Evo C2VMG laptop. */ +static struct regulator_consumer_supply ovti5675_avdd_consumer_supplies[] = { + REGULATOR_SUPPLY("avdd", "i2c-OVTI5675:00"), +}; + +static struct regulator_consumer_supply ovti5675_dovdd_consumer_supplies[] = { + REGULATOR_SUPPLY("dovdd", "i2c-OVTI5675:00"), +}; + +static struct regulator_consumer_supply ovti5675_dvdd_consumer_supplies[] = { + REGULATOR_SUPPLY("dvdd", "i2c-OVTI5675:00"), +}; + +static const struct regulator_init_data msi_prestige_ai_evo_tps68470_core_reg_init_data = { + .constraints = { + .min_uV = 1200000, + .max_uV = 1200000, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ovti5675_dvdd_consumer_supplies), + .consumer_supplies = ovti5675_dvdd_consumer_supplies, +}; + +static const struct regulator_init_data msi_prestige_ai_evo_tps68470_ana_reg_init_data = { + .constraints = { + .min_uV = 2815200, + .max_uV = 2815200, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ovti5675_avdd_consumer_supplies), + .consumer_supplies = ovti5675_avdd_consumer_supplies, +}; + +static const struct regulator_init_data msi_prestige_ai_evo_tps68470_vio_reg_init_data = { + .constraints = { + .min_uV = 1800600, + .max_uV = 1800600, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, +}; + +static const struct regulator_init_data msi_prestige_ai_evo_tps68470_vsio_reg_init_data = { + .constraints = { + .min_uV = 1800600, + .max_uV = 1800600, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ovti5675_dovdd_consumer_supplies), + .consumer_supplies = ovti5675_dovdd_consumer_supplies, +}; + +static const struct tps68470_regulator_platform_data msi_prestige_ai_evo_tps68470_pdata = { + .reg_init_data = { + [TPS68470_CORE] = &msi_prestige_ai_evo_tps68470_core_reg_init_data, + [TPS68470_ANA] = &msi_prestige_ai_evo_tps68470_ana_reg_init_data, + [TPS68470_VIO] = &msi_prestige_ai_evo_tps68470_vio_reg_init_data, + [TPS68470_VSIO] = &msi_prestige_ai_evo_tps68470_vsio_reg_init_data, + }, +}; + +/* Settings for Intel NVL platform */ + +static struct regulator_consumer_supply ovti13b1_core_consumer_supplies[] = { + REGULATOR_SUPPLY("dvdd", "i2c-OVTI13B1:01"), +}; + +static struct regulator_consumer_supply ovti13b1_ana_consumer_supplies[] = { + REGULATOR_SUPPLY("avdd", "i2c-OVTI13B1:01"), +}; + +static struct regulator_consumer_supply ovti13b1_vcm_consumer_supplies[] = { + REGULATOR_SUPPLY("vcc", "i2c-OVTI13B1:01-VCM"), +}; + +static struct regulator_consumer_supply ovti13b1_vsio_consumer_supplies[] = { + REGULATOR_SUPPLY("dovdd", "i2c-OVTI13B1:01"), +}; + +static const struct regulator_init_data intel_nvl_tps68470_core_reg_init_data = { + .constraints = { + .min_uV = 1200000, + .max_uV = 1200000, + .apply_uV = true, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ovti13b1_core_consumer_supplies), + .consumer_supplies = ovti13b1_core_consumer_supplies, +}; + +static const struct regulator_init_data intel_nvl_tps68470_ana_reg_init_data = { + .constraints = { + .min_uV = 2815200, + .max_uV = 2815200, + .apply_uV = true, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ovti13b1_ana_consumer_supplies), + .consumer_supplies = ovti13b1_ana_consumer_supplies, +}; +static const struct regulator_init_data intel_nvl_tps68470_vcm_reg_init_data = { + .constraints = { + .min_uV = 2815200, + .max_uV = 2815200, + .apply_uV = true, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ovti13b1_vcm_consumer_supplies), + .consumer_supplies = ovti13b1_vcm_consumer_supplies, +}; + +/* Ensure the always-on VIO regulator has the same voltage as VSIO */ +static const struct regulator_init_data intel_nvl_tps68470_vio_reg_init_data = { + .constraints = { + .min_uV = 1800600, + .max_uV = 1800600, + .apply_uV = true, + .always_on = true, + }, +}; +static const struct regulator_init_data intel_nvl_tps68470_vsio_reg_init_data = { + .constraints = { + .min_uV = 1800600, + .max_uV = 1800600, + .apply_uV = true, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(ovti13b1_vsio_consumer_supplies), + .consumer_supplies = ovti13b1_vsio_consumer_supplies, +}; + +static const struct tps68470_regulator_platform_data intel_nvl_tps68470_pdata = { + .reg_init_data = { + [TPS68470_CORE] = &intel_nvl_tps68470_core_reg_init_data, + [TPS68470_ANA] = &intel_nvl_tps68470_ana_reg_init_data, + [TPS68470_VCM] = &intel_nvl_tps68470_vcm_reg_init_data, + [TPS68470_VIO] = &intel_nvl_tps68470_vio_reg_init_data, + [TPS68470_VSIO] = &intel_nvl_tps68470_vsio_reg_init_data, + }, +}; + static struct gpiod_lookup_table surface_go_int347a_gpios = { .dev_id = "i2c-INT347A:00", .table = { @@ -258,6 +395,31 @@ static struct gpiod_lookup_table dell_7212_int3479_gpios = { } }; +static struct gpiod_lookup_table msi_prestige_ai_evo_ovti5675_gpios = { + .dev_id = "i2c-OVTI5675:00", + .table = { + GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW), + { } + } +}; + +static struct gpiod_lookup_table intel_nvl_tps68470_gpios = { + .dev_id = "i2c-OVTI13B1:01", + .table = { + GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW), + { } + } +}; + +static const struct property_entry int3472_tps68470_daisy_chain_gpio_props[] = { + PROPERTY_ENTRY_BOOL("daisy-chain-enable"), + { } +}; + +static const struct software_node int3472_tps68470_daisy_chain_gpio_swnode = { + .properties = int3472_tps68470_daisy_chain_gpio_props, +}; + static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = { .dev_name = "i2c-INT3472:05", .tps68470_regulator_pdata = &surface_go_tps68470_pdata, @@ -287,6 +449,26 @@ static const struct int3472_tps68470_board_data dell_7212_tps68470_board_data = }, }; +static const struct int3472_tps68470_board_data msi_prestige_ai_evo_tps68470_board_data = { + .dev_name = "i2c-INT3472:06", + .tps68470_regulator_pdata = &msi_prestige_ai_evo_tps68470_pdata, + .tps68470_gpio_swnode = &int3472_tps68470_daisy_chain_gpio_swnode, + .n_gpiod_lookups = 1, + .tps68470_gpio_lookup_tables = { + &msi_prestige_ai_evo_ovti5675_gpios, + }, +}; + +static const struct int3472_tps68470_board_data intel_nvl_tps68470_board_data = { + .dev_name = "i2c-INT3472:04", + .tps68470_regulator_pdata = &intel_nvl_tps68470_pdata, + .tps68470_gpio_swnode = &int3472_tps68470_daisy_chain_gpio_swnode, + .n_gpiod_lookups = 1, + .tps68470_gpio_lookup_tables = { + &intel_nvl_tps68470_gpios, + }, +}; + static const struct dmi_system_id int3472_tps68470_board_data_table[] = { { .matches = { @@ -316,6 +498,37 @@ static const struct dmi_system_id int3472_tps68470_board_data_table[] = { }, .driver_data = (void *)&dell_7212_tps68470_board_data, }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Prestige 13 AI+ Evo A2VMG"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "MS-13Q3"), + }, + .driver_data = (void *)&msi_prestige_ai_evo_tps68470_board_data, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Prestige 14 AI+ Evo C2VMG"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "MS-14N3"), + }, + .driver_data = (void *)&msi_prestige_ai_evo_tps68470_board_data, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Prestige 16 AI+ Evo B2VMG"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "MS-15A3"), + }, + .driver_data = (void *)&msi_prestige_ai_evo_tps68470_board_data, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Nova Lake Client Platform"), + }, + .driver_data = (void *)&intel_nvl_tps68470_board_data, + }, { } }; diff --git a/drivers/platform/x86/intel/ishtp_eclite.c b/drivers/platform/x86/intel/ishtp_eclite.c index 93ac8b2dbf38..bca7e217878b 100644 --- a/drivers/platform/x86/intel/ishtp_eclite.c +++ b/drivers/platform/x86/intel/ishtp_eclite.c @@ -600,13 +600,16 @@ static int ecl_ishtp_cl_probe(struct ishtp_cl_device *cl_device) rv = acpi_opregion_init(opr_dev); if (rv) { dev_err(cl_data_to_dev(opr_dev), "ACPI opregion init failed\n"); - goto err_exit; + goto err_put; } /* Reprobe devices depending on ECLite - battery, fan, etc. */ acpi_dev_clear_dependencies(opr_dev->adev); return 0; + +err_put: + acpi_dev_put(opr_dev->adev); err_exit: ishtp_set_connection_state(ecl_ishtp_cl, ISHTP_CL_DISCONNECTING); ishtp_cl_disconnect(ecl_ishtp_cl); diff --git a/drivers/platform/x86/intel/plr_tpmi.c b/drivers/platform/x86/intel/plr_tpmi.c index 05727169f49c..f98c241edee4 100644 --- a/drivers/platform/x86/intel/plr_tpmi.c +++ b/drivers/platform/x86/intel/plr_tpmi.c @@ -20,8 +20,8 @@ #include <linux/kstrtox.h> #include <linux/lockdep.h> #include <linux/module.h> -#include <linux/mod_devicetable.h> #include <linux/mutex.h> +#include <linux/notifier.h> #include <linux/seq_file.h> #include <linux/sprintf.h> #include <linux/types.h> @@ -60,6 +60,8 @@ struct tpmi_plr { struct tpmi_plr_die *die_info; int num_dies; struct auxiliary_device *auxdev; + struct notifier_block nb; + struct mutex lock; /* Protect access to dbgfs_dir */ }; static const char * const plr_coarse_reasons[] = { @@ -255,6 +257,30 @@ static ssize_t plr_status_write(struct file *filp, const char __user *ubuf, } DEFINE_SHOW_STORE_ATTRIBUTE(plr_status); +static int intel_plr_notify(struct notifier_block *self, unsigned long action, void *data) +{ + struct tpmi_plr *plr = container_of(self, struct tpmi_plr, nb); + + if (action == TPMI_CORE_EXIT) { + guard(mutex)(&plr->lock); + plr->dbgfs_dir = NULL; + } + + return NOTIFY_DONE; +} + +static int intel_plr_register_notifier(struct notifier_block *nb) +{ + nb->notifier_call = intel_plr_notify; + nb->priority = 0; + return tpmi_register_notifier(nb); +} + +static void intel_plr_unregister_notifier(struct notifier_block *nb) +{ + tpmi_unregister_notifier(nb); +} + static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) { struct oobmsm_plat_info *plat_info; @@ -282,10 +308,18 @@ static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxilia if (!plr) return -ENOMEM; + err = devm_mutex_init(&auxdev->dev, &plr->lock); + if (err) + return err; + + intel_plr_register_notifier(&plr->nb); + plr->die_info = devm_kcalloc(&auxdev->dev, num_resources, sizeof(*plr->die_info), GFP_KERNEL); - if (!plr->die_info) - return -ENOMEM; + if (!plr->die_info) { + err = -ENOMEM; + goto err_notify; + } plr->num_dies = num_resources; plr->dbgfs_dir = debugfs_create_dir("plr", dentry); @@ -326,6 +360,9 @@ static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxilia err: debugfs_remove_recursive(plr->dbgfs_dir); +err_notify: + intel_plr_unregister_notifier(&plr->nb); + return err; } @@ -333,6 +370,9 @@ static void intel_plr_remove(struct auxiliary_device *auxdev) { struct tpmi_plr *plr = auxiliary_get_drvdata(auxdev); + intel_plr_unregister_notifier(&plr->nb); + + guard(mutex)(&plr->lock); debugfs_remove_recursive(plr->dbgfs_dir); } diff --git a/drivers/platform/x86/intel/pmc/Kconfig b/drivers/platform/x86/intel/pmc/Kconfig index c6ef0bcf76af..561d46634ab2 100644 --- a/drivers/platform/x86/intel/pmc/Kconfig +++ b/drivers/platform/x86/intel/pmc/Kconfig @@ -9,6 +9,7 @@ config INTEL_PMC_CORE depends on ACPI depends on INTEL_PMT_TELEMETRY select INTEL_PMC_SSRAM_TELEMETRY + select INTEL_PMC_PWRM_TELEMETRY help The Intel Platform Controller Hub for Intel Core SoCs provides access to Power Management Controller registers via various interfaces. This @@ -28,3 +29,27 @@ config INTEL_PMC_CORE config INTEL_PMC_SSRAM_TELEMETRY tristate + help + This driver discovers PMC SSRAM telemetry regions through the PMC's + MMIO interface (PCI) or ACPI _DSD properties and registers them with + the Intel VSEC framework as Intel PMT telemetry devices. + + It probes the PMC SSRAM device, extracts DVSEC information from MMIO, + reads device IDs and base addresses for multiple PMCs (main, IOE, PCH), + and exposes the discovered telemetry through Intel PMT interfaces + (including sysfs). + + This option is selected by INTEL_PMC_CORE. + +config INTEL_PMC_PWRM_TELEMETRY + tristate + help + This driver discovers PMC PWRM telemetry regions described in ACPI + _DSD and registers them with the Intel VSEC framework as Intel PMT + telemetry devices. + + It validates the ACPI discovery data and publishes the discovered + regions so they can be accessed through the Intel PMT telemetry + interfaces (including sysfs). + + This option is selected by INTEL_PMC_CORE. diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile index bb960c8721d7..5b595176f812 100644 --- a/drivers/platform/x86/intel/pmc/Makefile +++ b/drivers/platform/x86/intel/pmc/Makefile @@ -4,7 +4,8 @@ # intel_pmc_core-y := core.o spt.o cnp.o icl.o \ - tgl.o adl.o mtl.o arl.o lnl.o ptl.o wcl.o + tgl.o adl.o mtl.o arl.o \ + lnl.o ptl.o wcl.o nvl.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv-y := pltdrv.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o @@ -12,3 +13,5 @@ obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o # Intel PMC SSRAM driver intel_pmc_ssram_telemetry-y += ssram_telemetry.o obj-$(CONFIG_INTEL_PMC_SSRAM_TELEMETRY) += intel_pmc_ssram_telemetry.o +intel_pmc_pwrm_telemetry-y += pwrm_telemetry.o +obj-$(CONFIG_INTEL_PMC_PWRM_TELEMETRY) += intel_pmc_pwrm_telemetry.o diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c index eb23bc68340a..11609d593383 100644 --- a/drivers/platform/x86/intel/pmc/arl.c +++ b/drivers/platform/x86/intel/pmc/arl.c @@ -672,6 +672,9 @@ static struct pmc_info arl_pmc_info_list[] = { {} }; +static const u8 arl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE, PMC_IDX_PCH}; +static const u8 arl_h_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE}; + #define ARL_NPU_PCI_DEV 0xad1d #define ARL_GNA_PCI_DEV 0xae4c #define ARL_H_NPU_PCI_DEV 0x7d1d @@ -720,8 +723,9 @@ static int arl_h_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_ static u32 ARL_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, 0x0}; struct pmc_dev_info arl_pmc_dev = { - .pci_func = 0, .dmu_guids = ARL_PMT_DMU_GUIDS, + .num_pmcs = ARRAY_SIZE(arl_pmc_list), + .pmc_list = arl_pmc_list, .regmap_list = arl_pmc_info_list, .map = &arl_socs_reg_map, .sub_req_show = &pmc_core_substate_req_regs_fops, @@ -729,12 +733,15 @@ struct pmc_dev_info arl_pmc_dev = { .resume = arl_resume, .init = arl_core_init, .sub_req = pmc_core_pmt_get_lpm_req, + .ssram_hidden = true, + .die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET, }; static u32 ARL_H_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, ARL_H_PMT_DMU_GUID, 0x0}; struct pmc_dev_info arl_h_pmc_dev = { - .pci_func = 2, .dmu_guids = ARL_H_PMT_DMU_GUIDS, + .num_pmcs = ARRAY_SIZE(arl_h_pmc_list), + .pmc_list = arl_h_pmc_list, .regmap_list = arl_pmc_info_list, .map = &mtl_socm_reg_map, .sub_req_show = &pmc_core_substate_req_regs_fops, @@ -742,4 +749,6 @@ struct pmc_dev_info arl_h_pmc_dev = { .resume = arl_h_resume, .init = arl_h_core_init, .sub_req = pmc_core_pmt_get_lpm_req, + .ssram_hidden = true, + .die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET, }; diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 02b303418d18..9f77c0716e59 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -623,7 +623,8 @@ static u32 convert_ltr_scale(u32 val) * ---------------------------------------------- */ if (val > 5) { - pr_warn("Invalid LTR scale factor.\n"); + pr_warn_once("Invalid LTR scale factor %u (only 0-5 are valid per PCIe spec)\n", + val); return 0; } @@ -1071,6 +1072,44 @@ static int pmc_core_die_c6_us_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(pmc_core_die_c6_us); +static int pmc_core_pkgc_counters_show(struct seq_file *s, + struct telem_endpoint *ep, + u32 offset, const char **counters) +{ + unsigned int i; + u32 counter; + int ret; + + for (i = 0; counters[i]; i++) { + ret = pmt_telem_read32(ep, offset + i, &counter, 1); + if (ret) + return ret; + seq_printf(s, "%-30s %-30u\n", counters[i], counter); + } + + return 0; +} + +static int pmc_core_pkgc_ltr_blocker_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + + return pmc_core_pkgc_counters_show(s, pmcdev->pc_ep, + pmcdev->pkgc_ltr_blocker_offset, + pmcdev->pkgc_ltr_blocker_counters); +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc_ltr_blocker); + +static int pmc_core_pkgc_blocker_residency_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + + return pmc_core_pkgc_counters_show(s, pmcdev->pc_ep, + pmcdev->pkgc_blocker_offset, + pmcdev->pkgc_blocker_counters); +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc_blocker_residency); + static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) { struct pmc_dev *pmcdev = s->private; @@ -1315,35 +1354,52 @@ static struct telem_endpoint *pmc_core_register_endpoint(struct pci_dev *pcidev, unsigned int i; for (i = 0; guids[i]; i++) { - ep = pmt_telem_find_and_register_endpoint(pcidev, guids[i], 0); + ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, guids[i], 0); if (!IS_ERR(ep)) return ep; } return ERR_PTR(-ENODEV); } -void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids) +void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) { struct telem_endpoint *ep; - struct pci_dev *pcidev; - pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(10, 0)); + struct pci_dev *pcidev __free(pci_dev_put) = pci_get_domain_bus_and_slot(0, 0, + PCI_DEVFN(10, 0)); if (!pcidev) { dev_err(&pmcdev->pdev->dev, "PUNIT PMT device not found."); return; } - ep = pmc_core_register_endpoint(pcidev, guids); - pci_dev_put(pcidev); - if (IS_ERR(ep)) { - dev_err(&pmcdev->pdev->dev, - "pmc_core: couldn't get DMU telem endpoint %ld", - PTR_ERR(ep)); - return; + if (pmc_dev_info->dmu_guids) { + ep = pmc_core_register_endpoint(pcidev, pmc_dev_info->dmu_guids); + if (IS_ERR(ep)) { + dev_err(&pmcdev->pdev->dev, + "pmc_core: couldn't get DMU telem endpoint %ld", + PTR_ERR(ep)); + return; + } + + pmcdev->punit_ep = ep; + pmcdev->die_c6_offset = pmc_dev_info->die_c6_offset; } - pmcdev->punit_ep = ep; - pmcdev->die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET; + if (pmc_dev_info->pc_guid) { + ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, pmc_dev_info->pc_guid, 0); + if (IS_ERR(ep)) { + dev_err(&pmcdev->pdev->dev, + "pmc_core: couldn't get Package C-state telem endpoint %ld", + PTR_ERR(ep)); + return; + } + + pmcdev->pc_ep = ep; + pmcdev->pkgc_ltr_blocker_counters = pmc_dev_info->pkgc_ltr_blocker_counters; + pmcdev->pkgc_ltr_blocker_offset = pmc_dev_info->pkgc_ltr_blocker_offset; + pmcdev->pkgc_blocker_counters = pmc_dev_info->pkgc_blocker_counters; + pmcdev->pkgc_blocker_offset = pmc_dev_info->pkgc_blocker_offset; + } } void pmc_core_set_device_d3(unsigned int device) @@ -1467,6 +1523,16 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev, struct pmc_dev_info pmcdev->dbgfs_dir, pmcdev, &pmc_core_die_c6_us_fops); } + + if (pmcdev->pc_ep) { + debugfs_create_file("pkgc_ltr_blocker_show", 0444, + pmcdev->dbgfs_dir, pmcdev, + &pmc_core_pkgc_ltr_blocker_fops); + debugfs_create_file("pkgc_blocker_residency_show", 0444, + pmcdev->dbgfs_dir, pmcdev, + &pmc_core_pkgc_blocker_residency_fops); + } + } /* @@ -1517,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel { const u8 *lpm_indices; int num_maps, mode_offset = 0; - int ret, lpm_size; + int ret = 0, lpm_size; u8 mode; lpm_indices = pmc->map->lpm_reg_index; @@ -1581,17 +1647,13 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc, static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) { - struct pci_dev *pcidev __free(pci_dev_put) = NULL; struct telem_endpoint *ep; unsigned int pmc_idx; int ret; - pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, pmc_dev_info->pci_func)); - if (!pcidev) - return -ENODEV; - for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) { struct pmc *pmc; + u16 devid; pmc = pmcdev->pmcs[pmc_idx]; if (!pmc) @@ -1600,7 +1662,17 @@ static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info * if (!pmc->map->lpm_req_guid) return -ENXIO; - ep = pmt_telem_find_and_register_endpoint(pcidev, pmc->map->lpm_req_guid, 0); + if (pmc_dev_info->ssram_hidden) + devid = pmcdev->pmcs[PMC_IDX_MAIN]->devid; + else + devid = pmc->devid; + + struct pci_dev *pcidev __free(pci_dev_put) = + pci_get_device(PCI_VENDOR_ID_INTEL, devid, NULL); + if (!pcidev) + return -ENODEV; + + ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, pmc->map->lpm_req_guid, 0); if (IS_ERR(ep)) { dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep); return -EPROBE_DEFER; @@ -1650,6 +1722,7 @@ static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_idx) pmc->map = map; pmc->base_addr = pmc_ssram_telemetry.base_addr; + pmc->devid = pmc_ssram_telemetry.devid; pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length); if (!pmc->regbase) { @@ -1662,16 +1735,17 @@ static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_idx) return 0; } -static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev) +static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev, u8 num_pmcs, const u8 *pmc_list) { + unsigned int i; int ret; - ret = pmc_core_pmc_add(pmcdev, PMC_IDX_MAIN); - if (ret) - return ret; - - pmc_core_pmc_add(pmcdev, PMC_IDX_IOE); - pmc_core_pmc_add(pmcdev, PMC_IDX_PCH); + for (i = 0; i < num_pmcs; ++i) { + /* Non-MAIN PMCs are allowed to fail */ + ret = pmc_core_pmc_add(pmcdev, pmc_list[i]); + if (ret && (pmc_list[i] == PMC_IDX_MAIN)) + return ret; + } return 0; } @@ -1693,7 +1767,9 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) ssram = pmc_dev_info->regmap_list != NULL; if (ssram) { pmcdev->regmap_list = pmc_dev_info->regmap_list; - ret = pmc_core_ssram_get_reg_base(pmcdev); + ret = pmc_core_ssram_get_reg_base(pmcdev, + pmc_dev_info->num_pmcs, + pmc_dev_info->pmc_list); /* * EAGAIN error code indicates Intel PMC SSRAM Telemetry driver * has not finished probe and PMC info is not available yet. Try @@ -1717,8 +1793,8 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) } pmc_core_get_low_power_modes(pmcdev); - if (pmc_dev_info->dmu_guids) - pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guids); + if (pmc_dev_info->dmu_guids || pmc_dev_info->pc_guid) + pmc_core_punit_pmt_init(pmcdev, pmc_dev_info); if (ssram) { ret = pmc_core_get_telem_info(pmcdev, pmc_dev_info); @@ -1739,6 +1815,9 @@ unmap_regbase: if (pmcdev->punit_ep) pmt_telem_unregister_endpoint(pmcdev->punit_ep); + if (pmcdev->pc_ep) + pmt_telem_unregister_endpoint(pmcdev->pc_ep); + return ret; } @@ -1771,6 +1850,8 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = { X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev), + X86_MATCH_VFM(INTEL_NOVALAKE, &nvl_s_pmc_dev), + X86_MATCH_VFM(INTEL_NOVALAKE_L, &nvl_h_pmc_dev), {} }; @@ -1835,6 +1916,9 @@ static void pmc_core_clean_structure(struct platform_device *pdev) if (pmcdev->punit_ep) pmt_telem_unregister_endpoint(pmcdev->punit_ep); + if (pmcdev->pc_ep) + pmt_telem_unregister_endpoint(pmcdev->pc_ep); + platform_set_drvdata(pdev, NULL); } diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 118c8740ad3a..b4c7399f8369 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -14,10 +14,15 @@ #include <linux/acpi.h> #include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/intel_vsec.h> #include <linux/platform_device.h> +#include <linux/uuid.h> struct telem_endpoint; +DEFINE_FREE(pmc_acpi_free, void *, if (_T) ACPI_FREE(_T)) + #define SLP_S0_RES_COUNTER_MASK GENMASK(31, 0) #define PMC_BASE_ADDR_DEFAULT 0xFE000000 @@ -46,7 +51,7 @@ struct telem_endpoint; #define SPT_PMC_SLP_S0_RES_COUNTER_STEP 0x68 #define PMC_BASE_ADDR_MASK ~(SPT_PMC_MMIO_REG_LEN - 1) #define MTPMC_MASK 0xffff0000 -#define PPFEAR_MAX_NUM_ENTRIES 12 +#define PPFEAR_MAX_NUM_ENTRIES 13 #define SPT_PPFEAR_NUM_ENTRIES 5 #define SPT_PMC_READ_DISABLE_BIT 0x16 #define SPT_PMC_MSG_FULL_STS_BIT 0x18 @@ -307,6 +312,29 @@ enum ppfear_regs { #define WCL_NUM_S0IX_BLOCKER 94 #define WCL_BLK_REQ_OFFSET 50 +/* Nova Lake */ +#define NVL_PCDH_PPFEAR_NUM_ENTRIES 13 +#define NVL_PCDH_PMC_MMIO_REG_LEN 0x363c +#define NVL_PCDS_PMC_MMIO_REG_LEN 0x3118 +#define NVL_PCHS_PMC_MMIO_REG_LEN 0x30d8 +#define NVL_LPM_PRI_OFFSET 0x17a4 +#define NVL_LPM_EN_OFFSET 0x17a0 +#define NVL_LPM_RESIDENCY_OFFSET 0x17a8 +#define NVL_LPM_LIVE_STATUS_OFFSET 0x1760 +#define NVL_LPM_NUM_MAPS 15 +#define NVL_PCDH_NUM_S0IX_BLOCKER 107 +#define NVL_PCDS_NUM_S0IX_BLOCKER 71 +#define NVL_PCHS_NUM_S0IX_BLOCKER 54 +#define NVL_PCDS_PMC_LTR_RESERVED 0x1bac +#define NVL_PCDH_BLK_REQ_OFFSET 53 +#define NVL_PCDS_BLK_REQ_OFFSET 18 +#define NVL_PCHS_BLK_REQ_OFFSET 46 +#define NVL_PMT_PC_GUID 0x13000101 +#define NVL_PMT_DMU_GUID 0x1a000101 +#define NVL_LTR_BLK_OFFSET 64 +#define NVL_PKGC_BLK_OFFSET 4 +#define NVL_PMT_DMU_DIE_C6_OFFSET 25 + /* SSRAM PMC Device ID */ /* LNL */ #define PMC_DEVID_LNL_SOCM 0xa87f @@ -329,6 +357,11 @@ enum ppfear_regs { #define PMC_DEVID_MTL_IOEP 0x7ecf #define PMC_DEVID_MTL_IOEM 0x7ebf +/* NVL */ +#define PMC_DEVID_NVL_PCDH 0xd37e +#define PMC_DEVID_NVL_PCDS 0xd47e +#define PMC_DEVID_NVL_PCHS 0x6e27 + extern const char *pmc_lpm_modes[]; struct pmc_bit_map { @@ -425,6 +458,7 @@ struct pmc_info { * @ltr_ign: Holds LTR ignore data while suspended * @num_lpm_modes: Count of enabled modes * @lpm_en_modes: Array of enabled modes from lowest to highest priority + * @devid: Device ID of the SSRAM device * * pmc contains info about one power management controller device. */ @@ -436,6 +470,7 @@ struct pmc { u32 ltr_ign; u8 num_lpm_modes; u8 lpm_en_modes[LPM_MAX_NUM_MODES]; + u16 devid; }; /** @@ -453,6 +488,11 @@ struct pmc { * @suspend: Function to perform platform specific suspend * @resume: Function to perform platform specific resume * + * @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters + * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region + * @pkgc_blocker_counters: Array of PKGC blocker counters + * @pkgc_blocker_offset: Offset to PKGC blocker in telemetry region + * * pmc_dev contains info about power management controller device. */ struct pmc_dev { @@ -471,8 +511,14 @@ struct pmc_dev { u8 num_of_pkgc; u32 die_c6_offset; + struct telem_endpoint *pc_ep; struct telem_endpoint *punit_ep; struct pmc_info *regmap_list; + + const char **pkgc_ltr_blocker_counters; + u32 pkgc_ltr_blocker_offset; + const char **pkgc_blocker_counters; + u32 pkgc_blocker_offset; }; enum pmc_index { @@ -484,29 +530,45 @@ enum pmc_index { /** * struct pmc_dev_info - Structure to keep PMC device info - * @pci_func: Function number of the primary PMC * @dmu_guids: List of Die Management Unit GUID + * @pc_guid: GUID for telemetry region to read PKGC blocker info + * @pkgc_ltr_blocker_offset: Offset to PKGC LTR blockers in telemetry region + * @pkgc_blocker_offset:Offset to PKGC blocker in telemetry region + * @num_pmcs: Number of entries in @pmc_list + * @pmc_list: Index list of available PMC * @regmap_list: Pointer to a list of pmc_info structure that could be * available for the platform. When set, this field implies * SSRAM support. * @map: Pointer to a pmc_reg_map struct that contains platform * specific attributes of the primary PMC * @sub_req_show: File operations to show substate requirements + * @pkgc_ltr_blocker_counters: Array of PKGC LTR blocker counters + * @pkgc_blocker_counters: Array of PKGC blocker counters * @suspend: Function to perform platform specific suspend * @resume: Function to perform platform specific resume * @init: Function to perform platform specific init action * @sub_req: Function to achieve low power mode substate requirements + * @ssram_hidden: Some SSRAM devices are hidden on this platform + * @die_c6_offset: Telemetry offset to read Die C6 residency */ struct pmc_dev_info { - u8 pci_func; u32 *dmu_guids; + u32 pc_guid; + u32 pkgc_ltr_blocker_offset; + u32 pkgc_blocker_offset; + u8 num_pmcs; + const u8 *pmc_list; struct pmc_info *regmap_list; const struct pmc_reg_map *map; const struct file_operations *sub_req_show; + const char **pkgc_ltr_blocker_counters; + const char **pkgc_blocker_counters; void (*suspend)(struct pmc_dev *pmcdev); int (*resume)(struct pmc_dev *pmcdev); int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info); int (*sub_req)(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep); + bool ssram_hidden; + u32 die_c6_offset; }; extern const struct pmc_bit_map msr_map[]; @@ -529,13 +591,14 @@ extern const struct pmc_reg_map mtl_ioep_reg_map; extern const struct pmc_bit_map ptl_pcdp_clocksource_status_map[]; extern const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[]; extern const struct pmc_bit_map ptl_pcdp_signal_status_map[]; +extern const struct pmc_bit_map ptl_pcdp_ltr_show_map[]; void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev); int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore); int pmc_core_resume_common(struct pmc_dev *pmcdev); int get_primary_reg_base(struct pmc *pmc); -void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids); +void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info); void pmc_core_set_device_d3(unsigned int device); int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info); @@ -552,6 +615,8 @@ extern struct pmc_dev_info arl_h_pmc_dev; extern struct pmc_dev_info lnl_pmc_dev; extern struct pmc_dev_info ptl_pmc_dev; extern struct pmc_dev_info wcl_pmc_dev; +extern struct pmc_dev_info nvl_s_pmc_dev; +extern struct pmc_dev_info nvl_h_pmc_dev; void cnl_suspend(struct pmc_dev *pmcdev); int cnl_resume(struct pmc_dev *pmcdev); @@ -562,6 +627,8 @@ int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc, extern const struct file_operations pmc_core_substate_req_regs_fops; extern const struct file_operations pmc_core_substate_blk_req_fops; +extern const guid_t intel_vsec_guid; + #define pmc_for_each_mode(mode, pmc) \ for (unsigned int __i = 0, __cond; \ __cond = __i < (pmc)->num_lpm_modes, \ @@ -583,4 +650,13 @@ static const struct file_operations __name ## _fops = { \ .release = single_release, \ } +struct intel_vsec_header; +union acpi_object; + +/* Avoid checkpatch warning */ +typedef u32 (*acpi_disc_t)[PMT_DISC_DWORDS]; + +acpi_disc_t pmc_parse_telem_dsd(union acpi_object *obj, + struct intel_vsec_header *header); +union acpi_object *pmc_find_telem_guid(union acpi_object *dsd); #endif /* PMC_CORE_H */ diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c index 1cd81ee54dcf..5ff4922825d9 100644 --- a/drivers/platform/x86/intel/pmc/lnl.c +++ b/drivers/platform/x86/intel/pmc/lnl.c @@ -544,6 +544,8 @@ static struct pmc_info lnl_pmc_info_list[] = { {} }; +static const u8 lnl_pmc_list[] = {PMC_IDX_MAIN}; + #define LNL_NPU_PCI_DEV 0x643e #define LNL_IPU_PCI_DEV 0x645d @@ -571,7 +573,8 @@ static int lnl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in } struct pmc_dev_info lnl_pmc_dev = { - .pci_func = 2, + .num_pmcs = ARRAY_SIZE(lnl_pmc_list), + .pmc_list = lnl_pmc_list, .regmap_list = lnl_pmc_info_list, .map = &lnl_socm_reg_map, .sub_req_show = &pmc_core_substate_req_regs_fops, @@ -579,4 +582,5 @@ struct pmc_dev_info lnl_pmc_dev = { .resume = lnl_resume, .init = lnl_core_init, .sub_req = pmc_core_pmt_get_lpm_req, + .ssram_hidden = true, }; diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c index 57508cbf9cd4..9abeabd3dbe2 100644 --- a/drivers/platform/x86/intel/pmc/mtl.c +++ b/drivers/platform/x86/intel/pmc/mtl.c @@ -965,6 +965,8 @@ static struct pmc_info mtl_pmc_info_list[] = { {} }; +static const u8 mtl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_IOE}; + #define MTL_GNA_PCI_DEV 0x7e4c #define MTL_IPU_PCI_DEV 0x7d19 #define MTL_VPU_PCI_DEV 0x7d1d @@ -994,8 +996,9 @@ static int mtl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in static u32 MTL_PMT_DMU_GUIDS[] = {MTL_PMT_DMU_GUID, 0x0}; struct pmc_dev_info mtl_pmc_dev = { - .pci_func = 2, .dmu_guids = MTL_PMT_DMU_GUIDS, + .num_pmcs = ARRAY_SIZE(mtl_pmc_list), + .pmc_list = mtl_pmc_list, .regmap_list = mtl_pmc_info_list, .map = &mtl_socm_reg_map, .sub_req_show = &pmc_core_substate_req_regs_fops, @@ -1003,4 +1006,6 @@ struct pmc_dev_info mtl_pmc_dev = { .resume = mtl_resume, .init = mtl_core_init, .sub_req = pmc_core_pmt_get_lpm_req, + .ssram_hidden = true, + .die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET, }; diff --git a/drivers/platform/x86/intel/pmc/nvl.c b/drivers/platform/x86/intel/pmc/nvl.c new file mode 100644 index 000000000000..8dabf2511dd9 --- /dev/null +++ b/drivers/platform/x86/intel/pmc/nvl.c @@ -0,0 +1,1539 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains platform specific structure definitions + * and init function used by Nova Lake PCH. + * + * Copyright (c) 2026, Intel Corporation. + */ + +#include <linux/bits.h> +#include <linux/pci.h> + +#include "core.h" + +/* PMC SSRAM PMT Telemetry GUIDS */ +#define PCDH_LPM_REQ_GUID 0x01093101 +#define PCHS_LPM_REQ_GUID 0x01092101 +#define PCDS_LPM_REQ_GUID 0x01091102 + +/* + * Die Mapping to Product. + * Product PCDDie PCHDie + * NVL-H PCD-H None + * NVL-S PCD-S PCH-S + */ + +static const struct pmc_bit_map nvl_pcdh_pfear_map[] = { + {"PMC_PGD0", BIT(0)}, + {"FUSE_OSSE_PGD0", BIT(1)}, + {"SPI_PGD0", BIT(2)}, + {"XHCI_PGD0", BIT(3)}, + {"SPA_PGD0", BIT(4)}, + {"SPB_PGD0", BIT(5)}, + {"MPFPW2_PGD0", BIT(6)}, + {"GBE_PGD0", BIT(7)}, + + {"SBR16B20_PGD0", BIT(0)}, + {"DBG_SBR_PGD0", BIT(1)}, + {"SBR16B7_PGD0", BIT(2)}, + {"STRC_PGD0", BIT(3)}, + {"SBR16B8_PGD0", BIT(4)}, + {"D2D_DISP_PGD1", BIT(5)}, + {"LPSS_PGD0", BIT(6)}, + {"LPC_PGD0", BIT(7)}, + + {"SMB_PGD0", BIT(0)}, + {"ISH_PGD0", BIT(1)}, + {"SBR16B2_PGD0", BIT(2)}, + {"NPK_PGD0", BIT(3)}, + {"D2D_NOC_PGD1", BIT(4)}, + {"DBG_SBR16B_PGD0", BIT(5)}, + {"FUSE_PGD0", BIT(6)}, + {"SBR16B0_PGD0", BIT(7)}, + + {"P2SB0_PGD0", BIT(0)}, + {"OTG_PGD0", BIT(1)}, + {"EXI_PGD0", BIT(2)}, + {"CSE_PGD0", BIT(3)}, + {"CSME_KVM_PGD0", BIT(4)}, + {"CSME_PMT_PGD0", BIT(5)}, + {"CSME_CLINK_PGD0", BIT(6)}, + {"SBR16B21_PGD0", BIT(7)}, + + {"CSME_USBR_PGD0", BIT(0)}, + {"SBR16B22_PGD0", BIT(1)}, + {"CSME_SMT1_PGD0", BIT(2)}, + {"MPFPW1_PGD0", BIT(3)}, + {"CSME_SMS2_PGD0", BIT(4)}, + {"CSME_SMS_PGD0", BIT(5)}, + {"CSME_RTC_PGD0", BIT(6)}, + {"CSMEPSF_PGD0", BIT(7)}, + + {"D2D_NOC_PGD0", BIT(0)}, + {"ESE_PGD0", BIT(1)}, + {"SBR16B6_PGD0", BIT(2)}, + {"P2SB1_PGD0", BIT(3)}, + {"SBR16B3_PGD0", BIT(4)}, + {"OSSE_SMT1_PGD0", BIT(5)}, + {"D2D_DISP_PGD0", BIT(6)}, + {"SNPS_USB2_A_PGD0", BIT(7)}, + + {"U3FPW1_PGD0", BIT(0)}, + {"FIA_X_PGD0", BIT(1)}, + {"PSF4_PGD0", BIT(2)}, + {"CNVI_PGD0", BIT(3)}, + {"UFSX2_PGD0", BIT(4)}, + {"ENDBG_PGD0", BIT(5)}, + {"DBC_PGD0", BIT(6)}, + {"FIA_PG_PGD0", BIT(7)}, + + {"D2D_IPU_PGD0", BIT(0)}, + {"NPK_PGD1", BIT(1)}, + {"FIACPCB_X_PGD0", BIT(2)}, + {"SBR8B4_PGD0", BIT(3)}, + {"DBG_PSF_PGD0", BIT(4)}, + {"PSF6_PGD0", BIT(5)}, + {"UFSPW1_PGD0", BIT(6)}, + {"FIA_U_PGD0", BIT(7)}, + + {"PSF8_PGD0", BIT(0)}, + {"SBR16B9_PGD0", BIT(1)}, + {"PSF0_PGD0", BIT(2)}, + {"FIACPCB_U_PGD0", BIT(3)}, + {"TAM_PGD0", BIT(4)}, + {"D2D_NOC_PGD2", BIT(5)}, + {"SBR8B2_PGD0", BIT(6)}, + {"THC0_PGD0", BIT(7)}, + + {"THC1_PGD0", BIT(0)}, + {"PMC_PGD1", BIT(1)}, + {"DISP_PGA1_PGD0", BIT(2)}, + {"TCSS_PGD0", BIT(3)}, + {"DISP_PGA_PGD0", BIT(4)}, + {"SBR16B1_PGD0", BIT(5)}, + {"SBRG_PGD0", BIT(6)}, + {"PSF5_PGD0", BIT(7)}, + + {"SBR8B3_PGD0", BIT(0)}, + {"ACE_PGD0", BIT(1)}, + {"ACE_PGD1", BIT(2)}, + {"ACE_PGD2", BIT(3)}, + {"ACE_PGD3", BIT(4)}, + {"ACE_PGD4", BIT(5)}, + {"ACE_PGD5", BIT(6)}, + {"ACE_PGD6", BIT(7)}, + + {"ACE_PGD7", BIT(0)}, + {"ACE_PGD8", BIT(1)}, + {"ACE_PGD9", BIT(2)}, + {"ACE_PGD10", BIT(3)}, + {"FIACPCB_PG_PGD0", BIT(4)}, + {"SNPS_USB2_B_PGD0", BIT(5)}, + {"OSSE_PGD0", BIT(6)}, + {"SBR8B0_PGD0", BIT(7)}, + + {"SBR16B4_PGD0", BIT(0)}, + {"CSME_PTIO_PGD0", BIT(1)}, + {} +}; + +static const struct pmc_bit_map *ext_nvl_pcdh_pfear_map[] = { + nvl_pcdh_pfear_map, + NULL +}; + +static const struct pmc_bit_map nvl_pcdh_clocksource_status_map[] = { + {"AON2_OFF_STS", BIT(0), 1}, + {"AON3_OFF_STS", BIT(1), 0}, + {"AON4_OFF_STS", BIT(2), 1}, + {"AON5_OFF_STS", BIT(3), 1}, + {"AON1_OFF_STS", BIT(4), 0}, + {"XTAL_LVM_OFF_STS", BIT(5), 0}, + {"MPFPW1_0_PLL_OFF_STS", BIT(6), 1}, + {"D2D_PLL_OFF_STS", BIT(7), 1}, + {"USB3_PLL_OFF_STS", BIT(8), 1}, + {"AON3_SPL_OFF_STS", BIT(9), 1}, + {"MPFPW2_0_PLL_OFF_STS", BIT(12), 1}, + {"XTAL_AGGR_OFF_STS", BIT(17), 1}, + {"USB2_PLL_OFF_STS", BIT(18), 0}, + {"DDI2_PLL_OFF_STS", BIT(19), 1}, + {"SE_TCSS_PLL_OFF_STS", BIT(20), 1}, + {"DDI_PLL_OFF_STS", BIT(21), 1}, + {"FILTER_PLL_OFF_STS", BIT(22), 1}, + {"ACE_PLL_OFF_STS", BIT(24), 0}, + {"FABRIC_PLL_OFF_STS", BIT(25), 1}, + {"SOC_PLL_OFF_STS", BIT(26), 1}, + {"REF_PLL_OFF_STS", BIT(28), 1}, + {"IMG_PLL_OFF_STS", BIT(29), 1}, + {"GENLOCK_FILTER_PLL_OFF_STS", BIT(30), 1}, + {"RTC_PLL_OFF_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_power_gating_status_0_map[] = { + {"PMC_PGD0_PG_STS", BIT(0), 0}, + {"FUSE_OSSE_PGD0_PG_STS", BIT(1), 0}, + {"ESPISPI_PGD0_PG_STS", BIT(2), 0}, + {"XHCI_PGD0_PG_STS", BIT(3), 1}, + {"SPA_PGD0_PG_STS", BIT(4), 1}, + {"SPB_PGD0_PG_STS", BIT(5), 1}, + {"MPFPW2_PGD0_PG_STS", BIT(6), 0}, + {"GBE_PGD0_PG_STS", BIT(7), 1}, + {"SBR16B20_PGD0_PG_STS", BIT(8), 0}, + {"DBG_PGD0_PG_STS", BIT(9), 0}, + {"SBR16B7_PGD0_PG_STS", BIT(10), 0}, + {"STRC_PGD0_PG_STS", BIT(11), 0}, + {"SBR16B8_PGD0_PG_STS", BIT(12), 0}, + {"D2D_DISP_PGD1_PG_STS", BIT(13), 1}, + {"LPSS_PGD0_PG_STS", BIT(14), 1}, + {"LPC_PGD0_PG_STS", BIT(15), 0}, + {"SMB_PGD0_PG_STS", BIT(16), 0}, + {"ISH_PGD0_PG_STS", BIT(17), 0}, + {"SBR16B2_PGD0_PG_STS", BIT(18), 0}, + {"NPK_PGD0_PG_STS", BIT(19), 0}, + {"D2D_NOC_PGD1_PG_STS", BIT(20), 1}, + {"DBG_SBR16B_PGD0_PG_STS", BIT(21), 0}, + {"FUSE_PGD0_PG_STS", BIT(22), 0}, + {"SBR16B0_PGD0_PG_STS", BIT(23), 0}, + {"P2SB0_PGD0_PG_STS", BIT(24), 1}, + {"XDCI_PGD0_PG_STS", BIT(25), 1}, + {"EXI_PGD0_PG_STS", BIT(26), 0}, + {"CSE_PGD0_PG_STS", BIT(27), 1}, + {"KVMCC_PGD0_PG_STS", BIT(28), 1}, + {"PMT_PGD0_PG_STS", BIT(29), 1}, + {"CLINK_PGD0_PG_STS", BIT(30), 1}, + {"SBR16B21_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_power_gating_status_1_map[] = { + {"USBR0_PGD0_PG_STS", BIT(0), 1}, + {"SBR16B22_PGD0_PG_STS", BIT(1), 0}, + {"SMT1_PGD0_PG_STS", BIT(2), 1}, + {"MPFPW1_PGD0_PG_STS", BIT(3), 0}, + {"SMS2_PGD0_PG_STS", BIT(4), 1}, + {"SMS1_PGD0_PG_STS", BIT(5), 1}, + {"CSMERTC_PGD0_PG_STS", BIT(6), 0}, + {"CSMEPSF_PGD0_PG_STS", BIT(7), 0}, + {"D2D_NOC_PGD0_PG_STS", BIT(8), 0}, + {"ESE_PGD0_PG_STS", BIT(9), 1}, + {"SBR16B6_PGD0_PG_STS", BIT(10), 0}, + {"P2SB1_PGD0_PG_STS", BIT(11), 1}, + {"SBR16B3_PGD0_PG_STS", BIT(12), 0}, + {"OSSE_SMT1_PGD0_PG_STS", BIT(13), 1}, + {"D2D_DISP_PGD0_PG_STS", BIT(14), 1}, + {"SNPA_USB2_A_PGD0_PG_STS", BIT(15), 0}, + {"U3FPW1_PGD0_PG_STS", BIT(16), 0}, + {"FIA_X_PGD0_PG_STS", BIT(17), 0}, + {"PSF4_PGD0_PG_STS", BIT(18), 0}, + {"CNVI_PGD0_PG_STS", BIT(19), 0}, + {"UFSX2_PGD0_PG_STS", BIT(20), 1}, + {"ENDBG_PGD0_PG_STS", BIT(21), 0}, + {"DBC_PGD0_PG_STS", BIT(22), 0}, + {"FIA_PG_PGD0_PG_STS", BIT(23), 0}, + {"D2D_IPU_PGD0_PG_STS", BIT(24), 1}, + {"NPK_PGD1_PG_STS", BIT(25), 0}, + {"FIACPCB_X_PGD0_PG_STS", BIT(26), 0}, + {"SBR8B4_PGD0_PG_STS", BIT(27), 0}, + {"DBG_PSF_PGD0_PG_STS", BIT(28), 0}, + {"PSF6_PGD0_PG_STS", BIT(29), 0}, + {"UFSPW1_PGD0_PG_STS", BIT(30), 0}, + {"FIA_U_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_power_gating_status_2_map[] = { + {"PSF8_PGD0_PG_STS", BIT(0), 0}, + {"SBR16B9_PGD0_PG_STS", BIT(1), 0}, + {"PSF0_PGD0_PG_STS", BIT(2), 0}, + {"FIACPCB_U_PGD0_PG_STS", BIT(3), 0}, + {"TAM_PGD0_PG_STS", BIT(4), 1}, + {"D2D_NOC_PGD2_PG_STS", BIT(5), 1}, + {"SBR8B2_PGD0_PG_STS", BIT(6), 0}, + {"THC0_PGD0_PG_STS", BIT(7), 1}, + {"THC1_PGD0_PG_STS", BIT(8), 1}, + {"PMC_PGD1_PG_STS", BIT(9), 0}, + {"DISP_PGA1_PGD0_PG_STS", BIT(10), 0}, + {"TCSS_PGD0_PG_STS", BIT(11), 0}, + {"DISP_PGA_PGD0_PG_STS", BIT(12), 0}, + {"SBR16B1_PGD0_PG_STS", BIT(13), 0}, + {"SBRG_PGD0_PG_STS", BIT(14), 0}, + {"PSF5_PGD0_PG_STS", BIT(15), 0}, + {"SBR8B3_PGD0_PG_STS", BIT(16), 0}, + {"ACE_PGD0_PG_STS", BIT(17), 0}, + {"ACE_PGD1_PG_STS", BIT(18), 0}, + {"ACE_PGD2_PG_STS", BIT(19), 0}, + {"ACE_PGD3_PG_STS", BIT(20), 0}, + {"ACE_PGD4_PG_STS", BIT(21), 0}, + {"ACE_PGD5_PG_STS", BIT(22), 0}, + {"ACE_PGD6_PG_STS", BIT(23), 0}, + {"ACE_PGD7_PG_STS", BIT(24), 0}, + {"ACE_PGD8_PG_STS", BIT(25), 0}, + {"ACE_PGD9_PG_STS", BIT(26), 0}, + {"ACE_PGD10_PG_STS", BIT(27), 0}, + {"FIACPCB_PG_PGD0_PG_STS", BIT(28), 0}, + {"SNPS_USB2_B_PGD0_PG_STS", BIT(29), 0}, + {"OSSE_PGD0_PG_STS", BIT(30), 1}, + {"SBR8B0_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_power_gating_status_3_map[] = { + {"SBR16B4_PGD0_PG_STS", BIT(0), 0}, + {"PTIO_PGD0_PG_STS", BIT(1), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_d3_status_0_map[] = { + {"LPSS_D3_STS", BIT(3), 1}, + {"XDCI_D3_STS", BIT(4), 1}, + {"XHCI_D3_STS", BIT(5), 1}, + {"OSSE_D3_STS", BIT(6), 0}, + {"SPA_D3_STS", BIT(12), 0}, + {"SPB_D3_STS", BIT(13), 0}, + {"ESPISPI_D3_STS", BIT(18), 0}, + {"PSTH_D3_STS", BIT(21), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_d3_status_1_map[] = { + {"OSSE_SMT1_D3_STS", BIT(0), 0}, + {"GBE_D3_STS", BIT(19), 0}, + {"ITSS_D3_STS", BIT(23), 0}, + {"CNVI_D3_STS", BIT(27), 0}, + {"UFSX2_D3_STS", BIT(28), 0}, + {"ESE_D3_STS", BIT(29), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_d3_status_2_map[] = { + {"CSMERTC_D3_STS", BIT(1), 0}, + {"CSE_D3_STS", BIT(4), 0}, + {"KVMCC_D3_STS", BIT(5), 0}, + {"USBR0_D3_STS", BIT(6), 0}, + {"ISH_D3_STS", BIT(7), 0}, + {"SMT1_D3_STS", BIT(8), 0}, + {"SMT2_D3_STS", BIT(9), 0}, + {"SMT3_D3_STS", BIT(10), 0}, + {"OSSE_SMT2_D3_STS", BIT(11), 0}, + {"CLINK_D3_STS", BIT(14), 0}, + {"PTIO_D3_STS", BIT(16), 0}, + {"PMT_D3_STS", BIT(17), 0}, + {"SMS1_D3_STS", BIT(18), 0}, + {"SMS2_D3_STS", BIT(19), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_d3_status_3_map[] = { + {"THC0_D3_STS", BIT(14), 1}, + {"THC1_D3_STS", BIT(15), 1}, + {"OSSE_SMT3_D3_STS", BIT(16), 0}, + {"ACE_D3_STS", BIT(23), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_vnn_req_status_0_map[] = { + {"LPSS_VNN_REQ_STS", BIT(3), 1}, + {"OSSE_VNN_REQ_STS", BIT(6), 1}, + {"ESPISPI_VNN_REQ_STS", BIT(18), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_vnn_req_status_1_map[] = { + {"OSSE_SMT1_VNN_REQ_STS", BIT(0), 1}, + {"NPK_VNN_REQ_STS", BIT(4), 1}, + {"DFXAGG_VNN_REQ_STS", BIT(8), 0}, + {"EXI_VNN_REQ_STS", BIT(9), 1}, + {"P2D_VNN_REQ_STS", BIT(18), 1}, + {"GBE_VNN_REQ_STS", BIT(19), 1}, + {"SMB_VNN_REQ_STS", BIT(25), 1}, + {"LPC_VNN_REQ_STS", BIT(26), 0}, + {"ESE_VNN_REQ_STS", BIT(29), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_vnn_req_status_2_map[] = { + {"CSMERTC_VNN_REQ_STS", BIT(1), 1}, + {"CSE_VNN_REQ_STS", BIT(4), 1}, + {"ISH_VNN_REQ_STS", BIT(7), 1}, + {"SMT1_VNN_REQ_STS", BIT(8), 1}, + {"CLINK_VNN_REQ_STS", BIT(14), 1}, + {"SMS1_VNN_REQ_STS", BIT(18), 1}, + {"SMS2_VNN_REQ_STS", BIT(19), 1}, + {"GPIOCOM4_VNN_REQ_STS", BIT(20), 1}, + {"GPIOCOM3_VNN_REQ_STS", BIT(21), 1}, + {"DISP_SHIM_VNN_REQ_STS", BIT(22), 1}, + {"GPIOCOM1_VNN_REQ_STS", BIT(23), 1}, + {"GPIOCOM0_VNN_REQ_STS", BIT(24), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_vnn_req_status_3_map[] = { + {"DTS0_VNN_REQ_STS", BIT(7), 0}, + {"GPIOCOM5_VNN_REQ_STS", BIT(11), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_vnn_misc_status_map[] = { + {"CPU_C10_REQ_STS", BIT(0), 0}, + {"TS_OFF_REQ_STS", BIT(1), 0}, + {"PNDE_MET_REQ_STS", BIT(2), 1}, + {"PG5_PMA0_REQ_STS", BIT(3), 1}, + {"FW_THROTTLE_ALLOWED_REQ_STS", BIT(4), 0}, + {"VNN_SOC_REQ_STS", BIT(6), 1}, + {"ISH_VNNAON_REQ_STS", BIT(7), 0}, + {"D2D_NOC_CFI_QACTIVE_REQ_STS", BIT(8), 1}, + {"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9), 1}, + {"D2D_IPU_QACTIVE_REQ_STS", BIT(10), 1}, + {"PLT_GREATER_REQ_STS", BIT(11), 1}, + {"ALL_SBR_IDLE_REQ_STS", BIT(12), 0}, + {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13), 0}, + {"PM_SYNC_STATES_REQ_STS", BIT(14), 0}, + {"EA_REQ_STS", BIT(15), 0}, + {"MPHY_CORE_OFF_REQ_STS", BIT(16), 0}, + {"BRK_EV_EN_REQ_STS", BIT(17), 0}, + {"AUTO_DEMO_EN_REQ_STS", BIT(18), 0}, + {"ITSS_CLK_SRC_REQ_STS", BIT(19), 1}, + {"ARC_IDLE_REQ_STS", BIT(21), 0}, + {"PG5_PMA1_REQ_STS", BIT(22), 1}, + {"FIA_DEEP_PM_REQ_STS", BIT(23), 0}, + {"XDCI_ATTACHED_REQ_STS", BIT(24), 1}, + {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25), 0}, + {"D2D_DISP_DDI_QACTIVE_REQ_STS", BIT(26), 1}, + {"PRE_WAKE0_REQ_STS", BIT(27), 1}, + {"PRE_WAKE1_REQ_STS", BIT(28), 1}, + {"PRE_WAKE2_REQ_STS", BIT(29), 1}, + {"PG5_PMA2_GVNN", BIT(30), 1}, + {"D2D_DISP_EDP_QACTIVE_REQ_STS", BIT(31), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcdh_rsc_status_map[] = { + {"CORE", 0, 1}, + {"Memory", 0, 1}, + {"PRIM_D2D", 0, 1}, + {"PSF0", 0, 1}, + {"PSF4", 0, 1}, + {"PSF6", 0, 1}, + {"PSF8", 0, 1}, + {"SB", 0, 1}, + {} +}; + +static const struct pmc_bit_map *nvl_pcdh_lpm_maps[] = { + nvl_pcdh_clocksource_status_map, + nvl_pcdh_power_gating_status_0_map, + nvl_pcdh_power_gating_status_1_map, + nvl_pcdh_power_gating_status_2_map, + nvl_pcdh_power_gating_status_3_map, + nvl_pcdh_d3_status_0_map, + nvl_pcdh_d3_status_1_map, + nvl_pcdh_d3_status_2_map, + nvl_pcdh_d3_status_3_map, + nvl_pcdh_vnn_req_status_0_map, + nvl_pcdh_vnn_req_status_1_map, + nvl_pcdh_vnn_req_status_2_map, + nvl_pcdh_vnn_req_status_3_map, + nvl_pcdh_vnn_misc_status_map, + ptl_pcdp_signal_status_map, + NULL +}; + +static const struct pmc_bit_map *nvl_pcdh_blk_maps[] = { + nvl_pcdh_power_gating_status_0_map, + nvl_pcdh_power_gating_status_1_map, + nvl_pcdh_power_gating_status_2_map, + nvl_pcdh_power_gating_status_3_map, + nvl_pcdh_rsc_status_map, + nvl_pcdh_vnn_req_status_0_map, + nvl_pcdh_vnn_req_status_1_map, + nvl_pcdh_vnn_req_status_2_map, + nvl_pcdh_vnn_req_status_3_map, + nvl_pcdh_d3_status_0_map, + nvl_pcdh_d3_status_1_map, + nvl_pcdh_d3_status_2_map, + nvl_pcdh_d3_status_3_map, + nvl_pcdh_clocksource_status_map, + nvl_pcdh_vnn_misc_status_map, + ptl_pcdp_signal_status_map, + NULL +}; + +static const struct pmc_bit_map nvl_pcds_pfear_map[] = { + {"PMC_PGD0", BIT(0)}, + {"FUSE_OSSE_PGD0", BIT(1)}, + {"SPI_PGD0", BIT(2)}, + {"XHCI_PGD0", BIT(3)}, + {"SPA_PGD0", BIT(4)}, + {"SPB_PGD0", BIT(5)}, + {"RSVD6", BIT(6)}, + {"GBE_PGD0", BIT(7)}, + + {"RSVD8", BIT(0)}, + {"RSVD9", BIT(1)}, + {"SBR16B7_PGD0", BIT(2)}, + {"SBR16B21_PGD0", BIT(3)}, + {"RSVD12", BIT(4)}, + {"D2D_DISP_PGD1", BIT(5)}, + {"LPSS_PGD0", BIT(6)}, + {"LPC_PGD0", BIT(7)}, + + {"SMB_PGD0", BIT(0)}, + {"ISH_PGD0", BIT(1)}, + {"SBR16B1_PGD0", BIT(2)}, + {"NPK_PGD0", BIT(3)}, + {"D2D_NOC_PGD1", BIT(4)}, + {"DBG_SBR16B_PGD0", BIT(5)}, + {"FUSE_PGD0", BIT(6)}, + {"RSVD23", BIT(7)}, + + {"P2SB0_PGD0", BIT(0)}, + {"OTG_PGD0", BIT(1)}, + {"EXI_PGD0", BIT(2)}, + {"CSE_PGD0", BIT(3)}, + {"CSME_KVM_PGD0", BIT(4)}, + {"CSME_PMT_PGD0", BIT(5)}, + {"CSME_CLINK_PGD0", BIT(6)}, + {"CSME_PTIO_PGD0", BIT(7)}, + + {"CSME_USBR_PGD0", BIT(0)}, + {"SBR16B22_PGD0", BIT(1)}, + {"CSME_SMT1_PGD0", BIT(2)}, + {"P2SB1_PGD0", BIT(3)}, + {"CSME_SMS2_PGD0", BIT(4)}, + {"CSME_SMS_PGD0", BIT(5)}, + {"CSME_RTC_PGD0", BIT(6)}, + {"CSMEPSF_PGD0", BIT(7)}, + + {"D2D_NOC_PGD0", BIT(0)}, + {"RSVD41", BIT(1)}, + {"RSVD42", BIT(2)}, + {"RSVD43", BIT(3)}, + {"SBR16B2_PGD0", BIT(4)}, + {"OSSE_SMT1_PGD0", BIT(5)}, + {"D2D_DISP_PGD0", BIT(6)}, + {"RSVD47_PGD0", BIT(7)}, + + {"RSVD48", BIT(0)}, + {"DBG_PSF_PGD0", BIT(1)}, + {"RSVD50", BIT(2)}, + {"CNVI_PGD0", BIT(3)}, + {"UFSX2_PGD0", BIT(4)}, + {"ENDBG_PGD0", BIT(5)}, + {"DBC_PGD0", BIT(6)}, + {"SBR16B4_PGD0", BIT(7)}, + + {"RSVD56", BIT(0)}, + {"NPK_PGD1", BIT(1)}, + {"RSVD58", BIT(2)}, + {"SBR16B20_PGD0", BIT(3)}, + {"RSVD60", BIT(4)}, + {"SBR8B20_PGD0", BIT(5)}, + {"RSVD62", BIT(6)}, + {"FIA_U_PGD0", BIT(7)}, + + {"PSF8_PGD0", BIT(0)}, + {"RSVD65", BIT(1)}, + {"RSVD66", BIT(2)}, + {"FIACPCB_U_PGD0", BIT(3)}, + {"TAM_PGD0", BIT(4)}, + {"D2D_NOC_PGD2", BIT(5)}, + {"SBR8B2_PGD0", BIT(6)}, + {"THC0_PGD0", BIT(7)}, + + {"THC1_PGD0", BIT(0)}, + {"PMC_PGD1", BIT(1)}, + {"SBR16B3_PGD0", BIT(2)}, + {"TCSS_PGD0", BIT(3)}, + {"DISP_PGA_PGD0", BIT(4)}, + {"RSVD77", BIT(5)}, + {"RSVD78", BIT(6)}, + {"RSVD79", BIT(7)}, + + {"SBRG_PGD0", BIT(0)}, + {"RSVD81", BIT(1)}, + {"SBR16B0_PGD0", BIT(2)}, + {"SBR8B0_PGD0", BIT(3)}, + {"PSF7_PGD0", BIT(4)}, + {"RSVD85", BIT(5)}, + {"RSVD86", BIT(6)}, + {"RSVD87", BIT(7)}, + + {"SBR16B6_PGD0", BIT(0)}, + {"PSD0_PGD0", BIT(1)}, + {"STRC_PGD0", BIT(2)}, + {"RSVD91", BIT(3)}, + {"DBG_SBR_PGD0", BIT(4)}, + {"RSVD93", BIT(5)}, + {"OSSE_PGD0", BIT(6)}, + {"DISP_PGA1_PGD0", BIT(7)}, + {} +}; + +static const struct pmc_bit_map *ext_nvl_pcds_pfear_map[] = { + nvl_pcds_pfear_map, + NULL +}; + +static const struct pmc_bit_map nvl_pcds_ltr_show_map[] = { + {"SOUTHPORT_A", CNP_PMC_LTR_SPA}, + {"SOUTHPORT_B", CNP_PMC_LTR_SPB}, + {"SATA", CNP_PMC_LTR_SATA}, + {"GIGABIT_ETHERNET", CNP_PMC_LTR_GBE}, + {"XHCI", CNP_PMC_LTR_XHCI}, + {"SOUTHPORT_F", ADL_PMC_LTR_SPF}, + {"ME", CNP_PMC_LTR_ME}, + {"SATA1", CNP_PMC_LTR_EVA}, + {"SOUTHPORT_C", CNP_PMC_LTR_SPC}, + {"HD_AUDIO", CNP_PMC_LTR_AZ}, + {"CNV", CNP_PMC_LTR_CNV}, + {"LPSS", CNP_PMC_LTR_LPSS}, + {"SOUTHPORT_D", CNP_PMC_LTR_SPD}, + {"SOUTHPORT_E", CNP_PMC_LTR_SPE}, + {"SATA2", PTL_PMC_LTR_SATA2}, + {"ESPI", CNP_PMC_LTR_ESPI}, + {"SCC", CNP_PMC_LTR_SCC}, + {"ISH", CNP_PMC_LTR_ISH}, + {"UFSX2", CNP_PMC_LTR_UFSX2}, + {"EMMC", CNP_PMC_LTR_EMMC}, + {"WIGIG", ICL_PMC_LTR_WIGIG}, + {"THC0", TGL_PMC_LTR_THC0}, + {"THC1", TGL_PMC_LTR_THC1}, + {"SOUTHPORT_G", MTL_PMC_LTR_SPG}, + {"RSVD", NVL_PCDS_PMC_LTR_RESERVED}, + {"IOE_PMC", MTL_PMC_LTR_IOE_PMC}, + {"DMI3", ARL_PMC_LTR_DMI3}, + {"OSSE", LNL_PMC_LTR_OSSE}, + + /* Below two cannot be used for LTR_IGNORE */ + {"CURRENT_PLATFORM", PTL_PMC_LTR_CUR_PLT}, + {"AGGREGATED_SYSTEM", PTL_PMC_LTR_CUR_ASLT}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_clocksource_status_map[] = { + {"AON2_OFF_STS", BIT(0), 1}, + {"AON3_OFF_STS", BIT(1), 0}, + {"AON4_OFF_STS", BIT(2), 1}, + {"AON5_OFF_STS", BIT(3), 1}, + {"AON1_OFF_STS", BIT(4), 0}, + {"XTAL_LVM_OFF_STS", BIT(5), 0}, + {"D2D_OFF_STS", BIT(8), 1}, + {"AON3_SPL_OFF_STS", BIT(9), 1}, + {"XTAL_AGGR_OFF_STS", BIT(17), 1}, + {"BCLK_EXT_INJ_OFF_STS", BIT(18), 1}, + {"DDI2_PLL_OFF_STS", BIT(19), 1}, + {"SE_TCSS_PLL_OFF_STS", BIT(20), 1}, + {"DDI_PLL_OFF_STS", BIT(21), 1}, + {"FILTER_PLL_OFF_STS", BIT(22), 1}, + {"PHY_OC_EXT_INJ_OFF_STS", BIT(23), 1}, + {"ACE_PLL_OFF_STS", BIT(24), 0}, + {"FABRIC_PLL_OFF_STS", BIT(25), 1}, + {"SOC_PLL_OFF_STS", BIT(26), 1}, + {"REF_PLL_OFF_STS", BIT(28), 1}, + {"GENLOCK_FILTER_PLL_OFF_STS", BIT(30), 1}, + {"RTC_PLL_OFF_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_power_gating_status_0_map[] = { + {"PMC_PGD0_PG_STS", BIT(0), 0}, + {"FUSE_OSSE_PGD0_PG_STS", BIT(1), 0}, + {"ESPISPI_PGD0_PG_STS", BIT(2), 0}, + {"XHCI_PGD0_PG_STS", BIT(3), 0}, + {"SPA_PGD0_PG_STS", BIT(4), 0}, + {"SPB_PGD0_PG_STS", BIT(5), 0}, + {"RSVD_6", BIT(6), 0}, + {"GBE_PGD0_PG_STS", BIT(7), 0}, + {"RSVD_8", BIT(8), 0}, + {"RSVD_9", BIT(9), 0}, + {"SBR16B7_PGD0_PG_STS", BIT(10), 0}, + {"SBR16B21_PGD0_PG_STS", BIT(11), 0}, + {"RSVD_12", BIT(12), 0}, + {"D2D_DISP_PGD1_PG_STS", BIT(13), 1}, + {"LPSS_PGD0_PG_STS", BIT(14), 0}, + {"LPC_PGD0_PG_STS", BIT(15), 0}, + {"SMB_PGD0_PG_STS", BIT(16), 0}, + {"ISH_PGD0_PG_STS", BIT(17), 0}, + {"SBR16B1_PGD0_PG_STS", BIT(18), 0}, + {"NPK_PGD0_PG_STS", BIT(19), 0}, + {"D2D_NOC_PGD1_PG_STS", BIT(20), 1}, + {"DBG_SBR16B_PGD0_PG_STS", BIT(21), 0}, + {"FUSE_PGD0_PG_STS", BIT(22), 0}, + {"RSVD_23", BIT(23), 0}, + {"P2SB0_PGD0_PG_STS", BIT(24), 1}, + {"XDCI_PGD0_PG_STS", BIT(25), 0}, + {"EXI_PGD0_PG_STS", BIT(26), 0}, + {"CSE_PGD0_PG_STS", BIT(27), 1}, + {"KVMCC_PGD0_PG_STS", BIT(28), 0}, + {"PMT_PGD0_PG_STS", BIT(29), 0}, + {"CLINK_PGD0_PG_STS", BIT(30), 0}, + {"PTIO_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_power_gating_status_1_map[] = { + {"USBR0_PGD0_PG_STS", BIT(0), 0}, + {"SBR16B22_PGD0_PG_STS", BIT(1), 0}, + {"SMT1_PGD0_PG_STS", BIT(2), 0}, + {"P2SB1_PGD0_PG_STS", BIT(3), 1}, + {"SMS2_PGD0_PG_STS", BIT(4), 0}, + {"SMS1_PGD0_PG_STS", BIT(5), 0}, + {"CSMERTC_PGD0_PG_STS", BIT(6), 0}, + {"CSMEPSF_PGD0_PG_STS", BIT(7), 0}, + {"D2D_NOC_PGD0_PG_STS", BIT(8), 0}, + {"RSVD_9", BIT(9), 0}, + {"RSVD_10", BIT(10), 0}, + {"RSVD_11", BIT(11), 0}, + {"SBR16B2_PGD0_PG_STS", BIT(12), 0}, + {"OSSE_SMT1_PGD0_PG_STS", BIT(13), 1}, + {"D2D_DISP_PGD0_PG_STS", BIT(14), 1}, + {"RSVD_15", BIT(15), 0}, + {"RSVD_16", BIT(16), 0}, + {"DBG_PSF_PGD0_PG_STS", BIT(17), 0}, + {"RSVD_18", BIT(18), 0}, + {"CNVI_PGD0_PG_STS", BIT(19), 0}, + {"UFSX2_PGD0_PG_STS", BIT(20), 0}, + {"ENDBG_PGD0_PG_STS", BIT(21), 0}, + {"DBC_PGD0_PG_STS", BIT(22), 0}, + {"SBR16B4_PGD0_PG_STS", BIT(23), 0}, + {"RSVD_24", BIT(24), 0}, + {"NPK_PGD1_PG_STS", BIT(25), 0}, + {"RSVD_26", BIT(26), 0}, + {"SBR16B20_PGD0_PG_STS", BIT(27), 0}, + {"RSVD_28", BIT(28), 0}, + {"SBR8B20_PGD0_PG_STS", BIT(29), 0}, + {"RSVD_30", BIT(30), 0}, + {"FIA_U_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_power_gating_status_2_map[] = { + {"PSF8_PGD0_PG_STS", BIT(0), 0}, + {"RSVD_1", BIT(1), 0}, + {"RSVD_2", BIT(2), 0}, + {"FIACPCB_U_PGD0_PG_STS", BIT(3), 0}, + {"TAM_PGD0_PG_STS", BIT(4), 1}, + {"D2D_NOC_PGD2_PG_STS", BIT(5), 1}, + {"SBR8B2_PGD0_PG_STS", BIT(6), 0}, + {"THC0_PGD0_PG_STS", BIT(7), 0}, + {"THC1_PGD0_PG_STS", BIT(8), 0}, + {"PMC_PGD1_PG_STS", BIT(9), 0}, + {"SBR16B3_PGD0_PG_STS", BIT(10), 0}, + {"TCSS_PGD0_PG_STS", BIT(11), 0}, + {"DISP_PGA_PGD0_PG_STS", BIT(12), 0}, + {"RSVD_13", BIT(13), 0}, + {"RSVD_14", BIT(14), 0}, + {"RSVD_15", BIT(15), 0}, + {"SBRG_PGD0_PG_STS", BIT(16), 0}, + {"RSVD_17", BIT(17), 0}, + {"SBR16B0_PGD0_PG_STS", BIT(18), 0}, + {"SBR8B0_PGD0_PG_STS", BIT(19), 0}, + {"PSF7_PGD0_PG_STS", BIT(20), 0}, + {"RSVD_21", BIT(21), 0}, + {"RSVD_22", BIT(22), 0}, + {"RSVD_23", BIT(23), 0}, + {"SBR16B6_PGD0_PG_STS", BIT(24), 0}, + {"PSF0_PGD0_PG_STS", BIT(25), 0}, + {"STRC_PGD0_PG_STS", BIT(26), 0}, + {"RSVD_27", BIT(27), 0}, + {"DBG_SBR_PGD0_PG_STS", BIT(28), 0}, + {"RSVD_29", BIT(29), 0}, + {"OSSE_PGD0_PG_STS", BIT(30), 1}, + {"DISP_PGA1_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_d3_status_0_map[] = { + {"LPSS_D3_STS", BIT(3), 1}, + {"XDCI_D3_STS", BIT(4), 1}, + {"XHCI_D3_STS", BIT(5), 1}, + {"SPA_D3_STS", BIT(12), 0}, + {"SPB_D3_STS", BIT(13), 0}, + {"ESPISPI_D3_STS", BIT(18), 0}, + {"PSTH_D3_STS", BIT(21), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_d3_status_1_map[] = { + {"OSSE_D3_STS", BIT(14), 0}, + {"GBE_D3_STS", BIT(19), 0}, + {"ITSS_D3_STS", BIT(23), 0}, + {"CNVI_D3_STS", BIT(27), 0}, + {"UFSX2_D3_STS", BIT(28), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_d3_status_2_map[] = { + {"CSMERTC_D3_STS", BIT(1), 0}, + {"CSE_D3_STS", BIT(4), 0}, + {"KVMCC_D3_STS", BIT(5), 0}, + {"USBR0_D3_STS", BIT(6), 0}, + {"ISH_D3_STS", BIT(7), 0}, + {"SMT1_D3_STS", BIT(8), 0}, + {"SMT2_D3_STS", BIT(9), 0}, + {"SMT3_D3_STS", BIT(10), 0}, + {"OSSE_SMT1_D3_STS", BIT(12), 0}, + {"CLINK_D3_STS", BIT(14), 0}, + {"PTIO_D3_STS", BIT(16), 0}, + {"PMT_D3_STS", BIT(17), 0}, + {"SMS1_D3_STS", BIT(18), 0}, + {"SMS2_D3_STS", BIT(19), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_d3_status_3_map[] = { + {"OSSE_SMT2_D3_STS", BIT(0), 0}, + {"THC0_D3_STS", BIT(14), 1}, + {"THC1_D3_STS", BIT(15), 1}, + {"OSSE_SMT3_D3_STS", BIT(19), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_vnn_req_status_0_map[] = { + {"LPSS_VNN_REQ_STS", BIT(3), 0}, + {"ESPISPI_VNN_REQ_STS", BIT(18), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_vnn_req_status_1_map[] = { + {"NPK_VNN_REQ_STS", BIT(4), 1}, + {"DFXAGG_VNN_REQ_STS", BIT(8), 0}, + {"EXI_VNN_REQ_STS", BIT(9), 1}, + {"OSSE_VNN_REQ_STS", BIT(14), 1}, + {"P2D_VNN_REQ_STS", BIT(18), 1}, + {"GBE_VNN_REQ_STS", BIT(19), 0}, + {"SMB_VNN_REQ_STS", BIT(25), 1}, + {"LPC_VNN_REQ_STS", BIT(26), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_vnn_req_status_2_map[] = { + {"CSMERTC_VNN_REQ_STS", BIT(1), 0}, + {"CSE_VNN_REQ_STS", BIT(4), 1}, + {"ISH_VNN_REQ_STS", BIT(7), 0}, + {"SMT1_VNN_REQ_STS", BIT(8), 0}, + {"OSSE_SMT1_VNN_REQ_STS", BIT(12), 1}, + {"CLINK_VNN_REQ_STS", BIT(14), 0}, + {"SMS1_VNN_REQ_STS", BIT(18), 0}, + {"SMS2_VNN_REQ_STS", BIT(19), 0}, + {"GPIOCOM4_VNN_REQ_STS", BIT(20), 0}, + {"GPIOCOM3_VNN_REQ_STS", BIT(21), 1}, + {"GPIOCOM1_VNN_REQ_STS", BIT(23), 1}, + {"GPIOCOM0_VNN_REQ_STS", BIT(24), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_vnn_req_status_3_map[] = { + {"DISP_SHIM_VNN_REQ_STS", BIT(4), 1}, + {"DTS0_VNN_REQ_STS", BIT(7), 0}, + {"GPIOCOM5_VNN_REQ_STS", BIT(11), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_vnn_misc_status_map[] = { + {"CPU_C10_REQ_STS", BIT(0), 0}, + {"TS_OFF_REQ_STS", BIT(1), 0}, + {"PNDE_MET_REQ_STS", BIT(2), 1}, + {"PG5_PMA0_REQ_STS", BIT(3), 1}, + {"FW_THROTTLE_ALLOWED_REQ_STS", BIT(4), 0}, + {"VNN_SOC_REQ_STS", BIT(6), 1}, + {"ISH_VNNAON_REQ_STS", BIT(7), 0}, + {"D2D_NOC_CFI_QACTIVE_REQ_STS", BIT(8), 1}, + {"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9), 1}, + {"PLT_GREATER_REQ_STS", BIT(11), 1}, + {"ALL_SBR_IDLE_REQ_STS", BIT(12), 0}, + {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13), 0}, + {"PM_SYNC_STATES_REQ_STS", BIT(14), 0}, + {"EA_REQ_STS", BIT(15), 0}, + {"MPHY_CORE_OFF_REQ_STS", BIT(16), 0}, + {"BRK_EV_EN_REQ_STS", BIT(17), 0}, + {"AUTO_DEMO_EN_REQ_STS", BIT(18), 0}, + {"ITSS_CLK_SRC_REQ_STS", BIT(19), 1}, + {"ARC_IDLE_REQ_STS", BIT(21), 0}, + {"PG5_PMA1_REQ_STS", BIT(22), 1}, + {"DG5_PMA0_REQ_STS", BIT(23), 1}, + {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25), 0}, + {"D2D_DISP_DDI_QACTIVE_REQ_STS", BIT(26), 1}, + {"PRE_WAKE0_REQ_STS", BIT(27), 1}, + {"PRE_WAKE1_REQ_STS", BIT(28), 1}, + {"PRE_WAKE2_REQ_STS", BIT(29), 1}, + {"D2D_DISP_EDP_QACTIVE_REQ_STS", BIT(31), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_rsc_status_map[] = { + {"CORE", 0, 1}, + {"Memory", 0, 1}, + {"PRIM_D2D", 0, 1}, + {"PSF0", 0, 1}, + {"SB", 0, 1}, + {} +}; + +static const struct pmc_bit_map nvl_pcds_signal_status_map[] = { + {"LSX_Wake0_STS", BIT(0), 0}, + {"LSX_Wake1_STS", BIT(1), 0}, + {"LSX_Wake2_STS", BIT(2), 0}, + {"LSX_Wake3_STS", BIT(3), 0}, + {"LSX_Wake4_STS", BIT(4), 0}, + {"LSX_Wake5_STS", BIT(5), 0}, + {"LSX_Wake6_STS", BIT(6), 0}, + {"LSX_Wake7_STS", BIT(7), 0}, + {"LPSS_Wake0_STS", BIT(8), 1}, + {"LPSS_Wake1_STS", BIT(9), 1}, + {"Int_Timer_SS_Wake0_STS", BIT(10), 1}, + {"Int_Timer_SS_Wake1_STS", BIT(11), 1}, + {"Int_Timer_SS_Wake2_STS", BIT(12), 1}, + {"Int_Timer_SS_Wake3_STS", BIT(13), 1}, + {"Int_Timer_SS_Wake4_STS", BIT(14), 1}, + {"Int_Timer_SS_Wake5_STS", BIT(15), 1}, + {} +}; + +static const struct pmc_bit_map *nvl_pcds_lpm_maps[] = { + nvl_pcds_clocksource_status_map, + nvl_pcds_power_gating_status_0_map, + nvl_pcds_power_gating_status_1_map, + nvl_pcds_power_gating_status_2_map, + nvl_pcds_d3_status_0_map, + nvl_pcds_d3_status_1_map, + nvl_pcds_d3_status_2_map, + nvl_pcds_d3_status_3_map, + nvl_pcds_vnn_req_status_0_map, + nvl_pcds_vnn_req_status_1_map, + nvl_pcds_vnn_req_status_2_map, + nvl_pcds_vnn_req_status_3_map, + nvl_pcds_vnn_misc_status_map, + nvl_pcds_signal_status_map, + NULL +}; + +static const struct pmc_bit_map *nvl_pcds_blk_maps[] = { + nvl_pcds_power_gating_status_0_map, + nvl_pcds_power_gating_status_1_map, + nvl_pcds_power_gating_status_2_map, + nvl_pcds_rsc_status_map, + nvl_pcds_vnn_req_status_0_map, + nvl_pcds_vnn_req_status_1_map, + nvl_pcds_vnn_req_status_2_map, + nvl_pcds_vnn_req_status_3_map, + nvl_pcds_d3_status_0_map, + nvl_pcds_d3_status_1_map, + nvl_pcds_d3_status_2_map, + nvl_pcds_d3_status_3_map, + nvl_pcds_clocksource_status_map, + nvl_pcds_vnn_misc_status_map, + nvl_pcds_signal_status_map, + NULL +}; + +static const struct pmc_bit_map nvl_pchs_pfear_map[] = { + {"PMC_PGD0", BIT(0)}, + {"FIA_D_PGD0", BIT(1)}, + {"SPI_PGD0", BIT(2)}, + {"XHCI_PGD0", BIT(3)}, + {"SPA_PGD0", BIT(4)}, + {"SPB_PGD0", BIT(5)}, + {"MPFPW2_PGD0", BIT(6)}, + {"GBE_PGD0", BIT(7)}, + + {"RSVD8", BIT(0)}, + {"PSF3_PGD0", BIT(1)}, + {"SBR5_PGD0", BIT(2)}, + {"SBR0_PGD0", BIT(3)}, + {"RSVD12", BIT(4)}, + {"D2D_DISP_PGD1", BIT(5)}, + {"LPSS_PGD0", BIT(6)}, + {"LPC_PGD0", BIT(7)}, + + {"SMB_PGD0", BIT(0)}, + {"ISH_PGD0", BIT(1)}, + {"P2SB_PGD0", BIT(2)}, + {"NPK_PGD0", BIT(3)}, + {"D2D_NOC_PGD1", BIT(4)}, + {"EAH_PGD0", BIT(5)}, + {"FUSE_PGD0", BIT(6)}, + {"SBR8_PGD0", BIT(7)}, + + {"PSF7_PGD0", BIT(0)}, + {"OTG_PGD0", BIT(1)}, + {"EXI_PGD0", BIT(2)}, + {"CSE_PGD0", BIT(3)}, + {"CSME_KVM_PGD0", BIT(4)}, + {"CSME_PMT_PGD0", BIT(5)}, + {"CSME_CLINK_PGD0", BIT(6)}, + {"CSME_PTIO_PGD0", BIT(7)}, + + {"CSME_USBR_PGD0", BIT(0)}, + {"SBR1_PGD0", BIT(1)}, + {"CSME_SMT1_PGD0", BIT(2)}, + {"MPFPW1_PGD0", BIT(3)}, + {"CSME_SMS2_PGD0", BIT(4)}, + {"CSME_SMS_PGD0", BIT(5)}, + {"CSME_RTC_PGD0", BIT(6)}, + {"CSMEPSF_PGD0", BIT(7)}, + + {"D2D_NOC_PGD0", BIT(0)}, + {"ESE_PGD0", BIT(1)}, + {"SBR2_PGD0", BIT(2)}, + {"SBR3_PGD0", BIT(3)}, + {"SBR4_PGD0", BIT(4)}, + {"RSVD45", BIT(5)}, + {"D2D_DISP_PGD0", BIT(6)}, + {"PSF1_PGD0", BIT(7)}, + + {"U3FPW1_PGD0", BIT(0)}, + {"DMI3FPW_PGD0", BIT(1)}, + {"PSF4_PGD0", BIT(2)}, + {"CNVI_PGD0", BIT(3)}, + {"RSVD52", BIT(4)}, + {"ENDBG_PGD0", BIT(5)}, + {"DBC_PGD0", BIT(6)}, + {"SMT4_PGD0", BIT(7)}, + + {"RSVD56", BIT(0)}, + {"NPK_PGD1", BIT(1)}, + {"RSVD58", BIT(2)}, + {"DMI3_PGD0", BIT(3)}, + {"RSVD60", BIT(4)}, + {"FIACPCB_D_PGD0", BIT(5)}, + {"RSVD62", BIT(6)}, + {"FIA_U_PGD0", BIT(7)}, + + {"FIACPCB_PGS_PGD0", BIT(0)}, + {"FIA_PGS_PGD0", BIT(1)}, + {"RSVD66", BIT(2)}, + {"FIACPCB_U_PGD0", BIT(3)}, + {"TAM_PGD0", BIT(4)}, + {"D2D_NOC_PGD2", BIT(5)}, + {"PSF2_PGD0", BIT(6)}, + {"THC0_PGD0", BIT(7)}, + + {"THC1_PGD0", BIT(0)}, + {"PMC_PGD1", BIT(1)}, + {"SBR9_PGD0", BIT(2)}, + {"U3FPW2_PGD0", BIT(3)}, + {"RSVD76", BIT(4)}, + {"DBG_PSF_PGD0", BIT(5)}, + {"DBG_SBR_PGD0", BIT(6)}, + {"SBR6_PGD0", BIT(7)}, + + {"SPC_PGD0", BIT(0)}, + {"ACE_PGD0", BIT(1)}, + {"ACE_PGD1", BIT(2)}, + {"ACE_PGD2", BIT(3)}, + {"ACE_PGD3", BIT(4)}, + {"ACE_PGD4", BIT(5)}, + {"ACE_PGD5", BIT(6)}, + {"ACE_PGD6", BIT(7)}, + + {"ACE_PGD7", BIT(0)}, + {"ACE_PGD8", BIT(1)}, + {"ACE_PGD9", BIT(2)}, + {"ACE_PGD10", BIT(3)}, + {"U3FPW3_PGD0", BIT(4)}, + {"SBR7_PGD0", BIT(5)}, + {"OSSE_PGD0", BIT(6)}, + {"ST_PGD0", BIT(7)}, + {} +}; + +static const struct pmc_bit_map *ext_nvl_pchs_pfear_map[] = { + nvl_pchs_pfear_map, + NULL +}; + +static const struct pmc_bit_map nvl_pchs_clocksource_status_map[] = { + {"AON2_OFF_STS", BIT(0), 1}, + {"AON3_OFF_STS", BIT(1), 0}, + {"AON4_OFF_STS", BIT(2), 0}, + {"AON2_SPL_OFF_STS", BIT(3), 0}, + {"AONL_OFF_STS", BIT(4), 0}, + {"XTAL_LVM_OFF_STS", BIT(5), 0}, + {"AON5_OFF_STS", BIT(6), 0}, + {"USB3_PLL_OFF_STS", BIT(8), 1}, + {"MAIN_CRO_OFF_STS", BIT(11), 0}, + {"MAIN_DIVIDER_OFF_STS", BIT(12), 1}, + {"REF_PLL_NON_OC_OFF_STS", BIT(13), 1}, + {"DMI_PLL_OFF_STS", BIT(14), 1}, + {"PHY_EXT_INJ_OFF_STS", BIT(15), 1}, + {"AON6_MCRO_OFF_STS", BIT(16), 0}, + {"XTAL_AGGR_OFF_STS", BIT(17), 0}, + {"USB2_PLL_OFF_STS", BIT(18), 1}, + {"GBE_PLL_OFF_STS", BIT(21), 1}, + {"SATA_PLL_OFF_STS", BIT(22), 1}, + {"PCIE0_PLL_OFF_STS", BIT(23), 1}, + {"PCIE1_PLL_OFF_STS", BIT(24), 1}, + {"FABRIC_PLL_OFF_STS", BIT(25), 1}, + {"PCIE2_PLL_OFF_STS", BIT(26), 1}, + {"REF_PLL_OFF_STS", BIT(28), 1}, + {"REF38P4_PLL_OFF_STS", BIT(31), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_power_gating_status_0_map[] = { + {"PMC_PGD0_PG_STS", BIT(0), 0}, + {"FIA_D_PGD0_PG_STS", BIT(1), 0}, + {"ESPISPI_PGD0_PG_STS", BIT(2), 0}, + {"XHCI_PGD0_PG_STS", BIT(3), 0}, + {"SPA_PGD0_PG_STS", BIT(4), 1}, + {"SPB_PGD0_PG_STS", BIT(5), 1}, + {"MPFPW2_PGD0_PG_STS", BIT(6), 0}, + {"GBE_PGD0_PG_STS", BIT(7), 1}, + {"RSVD_8", BIT(8), 0}, + {"PSF3_PGD0_PG_STS", BIT(9), 0}, + {"SBR5_PGD0_PG_STS", BIT(10), 0}, + {"SBR0_PGD0_PG_STS", BIT(11), 0}, + {"RSVD_12", BIT(12), 0}, + {"D2D_DISP_PGD1_PG_STS", BIT(13), 0}, + {"LPSS_PGD0_PG_STS", BIT(14), 1}, + {"LPC_PGD0_PG_STS", BIT(15), 0}, + {"SMB_PGD0_PG_STS", BIT(16), 0}, + {"ISH_PGD0_PG_STS", BIT(17), 0}, + {"P2S_PGD0_PG_STS", BIT(18), 0}, + {"NPK_PGD0_PG_STS", BIT(19), 0}, + {"D2D_NOC_PGD1_PG_STS", BIT(20), 0}, + {"EAH_PGD0_PG_STS", BIT(21), 0}, + {"FUSE_PGD0_PG_STS", BIT(22), 0}, + {"SBR8_PGD0_PG_STS", BIT(23), 0}, + {"PSF7_PGD0_PG_STS", BIT(24), 0}, + {"XDCI_PGD0_PG_STS", BIT(25), 1}, + {"EXI_PGD0_PG_STS", BIT(26), 0}, + {"CSE_PGD0_PG_STS", BIT(27), 1}, + {"KVMCC_PGD0_PG_STS", BIT(28), 1}, + {"PMT_PGD0_PG_STS", BIT(29), 1}, + {"CLINK_PGD0_PG_STS", BIT(30), 1}, + {"PTIO_PGD0_PG_STS", BIT(31), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_power_gating_status_1_map[] = { + {"USBR0_PGD0_PG_STS", BIT(0), 1}, + {"SBR1_PGD0_PG_STS", BIT(1), 0}, + {"SMT1_PGD0_PG_STS", BIT(2), 1}, + {"MPFPW1_PGD0_PG_STS", BIT(3), 0}, + {"SMS2_PGD0_PG_STS", BIT(4), 1}, + {"SMS1_PGD0_PG_STS", BIT(5), 1}, + {"CSMERTC_PGD0_PG_STS", BIT(6), 0}, + {"CSMEPSF_PGD0_PG_STS", BIT(7), 0}, + {"D2D_NOC_PGD0_PG_STS", BIT(8), 0}, + {"ESE_PGD0_PG_STS", BIT(9), 1}, + {"SBR2_PGD0_PG_STS", BIT(10), 0}, + {"SBR3_PGD0_PG_STS", BIT(11), 0}, + {"SBR4_PGD0_PG_STS", BIT(12), 0}, + {"RSVD_13", BIT(13), 0}, + {"D2D_DISP_PGD0_PG_STS", BIT(14), 0}, + {"PSF1_PGD0_PG_STS", BIT(15), 0}, + {"U3FPW1_PGD0_PG_STS", BIT(16), 0}, + {"DMI3FPW_PGD0_PG_STS", BIT(17), 0}, + {"PSF4_PGD0_PG_STS", BIT(18), 0}, + {"CNVI_PGD0_PG_STS", BIT(19), 0}, + {"RSVD_20", BIT(20), 0}, + {"ENDBG_PGD0_PG_STS", BIT(21), 0}, + {"DBC_PGD0_PG_STS", BIT(22), 0}, + {"SMT4_PGD0_PG_STS", BIT(23), 1}, + {"RSVD_24", BIT(24), 0}, + {"NPK_PGD1_PG_STS", BIT(25), 0}, + {"RSVD_26", BIT(26), 0}, + {"DMI3_PGD0_PG_STS", BIT(27), 1}, + {"RSVD_28", BIT(28), 0}, + {"FIACPCB_D_PGD0_PG_STS", BIT(29), 0}, + {"RSVD_30", BIT(30), 0}, + {"FIA_U_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_power_gating_status_2_map[] = { + {"FIACPCB_PGS_PGD0_PG_STS", BIT(0), 0}, + {"FIA_PGS_PGD0_PG_STS", BIT(1), 0}, + {"RSVD_2", BIT(2), 0}, + {"FIACPCB_U_PGD0_PG_STS", BIT(3), 0}, + {"TAM_PGD0_PG_STS", BIT(4), 0}, + {"D2D_NOC_PGD2_PG_STS", BIT(5), 0}, + {"PSF2_PGD0_PG_STS", BIT(6), 0}, + {"THC0_PGD0_PG_STS", BIT(7), 1}, + {"THC1_PGD0_PG_STS", BIT(8), 1}, + {"PMC_PGD1_PG_STS", BIT(9), 0}, + {"SBR9_PGA0_PGD0_PG_STS", BIT(10), 0}, + {"U3FPW2_PGD0_PG_STS", BIT(11), 0}, + {"RSVD_12", BIT(12), 0}, + {"DBG_PSF_PGD0_PG_STS", BIT(13), 0}, + {"DBG_SBR_PGD0_PG_STS", BIT(14), 0}, + {"SBR6_PGD0_PG_STS", BIT(15), 0}, + {"SPC_PGD0_PG_STS", BIT(16), 1}, + {"ACE_PGD0_PG_STS", BIT(17), 0}, + {"ACE_PGD1_PG_STS", BIT(18), 0}, + {"ACE_PGD2_PG_STS", BIT(19), 0}, + {"ACE_PGD3_PG_STS", BIT(20), 0}, + {"ACE_PGD4_PG_STS", BIT(21), 0}, + {"ACE_PGD5_PG_STS", BIT(22), 0}, + {"ACE_PGD6_PG_STS", BIT(23), 0}, + {"ACE_PGD7_PG_STS", BIT(24), 0}, + {"ACE_PGD8_PG_STS", BIT(25), 0}, + {"ACE_PGD9_PG_STS", BIT(26), 0}, + {"ACE_PGD10_PG_STS", BIT(27), 0}, + {"U3FPW3_PGD0_PG_STS", BIT(28), 0}, + {"SBR7_PGD0_PG_STS", BIT(29), 0}, + {"OSSE_PGD0_PG_STS", BIT(30), 0}, + {"SATA_PGD0_PG_STS", BIT(31), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_d3_status_0_map[] = { + {"LPSS_D3_STS", BIT(3), 1}, + {"XDCI_D3_STS", BIT(4), 1}, + {"XHCI_D3_STS", BIT(5), 0}, + {"SPA_D3_STS", BIT(12), 0}, + {"SPB_D3_STS", BIT(13), 0}, + {"SPC_D3_STS", BIT(14), 0}, + {"ESPISPI_D3_STS", BIT(18), 0}, + {"SATA_D3_STS", BIT(20), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_d3_status_1_map[] = { + {"OSSE_D3_STS", BIT(6), 0}, + {"GBE_D3_STS", BIT(19), 0}, + {"ITSS_D3_STS", BIT(23), 0}, + {"P2S_D3_STS", BIT(24), 0}, + {"CNVI_D3_STS", BIT(27), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_d3_status_2_map[] = { + {"CSMERTC_D3_STS", BIT(1), 0}, + {"CSE_D3_STS", BIT(4), 0}, + {"KVMCC_D3_STS", BIT(5), 0}, + {"USBR0_D3_STS", BIT(6), 0}, + {"ISH_D3_STS", BIT(7), 0}, + {"SMT1_D3_STS", BIT(8), 0}, + {"SMT2_D3_STS", BIT(9), 0}, + {"SMT3_D3_STS", BIT(10), 0}, + {"SMT4_D3_STS", BIT(11), 0}, + {"SMT5_D3_STS", BIT(12), 0}, + {"SMT6_D3_STS", BIT(13), 0}, + {"CLINK_D3_STS", BIT(14), 0}, + {"PTIO_D3_STS", BIT(16), 0}, + {"PMT_D3_STS", BIT(17), 0}, + {"SMS1_D3_STS", BIT(18), 0}, + {"SMS2_D3_STS", BIT(19), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_d3_status_3_map[] = { + {"THC0_D3_STS", BIT(14), 0}, + {"THC1_D3_STS", BIT(15), 0}, + {"ACE_D3_STS", BIT(23), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_vnn_req_status_1_map[] = { + {"NPK_VNN_REQ_STS", BIT(4), 0}, + {"OSSE_VNN_REQ_STS", BIT(6), 0}, + {"DFXAGG_VNN_REQ_STS", BIT(8), 0}, + {"EXI_VNN_REQ_STS", BIT(9), 0}, + {"GBE_VNN_REQ_STS", BIT(19), 0}, + {"SMB_VNN_REQ_STS", BIT(25), 0}, + {"LPC_VNN_REQ_STS", BIT(26), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_vnn_req_status_2_map[] = { + {"CSMERTC_VNN_REQ_STS", BIT(1), 0}, + {"CSE_VNN_REQ_STS", BIT(4), 0}, + {"ISH_VNN_REQ_STS", BIT(7), 0}, + {"SMT1_VNN_REQ_STS", BIT(8), 0}, + {"SMT4_VNN_REQ_STS", BIT(11), 0}, + {"CLINK_VNN_REQ_STS", BIT(14), 0}, + {"SMS1_VNN_REQ_STS", BIT(18), 0}, + {"SMS2_VNN_REQ_STS", BIT(19), 0}, + {"GPIOCOM4_VNN_REQ_STS", BIT(20), 0}, + {"GPIOCOM3_VNN_REQ_STS", BIT(21), 0}, + {"GPIOCOM2_VNN_REQ_STS", BIT(22), 0}, + {"GPIOCOM1_VNN_REQ_STS", BIT(23), 0}, + {"GPIOCOM0_VNN_REQ_STS", BIT(24), 0}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_vnn_misc_status_map[] = { + {"CPU_C10_REQ_STS", BIT(0), 0}, + {"TS_OFF_REQ_STS", BIT(1), 0}, + {"PNDE_MET_REQ_STS", BIT(2), 1}, + {"PG5_PMA0_GVNN_REQ_STS", BIT(3), 1}, + {"FW_THROTTLE_ALLOWED_REQ_STS", BIT(4), 0}, + {"DMI_IN_L1_REQ_STS", BIT(6), 0}, + {"ISH_VNNAON_REQ_STS", BIT(7), 0}, + {"PLT_GREATER_REQ_STS", BIT(11), 1}, + {"ALL_SBR_IDLE_REQ_STS", BIT(12), 0}, + {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13), 0}, + {"PM_SYNC_STATES_REQ_STS", BIT(14), 0}, + {"EA_REQ_STS", BIT(15), 0}, + {"DMI_CLKREQ_B_REQ_STS", BIT(16), 0}, + {"BRK_EV_EN_REQ_STS", BIT(17), 0}, + {"AUTO_DEMO_EN_REQ_STS", BIT(18), 0}, + {"ITSS_CLK_SRC_REQ_STS", BIT(19), 1}, + {"ARC_IDLE_REQ_STS", BIT(21), 0}, + {"PG5_PMA1_GVNN_REQ_STS", BIT(22), 1}, + {"FIA_DEEP_PM_REQ_STS", BIT(23), 0}, + {"XDCI_ATTACHED_REQ_STS", BIT(24), 0}, + {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25), 0}, + {"PRE_WAKE0_REQ_STS", BIT(27), 1}, + {"PRE_WAKE1_REQ_STS", BIT(28), 1}, + {"PRE_WAKE2_EN_REQ_STS", BIT(29), 0}, + {"PG5_PMA2_GVNN_REQ_STS", BIT(30), 1}, + {} +}; + +static const struct pmc_bit_map nvl_pchs_rsc_status_map[] = { + {"Memory", 0, 1}, + {"Memory_NS", 0, 1}, + {"PSF1", 0, 1}, + {"PSF2", 0, 1}, + {"PSF3", 0, 1}, + {"REF_PLL", 0, 1}, + {"SB", 0, 1}, + {} +}; + +static const struct pmc_bit_map *nvl_pchs_lpm_maps[] = { + nvl_pchs_clocksource_status_map, + nvl_pchs_power_gating_status_0_map, + nvl_pchs_power_gating_status_1_map, + nvl_pchs_power_gating_status_2_map, + nvl_pchs_d3_status_0_map, + nvl_pchs_d3_status_1_map, + nvl_pchs_d3_status_2_map, + nvl_pchs_d3_status_3_map, + nvl_pcds_vnn_req_status_0_map, + nvl_pchs_vnn_req_status_1_map, + nvl_pchs_vnn_req_status_2_map, + nvl_pcdh_vnn_req_status_3_map, + nvl_pchs_vnn_misc_status_map, + ptl_pcdp_signal_status_map, + NULL +}; + +static const struct pmc_bit_map *nvl_pchs_blk_maps[] = { + nvl_pchs_power_gating_status_0_map, + nvl_pchs_power_gating_status_1_map, + nvl_pchs_power_gating_status_2_map, + nvl_pchs_rsc_status_map, + nvl_pchs_d3_status_0_map, + nvl_pchs_clocksource_status_map, + nvl_pchs_vnn_misc_status_map, + NULL +}; + +static const struct pmc_reg_map nvl_pcdh_reg_map = { + .pfear_sts = ext_nvl_pcdh_pfear_map, + .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, + .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP, + .ltr_show_sts = ptl_pcdp_ltr_show_map, + .msr_sts = msr_map, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = NVL_PCDH_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = NVL_PCDH_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, + .lpm_num_maps = NVL_LPM_NUM_MAPS, + .ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED, + .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, + .etr3_offset = ETR3_OFFSET, + .lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET, + .lpm_priority_offset = NVL_LPM_PRI_OFFSET, + .lpm_en_offset = NVL_LPM_EN_OFFSET, + .lpm_residency_offset = NVL_LPM_RESIDENCY_OFFSET, + .lpm_sts = nvl_pcdh_lpm_maps, + .lpm_status_offset = MTL_LPM_STATUS_OFFSET, + .lpm_live_status_offset = NVL_LPM_LIVE_STATUS_OFFSET, + .s0ix_blocker_maps = nvl_pcdh_blk_maps, + .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET, + .num_s0ix_blocker = NVL_PCDH_NUM_S0IX_BLOCKER, + .blocker_req_offset = NVL_PCDH_BLK_REQ_OFFSET, + .lpm_req_guid = PCDH_LPM_REQ_GUID, +}; + +static const struct pmc_reg_map nvl_pcds_reg_map = { + .pfear_sts = ext_nvl_pcds_pfear_map, + .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, + .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP, + .ltr_show_sts = nvl_pcds_ltr_show_map, + .msr_sts = msr_map, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = NVL_PCDS_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = LNL_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, + .lpm_num_maps = PTL_LPM_NUM_MAPS, + .ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED, + .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, + .etr3_offset = ETR3_OFFSET, + .lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET, + .lpm_priority_offset = MTL_LPM_PRI_OFFSET, + .lpm_en_offset = MTL_LPM_EN_OFFSET, + .lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET, + .lpm_sts = nvl_pcds_lpm_maps, + .lpm_status_offset = MTL_LPM_STATUS_OFFSET, + .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET, + .s0ix_blocker_maps = nvl_pcds_blk_maps, + .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET, + .num_s0ix_blocker = NVL_PCDS_NUM_S0IX_BLOCKER, + .lpm_req_guid = PCDS_LPM_REQ_GUID, + .blocker_req_offset = NVL_PCDS_BLK_REQ_OFFSET, +}; + +static const struct pmc_reg_map nvl_pchs_reg_map = { + .pfear_sts = ext_nvl_pchs_pfear_map, + .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, + .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP, + .ltr_show_sts = ptl_pcdp_ltr_show_map, + .msr_sts = msr_map, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = NVL_PCHS_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = LNL_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, + .lpm_num_maps = PTL_LPM_NUM_MAPS, + .ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED, + .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, + .etr3_offset = ETR3_OFFSET, + .lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET, + .lpm_priority_offset = MTL_LPM_PRI_OFFSET, + .lpm_en_offset = MTL_LPM_EN_OFFSET, + .lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET, + .lpm_sts = nvl_pchs_lpm_maps, + .lpm_status_offset = MTL_LPM_STATUS_OFFSET, + .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET, + .s0ix_blocker_maps = nvl_pchs_blk_maps, + .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET, + .num_s0ix_blocker = NVL_PCHS_NUM_S0IX_BLOCKER, + .blocker_req_offset = NVL_PCHS_BLK_REQ_OFFSET, + .lpm_req_guid = PCHS_LPM_REQ_GUID, +}; + +static struct pmc_info nvl_pmc_info_list[] = { + { + .devid = PMC_DEVID_NVL_PCDH, + .map = &nvl_pcdh_reg_map, + }, + { + .devid = PMC_DEVID_NVL_PCDS, + .map = &nvl_pcds_reg_map, + }, + { + .devid = PMC_DEVID_NVL_PCHS, + .map = &nvl_pchs_reg_map, + }, + {} +}; + +static const char *nvl_ltr_block_counter_arr[] = { + "PKGC_PREVENT_LTR_IADOMAIN", + "PKGC_PREVENT_LTR_GDIE", + "PKGC_PREVENT_LTR_PCH", + "PKGC_PREVENT_LTR_DISPLAY", + "PKGC_PREVENT_LTR_IPU", + NULL +}; + +static const char *nvl_pkgc_blocker_residency[] = { + "PKGC_BLOCK_RESIDENCY_INVALID", + "PKGC_BLOCK_RESIDENCY_MISC", + "PKGC_BLOCK_RESIDENCY_CDIE_MISC", + "PKGC_BLOCK_RESIDENCY_MEDIA_MISC", + "PKGC_BLOCK_RESIDENCY_GT_MISC", + "PKGC_BLOCK_RESIDENCY_HUBATOM_MISC", + "PKGC_BLOCK_RESIDENCY_IPU_BUSY", + "PKGC_BLOCK_RESIDENCY_IPU_LTR", + "PKGC_BLOCK_RESIDENCY_IPU_TIMER", + "PKGC_BLOCK_RESIDENCY_DISP_BUSY", + "PKGC_BLOCK_RESIDENCY_DISP_LTR", + "PKGC_BLOCK_RESIDENCY_DISP_TIMER", + "PKGC_BLOCK_RESIDENCY_VPU_BUSY", + "PKGC_BLOCK_RESIDENCY_VPU_TIMER", + "PKGC_BLOCK_RESIDENCY_PMC_BUSY", + "PKGC_BLOCK_RESIDENCY_PMC_LTR", + "PKGC_BLOCK_RESIDENCY_PMC_TIMER", + "PKGC_BLOCK_RESIDENCY_HUBATOM_ARAT", + "PKGC_BLOCK_RESIDENCY_CDIE0_ARAT", + "PKGC_BLOCK_RESIDENCY_CDIE1_ARAT", + "PKGC_BLOCK_RESIDENCY_GT_ARAT", + "PKGC_BLOCK_RESIDENCY_MEDIA_ARAT", + "PKGC_BLOCK_RESIDENCY_DEMOTION", + "PKGC_BLOCK_RESIDENCY_THERMALS", + "PKGC_BLOCK_RESIDENCY_SNCU", + "PKGC_BLOCK_RESIDENCY_SVTU", + "PKGC_BLOCK_RESIDENCY_IAA", + "PKGC_BLOCK_RESIDENCY_IOC", + NULL, +}; + +static const u8 nvl_pmc_list[] = {PMC_IDX_MAIN, PMC_IDX_PCH}; + +#define NVL_NPU_PCI_DEV 0xd71d + +/* + * Set power state of select devices that do not have drivers to D3 + * so that they do not block Package C entry. + */ +static void nvl_d3_fixup(void) +{ + pmc_core_set_device_d3(NVL_NPU_PCI_DEV); +} + +static int nvl_resume(struct pmc_dev *pmcdev) +{ + nvl_d3_fixup(); + return cnl_resume(pmcdev); +} + +static int nvl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) +{ + nvl_d3_fixup(); + return generic_core_init(pmcdev, pmc_dev_info); +} + +static u32 nvl_pmt_dmu_guids[] = {NVL_PMT_DMU_GUID, 0x0}; +struct pmc_dev_info nvl_s_pmc_dev = { + .num_pmcs = ARRAY_SIZE(nvl_pmc_list), + .pmc_list = nvl_pmc_list, + .regmap_list = nvl_pmc_info_list, + .map = &nvl_pcds_reg_map, + .sub_req_show = &pmc_core_substate_blk_req_fops, + .suspend = cnl_suspend, + .resume = nvl_resume, + .init = nvl_core_init, + .sub_req = pmc_core_pmt_get_blk_sub_req, + .dmu_guids = nvl_pmt_dmu_guids, + .pc_guid = NVL_PMT_PC_GUID, + .pkgc_ltr_blocker_offset = NVL_LTR_BLK_OFFSET, + .pkgc_ltr_blocker_counters = nvl_ltr_block_counter_arr, + .pkgc_blocker_offset = NVL_PKGC_BLK_OFFSET, + .pkgc_blocker_counters = nvl_pkgc_blocker_residency, + .ssram_hidden = false, + .die_c6_offset = NVL_PMT_DMU_DIE_C6_OFFSET, +}; + +struct pmc_dev_info nvl_h_pmc_dev = { + .num_pmcs = ARRAY_SIZE(nvl_pmc_list), + .pmc_list = nvl_pmc_list, + .regmap_list = nvl_pmc_info_list, + .map = &nvl_pcdh_reg_map, + .sub_req_show = &pmc_core_substate_blk_req_fops, + .suspend = cnl_suspend, + .resume = nvl_resume, + .init = nvl_core_init, + .sub_req = pmc_core_pmt_get_blk_sub_req, + .dmu_guids = nvl_pmt_dmu_guids, + .pc_guid = NVL_PMT_PC_GUID, + .pkgc_ltr_blocker_offset = NVL_LTR_BLK_OFFSET, + .pkgc_ltr_blocker_counters = nvl_ltr_block_counter_arr, + .pkgc_blocker_offset = NVL_PKGC_BLK_OFFSET, + .pkgc_blocker_counters = nvl_pkgc_blocker_residency, + .ssram_hidden = false, + .die_c6_offset = NVL_PMT_DMU_DIE_C6_OFFSET, +}; diff --git a/drivers/platform/x86/intel/pmc/ptl.c b/drivers/platform/x86/intel/pmc/ptl.c index 1f48e2bbc699..a7d5d4f77934 100644 --- a/drivers/platform/x86/intel/pmc/ptl.c +++ b/drivers/platform/x86/intel/pmc/ptl.c @@ -137,7 +137,7 @@ static const struct pmc_bit_map *ext_ptl_pcdp_pfear_map[] = { NULL }; -static const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = { +const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = { {"SOUTHPORT_A", CNP_PMC_LTR_SPA}, {"SOUTHPORT_B", CNP_PMC_LTR_SPB}, {"SATA", CNP_PMC_LTR_SATA}, @@ -543,6 +543,8 @@ static struct pmc_info ptl_pmc_info_list[] = { {} }; +static const u8 ptl_pmc_list[] = {PMC_IDX_MAIN}; + #define PTL_NPU_PCI_DEV 0xb03e #define PTL_IPU_PCI_DEV 0xb05d @@ -569,7 +571,8 @@ static int ptl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in } struct pmc_dev_info ptl_pmc_dev = { - .pci_func = 2, + .num_pmcs = ARRAY_SIZE(ptl_pmc_list), + .pmc_list = ptl_pmc_list, .regmap_list = ptl_pmc_info_list, .map = &ptl_pcdp_reg_map, .sub_req_show = &pmc_core_substate_blk_req_fops, @@ -577,4 +580,5 @@ struct pmc_dev_info ptl_pmc_dev = { .resume = ptl_resume, .init = ptl_core_init, .sub_req = pmc_core_pmt_get_blk_sub_req, + .ssram_hidden = true, }; diff --git a/drivers/platform/x86/intel/pmc/pwrm_telemetry.c b/drivers/platform/x86/intel/pmc/pwrm_telemetry.c new file mode 100644 index 000000000000..013f779f20a4 --- /dev/null +++ b/drivers/platform/x86/intel/pmc/pwrm_telemetry.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel PMC PWRM ACPI driver + * + * Copyright (C) 2025, Intel Corporation + */ + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/intel_vsec.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/resource.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uuid.h> + +#include "core.h" + +#define ENTRY_LEN 5 + +/* DWORD2 */ +#define DVSEC_ID_MASK GENMASK(15, 0) +#define NUM_ENTRIES_MASK GENMASK(23, 16) +#define ENTRY_SIZE_MASK GENMASK(31, 24) + +/* DWORD3 */ +#define TBIR_MASK GENMASK(2, 0) +#define DISC_TBL_OFF_MASK GENMASK(31, 3) + +const guid_t intel_vsec_guid = + GUID_INIT(0x294903fb, 0x634d, 0x4fc7, 0xaf, 0x1f, 0x0f, 0xb9, + 0x56, 0xb0, 0x4f, 0xc1); + +static bool is_valid_entry(union acpi_object *pkg) +{ + int i; + + if (!pkg || pkg->type != ACPI_TYPE_PACKAGE || pkg->package.count != ENTRY_LEN) + return false; + + if (pkg->package.elements[0].type != ACPI_TYPE_STRING) + return false; + + for (i = 1; i < ENTRY_LEN; i++) + if (pkg->package.elements[i].type != ACPI_TYPE_INTEGER) + return false; + + return true; +} + +acpi_disc_t pmc_parse_telem_dsd(union acpi_object *obj, + struct intel_vsec_header *header) +{ + union acpi_object *vsec_pkg; + union acpi_object *disc_pkg; + u64 hdr0, hdr1; + int num_regions; + int i; + + if (!header) + return ERR_PTR(-EINVAL); + + if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2) + return ERR_PTR(-EINVAL); + + /* First Package is DVSEC info */ + vsec_pkg = &obj->package.elements[0]; + if (!is_valid_entry(vsec_pkg)) + return ERR_PTR(-EINVAL); + + hdr0 = vsec_pkg->package.elements[3].integer.value; + hdr1 = vsec_pkg->package.elements[4].integer.value; + + header->id = FIELD_GET(DVSEC_ID_MASK, hdr0); + header->num_entries = FIELD_GET(NUM_ENTRIES_MASK, hdr0); + header->entry_size = FIELD_GET(ENTRY_SIZE_MASK, hdr0); + header->tbir = FIELD_GET(TBIR_MASK, hdr1); + header->offset = hdr1 & DISC_TBL_OFF_MASK; + + /* Second Package contains the discovery tables */ + disc_pkg = &obj->package.elements[1]; + if (disc_pkg->type != ACPI_TYPE_PACKAGE || disc_pkg->package.count < 1) + return ERR_PTR(-EINVAL); + + num_regions = disc_pkg->package.count; + if (header->num_entries != num_regions) + return ERR_PTR(-EINVAL); + + acpi_disc_t disc __free(kfree) = kmalloc_objs(*disc, num_regions); + if (!disc) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < num_regions; i++) { + union acpi_object *pkg; + u64 value; + int j; + + pkg = &disc_pkg->package.elements[i]; + if (!is_valid_entry(pkg)) + return ERR_PTR(-EINVAL); + + /* Element 0 is a descriptive string; DWORD values start at index 1. */ + for (j = 1; j < ENTRY_LEN; j++) { + value = pkg->package.elements[j].integer.value; + if (value > U32_MAX) + return ERR_PTR(-ERANGE); + + disc[i][j - 1] = value; + } + } + + return no_free_ptr(disc); +} +EXPORT_SYMBOL_NS_GPL(pmc_parse_telem_dsd, "INTEL_PMC_CORE"); + +union acpi_object *pmc_find_telem_guid(union acpi_object *dsd) +{ + int i; + + if (!dsd || dsd->type != ACPI_TYPE_PACKAGE) + return NULL; + + for (i = 0; i + 1 < dsd->package.count; i += 2) { + union acpi_object *uuid_obj, *data_obj; + guid_t uuid; + + uuid_obj = &dsd->package.elements[i]; + data_obj = &dsd->package.elements[i + 1]; + + if (uuid_obj->type != ACPI_TYPE_BUFFER || + uuid_obj->buffer.length != 16) + continue; + + memcpy(&uuid, uuid_obj->buffer.pointer, 16); + if (guid_equal(&uuid, &intel_vsec_guid)) + return data_obj; + } + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(pmc_find_telem_guid, "INTEL_PMC_CORE"); + +static int pmc_pwrm_acpi_probe(struct platform_device *pdev) +{ + struct intel_vsec_header header; + struct intel_vsec_header *headers[2] = { &header, NULL }; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + struct intel_vsec_platform_info info = { }; + struct device *dev = &pdev->dev; + union acpi_object *dsd; + struct resource *res; + acpi_handle handle; + acpi_status status; + + handle = ACPI_HANDLE(&pdev->dev); + if (!handle) + return -ENODEV; + + status = acpi_evaluate_object(handle, "_DSD", NULL, &buf); + if (ACPI_FAILURE(status)) { + return dev_err_probe(dev, -ENODEV, "Could not evaluate _DSD: %s\n", + acpi_format_exception(status)); + } + + void *dsd_buf __free(pmc_acpi_free) = buf.pointer; + + dsd = pmc_find_telem_guid(dsd_buf); + if (!dsd) + return -ENODEV; + + acpi_disc_t acpi_disc __free(kfree) = pmc_parse_telem_dsd(dsd, &header); + if (IS_ERR(acpi_disc)) + return PTR_ERR(acpi_disc); + + res = platform_get_resource(pdev, IORESOURCE_MEM, header.tbir); + if (!res) + return -EINVAL; + + info.headers = headers; + info.caps = VSEC_CAP_TELEMETRY; + info.acpi_disc = acpi_disc; + info.src = INTEL_VSEC_DISC_ACPI; + info.base_addr = res->start; + + return intel_vsec_register(&pdev->dev, &info); +} + +static const struct acpi_device_id pmc_pwrm_acpi_ids[] = { + { "INTC1122", 0 }, /* Nova Lake */ + { "INTC1129", 0 }, /* Nova Lake */ + { } +}; +MODULE_DEVICE_TABLE(acpi, pmc_pwrm_acpi_ids); + +static struct platform_driver pmc_pwrm_acpi_driver = { + .probe = pmc_pwrm_acpi_probe, + .driver = { + .name = "intel_pmc_pwrm_acpi", + .acpi_match_table = ACPI_PTR(pmc_pwrm_acpi_ids), + }, +}; +module_platform_driver(pmc_pwrm_acpi_driver); + +MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>"); +MODULE_DESCRIPTION("Intel PMC PWRM ACPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("INTEL_VSEC"); diff --git a/drivers/platform/x86/intel/pmc/ssram_telemetry.c b/drivers/platform/x86/intel/pmc/ssram_telemetry.c index 03fad9331fc0..8e280ec525a6 100644 --- a/drivers/platform/x86/intel/pmc/ssram_telemetry.c +++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.c @@ -5,7 +5,11 @@ * Copyright (c) 2023, Intel Corporation. */ +#include <linux/acpi.h> +#include <linux/bitmap.h> +#include <linux/bits.h> #include <linux/cleanup.h> +#include <linux/device.h> #include <linux/intel_vsec.h> #include <linux/pci.h> #include <linux/types.h> @@ -21,11 +25,114 @@ #define SSRAM_PCH_OFFSET 0x60 #define SSRAM_IOE_OFFSET 0x68 #define SSRAM_DEVID_OFFSET 0x70 +#define SSRAM_BASE_ADDR_MASK GENMASK_ULL(63, 3) +#define SSRAM_PCI_PMC_MASK (BIT(PMC_IDX_MAIN) | BIT(PMC_IDX_IOE) | BIT(PMC_IDX_PCH)) DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T)) -static struct pmc_ssram_telemetry *pmc_ssram_telems; -static bool device_probed; +enum resource_method { + RES_METHOD_PCI, + RES_METHOD_ACPI, +}; + +struct ssram_type { + enum resource_method method; + enum pmc_index p_index; +}; + +static const struct ssram_type pci_main = { + .method = RES_METHOD_PCI, + .p_index = PMC_IDX_MAIN, +}; + +static const struct ssram_type acpi_main = { + .method = RES_METHOD_ACPI, + .p_index = PMC_IDX_MAIN, +}; + +static const struct ssram_type acpi_pch = { + .method = RES_METHOD_ACPI, + .p_index = PMC_IDX_PCH, +}; + +enum pmc_ssram_state { + PMC_SSRAM_UNPROBED = 0, + PMC_SSRAM_PROBING, + PMC_SSRAM_PRESENT, + PMC_SSRAM_ABSENT, +}; + +static enum pmc_ssram_state pmc_ssram_state[MAX_NUM_PMC]; +static struct pmc_ssram_telemetry pmc_ssram_telems[MAX_NUM_PMC]; + +struct pmc_ssram_probe_cache { + /* Per-index values staged by probe before publishing globally. */ + struct pmc_ssram_telemetry telems[MAX_NUM_PMC]; + /* PMCs this probe instance is responsible for publishing. */ + unsigned long owned_mask; + /* Subset of owned_mask that was discovered successfully. */ + unsigned long valid_mask; +}; + +struct pmc_ssram_drvdata { + /* PMCs published by this bound device; used to unpublish on remove. */ + unsigned long owned_mask; +}; + +static inline u64 get_base(void __iomem *addr, u32 offset) +{ + return lo_hi_readq(addr + offset) & SSRAM_BASE_ADDR_MASK; +} + +static void pmc_ssram_get_devid_pwrmbase(struct pmc_ssram_probe_cache *probe_cache, + void __iomem *ssram, unsigned int pmc_idx) +{ + u64 pwrm_base; + u16 devid; + + pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET); + devid = readw(ssram + SSRAM_DEVID_OFFSET); + + probe_cache->telems[pmc_idx].base_addr = pwrm_base; + probe_cache->telems[pmc_idx].devid = devid; +} + +static void pmc_ssram_publish_absent(unsigned int pmc_idx) +{ + /* + * Publish only the state without modifying base_addr and devid. This + * lets a reader that already observed PRESENT finish copying the + * previous values even if unbind concurrently publishes ABSENT. Readers + * that observe ABSENT return -ENODEV without accessing data. + */ + smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_ABSENT); +} + +static void pmc_ssram_publish_present(struct pmc_ssram_probe_cache *probe_cache, + unsigned int pmc_idx) +{ + /* + * The devid and base_addr fields are read from hardware MMIO registers + * whose values are stable for a given PMC index. A reader that observed + * PRESENT from an earlier probe can safely copy them while a concurrent + * rebind republishes those fields because both probes read the same + * hardware values. + */ + pmc_ssram_telems[pmc_idx] = probe_cache->telems[pmc_idx]; + /* + * Publish base_addr and devid before publishing PRESENT. Pairs with the + * acquire load in the reader that consumes them after observing PRESENT. + */ + smp_store_release(&pmc_ssram_state[pmc_idx], PMC_SSRAM_PRESENT); +} + +static void pmc_ssram_mark_probing(unsigned long mask) +{ + unsigned long bit; + + for_each_set_bit(bit, &mask, MAX_NUM_PMC) + WRITE_ONCE(pmc_ssram_state[bit], PMC_SSRAM_PROBING); +} static int pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem *ssram) @@ -60,21 +167,18 @@ pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem info.base_addr = ssram_base; info.parent = &pcidev->dev; - return intel_vsec_register(pcidev, &info); -} - -static inline u64 get_base(void __iomem *addr, u32 offset) -{ - return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3); + return intel_vsec_register(&pcidev->dev, &info); } static int -pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset) +pmc_ssram_telemetry_get_pmc_pci(struct pci_dev *pcidev, + struct pmc_ssram_probe_cache *probe_cache, + unsigned int pmc_idx, u32 offset) { void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL; void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL; - u64 ssram_base, pwrm_base; - u16 devid; + u64 ssram_base; + int ret; ssram_base = pci_resource_start(pcidev, 0); tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE); @@ -99,14 +203,107 @@ pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 of ssram = no_free_ptr(tmp_ssram); } - pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET); - devid = readw(ssram + SSRAM_DEVID_OFFSET); - - pmc_ssram_telems[pmc_idx].devid = devid; - pmc_ssram_telems[pmc_idx].base_addr = pwrm_base; + pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx); /* Find and register and PMC telemetry entries */ - return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram); + ret = pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram); + if (ret) + dev_warn(&pcidev->dev, "could not register PMT\n"); + + probe_cache->valid_mask |= BIT(pmc_idx); + + return 0; +} + +static int pmc_ssram_telemetry_pci_init(struct pci_dev *pcidev, + struct pmc_ssram_probe_cache *probe_cache) +{ + int ret; + + ret = pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_MAIN, 0); + if (ret) + return ret; + + /* + * If MAIN PMC enumeration is successful but either IOE or PCH fail, + * don't fail probe as the MAIN PMC is still useful as it provides the + * global reset and slp_s0 counter access. Failed or missing secondary + * PMCs are left out of valid_mask and published as absent. + */ + pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_IOE, + SSRAM_IOE_OFFSET); + + pmc_ssram_telemetry_get_pmc_pci(pcidev, probe_cache, PMC_IDX_PCH, + SSRAM_PCH_OFFSET); + + return 0; +} + +static int pmc_ssram_telemetry_get_pmc_acpi(struct pci_dev *pcidev, + struct pmc_ssram_probe_cache *probe_cache, + unsigned int pmc_idx) +{ + u64 ssram_base; + + ssram_base = pci_resource_start(pcidev, 0); + if (!ssram_base) + return -ENODEV; + + void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = + ioremap(ssram_base, SSRAM_HDR_SIZE); + if (!ssram) + return -ENOMEM; + + pmc_ssram_get_devid_pwrmbase(probe_cache, ssram, pmc_idx); + probe_cache->valid_mask |= BIT(pmc_idx); + + return 0; +} + +static int pmc_ssram_telemetry_acpi_init(struct pci_dev *pcidev, + struct pmc_ssram_probe_cache *probe_cache, + enum pmc_index index) +{ + struct intel_vsec_header header; + struct intel_vsec_header *headers[2] = { &header, NULL }; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + struct intel_vsec_platform_info info = { }; + union acpi_object *dsd; + acpi_handle handle; + acpi_status status; + int ret; + + handle = ACPI_HANDLE(&pcidev->dev); + if (!handle) + return -ENODEV; + + status = acpi_evaluate_object(handle, "_DSD", NULL, &buf); + if (ACPI_FAILURE(status)) + return -ENODEV; + + void *dsd_buf __free(pmc_acpi_free) = buf.pointer; + + dsd = pmc_find_telem_guid(dsd_buf); + if (!dsd) + return -ENODEV; + + acpi_disc_t disc __free(kfree) = pmc_parse_telem_dsd(dsd, &header); + if (IS_ERR(disc)) + return PTR_ERR(disc); + + info.headers = headers; + info.caps = VSEC_CAP_TELEMETRY; + info.acpi_disc = disc; + info.src = INTEL_VSEC_DISC_ACPI; + + /* This is an ACPI companion device. PCI BAR will be used for base addr. */ + info.base_addr = 0; + + ret = intel_vsec_register(&pcidev->dev, &info); + if (ret) + dev_warn(&pcidev->dev, "could not register PMT\n"); + + return pmc_ssram_telemetry_get_pmc_acpi(pcidev, probe_cache, index); } /** @@ -114,48 +311,107 @@ pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 of * @pmc_idx: Index of the PMC * @pmc_ssram_telemetry: pmc_ssram_telemetry structure to store the PMC information * + * State flow per PMC index: + * - PMC_SSRAM_UNPROBED: Probe has not started for this index. + * - PMC_SSRAM_PROBING: Probe is currently discovering data for this index. + * - PMC_SSRAM_PRESENT: base_addr/devid were published and can be read. + * - PMC_SSRAM_ABSENT: No data is available for this index. + * + * Readers use an acquire load of the state. If state is PRESENT, reads of + * base_addr/devid are ordered after the state observation and pair with the + * writer's release-store when publishing PRESENT. + * * Return: * * 0 - Success * * -EAGAIN - Probe function has not finished yet. Try again. * * -EINVAL - Invalid pmc_idx - * * -ENODEV - PMC device is not available + * * -ENODEV - PMC device is not available (hardware absent or driver failed to initialize) */ int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx, struct pmc_ssram_telemetry *pmc_ssram_telemetry) { + enum pmc_ssram_state state; + + if (pmc_idx >= MAX_NUM_PMC) + return -EINVAL; + /* * PMCs are discovered in probe function. If this function is called before - * probe function complete, the result would be invalid. Use device_probed - * variable to avoid this case. Return -EAGAIN to inform the consumer to call - * again later. + * probe function complete, the result would be invalid. Use per-PMC state + * to inform the consumer to call again later. */ - if (!device_probed) + state = smp_load_acquire(&pmc_ssram_state[pmc_idx]); + if (state == PMC_SSRAM_UNPROBED || state == PMC_SSRAM_PROBING) return -EAGAIN; - /* - * Memory barrier is used to ensure the correct read order between - * device_probed variable and PMC info. - */ - smp_rmb(); - if (pmc_idx >= MAX_NUM_PMC) - return -EINVAL; - - if (!pmc_ssram_telems || !pmc_ssram_telems[pmc_idx].devid) + if (state == PMC_SSRAM_ABSENT) return -ENODEV; + /* + * Acquire semantics order reads of devid and base_addr after observing + * PRESENT and pair with the writer's release-store. + */ pmc_ssram_telemetry->devid = pmc_ssram_telems[pmc_idx].devid; pmc_ssram_telemetry->base_addr = pmc_ssram_telems[pmc_idx].base_addr; return 0; } EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info); -static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id) +static void pmc_ssram_publish_absent_mask(unsigned long mask) +{ + unsigned long bit; + + for_each_set_bit(bit, &mask, MAX_NUM_PMC) + pmc_ssram_publish_absent(bit); +} + +static void pmc_ssram_publish_telems(struct pmc_ssram_probe_cache *probe_cache, int ret) { + unsigned long bit; + + /* If probe failed, all owned indexes become absent for readers. */ + if (ret) { + pmc_ssram_publish_absent_mask(probe_cache->owned_mask); + return; + } + + /* Publish each owned index independently based on discovery result. */ + for_each_set_bit(bit, &probe_cache->owned_mask, MAX_NUM_PMC) { + if (probe_cache->valid_mask & BIT(bit)) + pmc_ssram_publish_present(probe_cache, bit); + else + pmc_ssram_publish_absent(bit); + } +} + +static int pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id) +{ + struct pmc_ssram_probe_cache probe_cache = {}; + struct pmc_ssram_drvdata *drvdata; + const struct ssram_type *ssram_type; + enum resource_method method; + enum pmc_index index; int ret; - pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC, - GFP_KERNEL); - if (!pmc_ssram_telems) { + ssram_type = (const struct ssram_type *)id->driver_data; + if (!ssram_type) { + dev_dbg(&pcidev->dev, "missing driver data\n"); + return -EINVAL; + } + + index = ssram_type->p_index; + method = ssram_type->method; + if (method == RES_METHOD_PCI) + probe_cache.owned_mask = SSRAM_PCI_PMC_MASK; + else if (method == RES_METHOD_ACPI) + probe_cache.owned_mask = BIT(index); + else + return -EINVAL; + + pmc_ssram_mark_probing(probe_cache.owned_mask); + + drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) { ret = -ENOMEM; goto probe_finish; } @@ -166,43 +422,67 @@ static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct goto probe_finish; } - ret = pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_MAIN, 0); - if (ret) - goto probe_finish; + if (method == RES_METHOD_PCI) + ret = pmc_ssram_telemetry_pci_init(pcidev, &probe_cache); + else if (method == RES_METHOD_ACPI) + ret = pmc_ssram_telemetry_acpi_init(pcidev, &probe_cache, index); + else + ret = -EINVAL; - pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET); - pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET); + if (!ret) { + drvdata->owned_mask = probe_cache.owned_mask; + pci_set_drvdata(pcidev, drvdata); + } probe_finish: - /* - * Memory barrier is used to ensure the correct write order between PMC info - * and device_probed variable. - */ - smp_wmb(); - device_probed = true; + pmc_ssram_publish_telems(&probe_cache, ret); + return ret; } -static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN) }, +static void pmc_ssram_telemetry_remove(struct pci_dev *pcidev) +{ + struct pmc_ssram_drvdata *drvdata = pci_get_drvdata(pcidev); + + if (drvdata) + pmc_ssram_publish_absent_mask(drvdata->owned_mask); +} + +static const struct pci_device_id pmc_ssram_telemetry_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM), + .driver_data = (kernel_ulong_t)&pci_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS), + .driver_data = (kernel_ulong_t)&pci_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM), + .driver_data = (kernel_ulong_t)&pci_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM), + .driver_data = (kernel_ulong_t)&pci_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH), + .driver_data = (kernel_ulong_t)&pci_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP), + .driver_data = (kernel_ulong_t)&pci_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN), + .driver_data = (kernel_ulong_t)&pci_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCDH), + .driver_data = (kernel_ulong_t)&acpi_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCDS), + .driver_data = (kernel_ulong_t)&acpi_main }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_NVL_PCHS), + .driver_data = (kernel_ulong_t)&acpi_pch }, { } }; -MODULE_DEVICE_TABLE(pci, intel_pmc_ssram_telemetry_pci_ids); +MODULE_DEVICE_TABLE(pci, pmc_ssram_telemetry_pci_ids); -static struct pci_driver intel_pmc_ssram_telemetry_driver = { +static struct pci_driver pmc_ssram_telemetry_driver = { .name = "intel_pmc_ssram_telemetry", - .id_table = intel_pmc_ssram_telemetry_pci_ids, - .probe = intel_pmc_ssram_telemetry_probe, + .id_table = pmc_ssram_telemetry_pci_ids, + .probe = pmc_ssram_telemetry_probe, + .remove = pmc_ssram_telemetry_remove, }; -module_pci_driver(intel_pmc_ssram_telemetry_driver); +module_pci_driver(pmc_ssram_telemetry_driver); MODULE_IMPORT_NS("INTEL_VSEC"); +MODULE_IMPORT_NS("INTEL_PMC_CORE"); MODULE_AUTHOR("Xi Pardee <xi.pardee@intel.com>"); MODULE_DESCRIPTION("Intel PMC SSRAM Telemetry driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/pmc/wcl.c b/drivers/platform/x86/intel/pmc/wcl.c index a45707e6364f..d34ac916907d 100644 --- a/drivers/platform/x86/intel/pmc/wcl.c +++ b/drivers/platform/x86/intel/pmc/wcl.c @@ -469,6 +469,8 @@ static struct pmc_info wcl_pmc_info_list[] = { {} }; +static const u8 wcl_pmc_list[] = {PMC_IDX_MAIN}; + #define WCL_NPU_PCI_DEV 0xfd3e /* @@ -493,12 +495,14 @@ static int wcl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in } struct pmc_dev_info wcl_pmc_dev = { - .pci_func = 2, .regmap_list = wcl_pmc_info_list, + .num_pmcs = ARRAY_SIZE(wcl_pmc_list), + .pmc_list = wcl_pmc_list, .map = &wcl_pcdn_reg_map, .sub_req_show = &pmc_core_substate_blk_req_fops, .suspend = cnl_suspend, .resume = wcl_resume, .init = wcl_core_init, .sub_req = pmc_core_pmt_get_blk_sub_req, + .ssram_hidden = true, }; diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index be3c8d9e4fff..d0ab8e33c62a 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -12,6 +12,7 @@ #include <linux/log2.h> #include <linux/intel_vsec.h> #include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/mm.h> #include <linux/pci.h> @@ -60,11 +61,11 @@ pmt_memcpy64_fromio(void *to, const u64 __iomem *from, size_t count) return count; } -int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf, +int pmt_telem_read_mmio(struct device *dev, struct pmt_callbacks *cb, u32 guid, void *buf, void __iomem *addr, loff_t off, u32 count) { if (cb && cb->read_telem) - return cb->read_telem(pdev, guid, buf, off, count); + return cb->read_telem(dev, guid, buf, off, count); addr += off; @@ -99,7 +100,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj, if (count > entry->size - off) count = entry->size - off; - count = pmt_telem_read_mmio(entry->pcidev, entry->cb, entry->header.guid, buf, + count = pmt_telem_read_mmio(entry->ep->dev, entry->cb, entry->header.guid, buf, entry->base, off, count); return count; @@ -204,13 +205,14 @@ struct class intel_pmt_class = { }; EXPORT_SYMBOL_GPL(intel_pmt_class); -static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, - struct intel_vsec_device *ivdev, - struct resource *disc_res) +static int pmt_resolve_access_pci(struct intel_pmt_entry *entry, + struct intel_vsec_device *ivdev, + int idx) { - struct pci_dev *pci_dev = ivdev->pcidev; + struct pci_dev *pci_dev = to_pci_dev(ivdev->dev); struct device *dev = &ivdev->auxdev.dev; struct intel_pmt_header *header = &entry->header; + struct resource *disc_res; u8 bir; /* @@ -235,6 +237,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, * For access_type LOCAL, the base address is as follows: * base address = end of discovery region + base offset */ + disc_res = &ivdev->resource[idx]; entry->base_addr = disc_res->end + 1 + header->base_offset; /* @@ -284,6 +287,81 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, } entry->pcidev = pci_dev; + + return 0; +} + +static int pmt_resolve_access_acpi(struct intel_pmt_entry *entry, + struct intel_vsec_device *ivdev) +{ + struct pci_dev *pci_dev = NULL; + struct device *dev = &ivdev->auxdev.dev; + struct intel_pmt_header *header = &entry->header; + u8 bir; + + if (dev_is_pci(ivdev->dev)) + pci_dev = to_pci_dev(ivdev->dev); + + /* + * The base offset should always be 8 byte aligned. + * + * For non-local access types the lower 3 bits of base offset + * contains the index of the base address register where the + * telemetry can be found. + */ + bir = GET_BIR(header->base_offset); + + switch (header->access_type) { + case ACCESS_BARID: + /* ACPI platform drivers use base_addr */ + if (ivdev->base_addr) { + entry->base_addr = ivdev->base_addr + + GET_ADDRESS(header->base_offset); + break; + } + + /* If base_addr is not provided, then this is an ACPI companion device */ + if (!pci_dev) { + dev_err(dev, "ACCESS_BARID requires PCI BAR resources or base_addr\n"); + return -EINVAL; + } + + entry->base_addr = pci_resource_start(pci_dev, bir) + + GET_ADDRESS(header->base_offset); + break; + default: + dev_err(dev, "Unsupported access type %d for ACPI based PMT\n", + header->access_type); + return -EINVAL; + } + + return 0; +} + +static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, + struct intel_vsec_device *ivdev, + int idx) +{ + struct intel_pmt_header *header = &entry->header; + struct device *dev = &ivdev->auxdev.dev; + int ret; + + switch (ivdev->src) { + case INTEL_VSEC_DISC_PCI: + ret = pmt_resolve_access_pci(entry, ivdev, idx); + if (ret) + return ret; + break; + case INTEL_VSEC_DISC_ACPI: + ret = pmt_resolve_access_acpi(entry, ivdev); + if (ret) + return ret; + break; + default: + dev_err(dev, "Unknown discovery source: %d\n", ivdev->src); + return -EINVAL; + } + entry->guid = header->guid; entry->size = header->size; entry->cb = ivdev->priv_data; @@ -368,24 +446,84 @@ fail_dev_create: return ret; } +static int pmt_get_headers(struct intel_vsec_device *ivdev, int idx, + struct intel_pmt_entry *entry) +{ + struct device *dev = &ivdev->auxdev.dev; + size_t header_bytes = sizeof(entry->disc_header); + + switch (ivdev->src) { + case INTEL_VSEC_DISC_PCI: { + struct resource *disc_res = &ivdev->resource[idx]; + void __iomem *disc_table; + + disc_table = devm_ioremap_resource(dev, disc_res); + if (IS_ERR(disc_table)) + return PTR_ERR(disc_table); + + /* + * The mapped resource is sized by the namespace's DVSEC + * entry_size (in dwords), which can be less than the default + * size (e.g. telemetry uses entry_size = 3, 12 bytes). Cap the + * copy to resource_size() to avoid reading past the mapped + * region. + */ + memset(entry->disc_header, 0, header_bytes); + memcpy_fromio(entry->disc_header, disc_table, + min(header_bytes, resource_size(disc_res))); + + /* Used by crashlog driver */ + entry->disc_table = disc_table; + + return 0; + } + case INTEL_VSEC_DISC_ACPI: { + memcpy(entry->disc_header, &ivdev->acpi_disc[idx][0], header_bytes); + /* + * No MMIO mapping exists on the ACPI source path; the cached + * headers are the only view of the discovery record. Consumers + * that dereference disc_table (e.g. crashlog) must therefore + * only be wired to namespaces backed by INTEL_VSEC_DISC_PCI. + */ + entry->disc_table = NULL; + + return 0; + } + default: + dev_err(dev, "Unknown discovery source type: %d\n", ivdev->src); + break; + } + + return -EINVAL; +} + int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_pmt_namespace *ns, struct intel_vsec_device *intel_vsec_dev, int idx) { struct device *dev = &intel_vsec_dev->auxdev.dev; - struct resource *disc_res; int ret; - disc_res = &intel_vsec_dev->resource[idx]; + ret = pmt_get_headers(intel_vsec_dev, idx, entry); + if (ret) + return ret; - entry->disc_table = devm_ioremap_resource(dev, disc_res); - if (IS_ERR(entry->disc_table)) - return PTR_ERR(entry->disc_table); + if (ns->pmt_pre_decode) { + ret = ns->pmt_pre_decode(intel_vsec_dev, entry); + if (ret) + return ret; + } ret = ns->pmt_header_decode(entry, dev); if (ret) return ret; - ret = intel_pmt_populate_entry(entry, intel_vsec_dev, disc_res); + if (ns->pmt_post_decode) { + ret = ns->pmt_post_decode(intel_vsec_dev, entry); + if (ret) + return ret; + } + + ret = intel_pmt_populate_entry(entry, intel_vsec_dev, idx); if (ret) return ret; diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index 3c5ad5f52bca..a0ece4fc3837 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -19,11 +19,12 @@ #define GET_BIR(v) ((v) & GENMASK(2, 0)) #define GET_ADDRESS(v) ((v) & GENMASK(31, 3)) +struct device; struct pci_dev; extern struct class intel_pmt_class; struct telem_endpoint { - struct pci_dev *pcidev; + struct device *dev; struct telem_header header; struct pmt_callbacks *cb; void __iomem *base; @@ -36,12 +37,14 @@ struct intel_pmt_header { u32 size; u32 guid; u8 access_type; + u8 telem_type; }; struct intel_pmt_entry { struct telem_endpoint *ep; struct pci_dev *pcidev; struct intel_pmt_header header; + u32 disc_header[PMT_DISC_DWORDS]; struct bin_attribute pmt_bin_attr; const struct attribute_group *attr_grp; struct kobject *kobj; @@ -61,11 +64,15 @@ struct intel_pmt_namespace { struct xarray *xa; int (*pmt_header_decode)(struct intel_pmt_entry *entry, struct device *dev); + int (*pmt_pre_decode)(struct intel_vsec_device *ivdev, + struct intel_pmt_entry *entry); + int (*pmt_post_decode)(struct intel_vsec_device *ivdev, + struct intel_pmt_entry *entry); int (*pmt_add_endpoint)(struct intel_vsec_device *ivdev, struct intel_pmt_entry *entry); }; -int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf, +int pmt_telem_read_mmio(struct device *dev, struct pmt_callbacks *cb, u32 guid, void *buf, void __iomem *addr, loff_t off, u32 count); bool intel_pmt_is_early_client_hw(struct device *dev); int intel_pmt_dev_create(struct intel_pmt_entry *entry, diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index b0393c9c5b4b..f936daf99e4d 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -496,11 +496,9 @@ static const struct crashlog_info *select_crashlog_info(u32 type, u32 version) return &crashlog_type1_ver2; } -static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, - struct device *dev) +static int pmt_crashlog_pre_decode(struct intel_vsec_device *ivdev, + struct intel_pmt_entry *entry) { - void __iomem *disc_table = entry->disc_table; - struct intel_pmt_header *header = &entry->header; struct crashlog_entry *crashlog; u32 version; u32 type; @@ -513,6 +511,16 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, mutex_init(&crashlog->control_mutex); crashlog->info = select_crashlog_info(type, version); + entry->attr_grp = crashlog->info->attr_grp; + + return 0; +} + +static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, + struct device *dev) +{ + void __iomem *disc_table = entry->disc_table; + struct intel_pmt_header *header = &entry->header; header->access_type = GET_ACCESS(readl(disc_table)); header->guid = readl(disc_table + GUID_OFFSET); @@ -521,8 +529,6 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, /* Size is measured in DWORDS, but accessor returns bytes */ header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET)); - entry->attr_grp = crashlog->info->attr_grp; - return 0; } @@ -530,6 +536,7 @@ static DEFINE_XARRAY_ALLOC(crashlog_array); static struct intel_pmt_namespace pmt_crashlog_ns = { .name = "crashlog", .xa = &crashlog_array, + .pmt_pre_decode = pmt_crashlog_pre_decode, .pmt_header_decode = pmt_crashlog_header_decode, }; diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index e500aa327d23..c482368bfaae 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -542,7 +542,7 @@ static int pmt_features_probe(struct auxiliary_device *auxdev, const struct auxi if (!priv) return -ENOMEM; - priv->parent = &ivdev->pcidev->dev; + priv->parent = ivdev->dev; auxiliary_set_drvdata(auxdev, priv); priv->dev = device_create(&intel_pmt_class, &auxdev->dev, MKDEV(0, 0), priv, @@ -609,7 +609,7 @@ void intel_pmt_get_features(struct intel_pmt_entry *entry) mutex_lock(&feature_list_lock); list_for_each_entry(feature, &pmt_feature_list, list) { - if (feature->priv->parent != &entry->ep->pcidev->dev) + if (feature->priv->parent != entry->ep->dev) continue; pmt_get_features(entry, feature); diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c index a52803bfe124..953f35b6daec 100644 --- a/drivers/platform/x86/intel/pmt/telemetry.c +++ b/drivers/platform/x86/intel/pmt/telemetry.c @@ -58,14 +58,9 @@ struct pmt_telem_priv { struct intel_pmt_entry entry[]; }; -static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry, - struct device *dev) +static bool pmt_telem_region_overlaps(struct device *dev, u32 guid, u32 type) { - u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET); - if (intel_pmt_is_early_client_hw(dev)) { - u32 type = TELEM_TYPE(readl(entry->disc_table)); - if ((type == TELEM_TYPE_PUNIT_FIXED) || (guid == TELEM_CLIENT_FIXED_BLOCK_GUID)) return true; @@ -77,18 +72,28 @@ static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry, static int pmt_telem_header_decode(struct intel_pmt_entry *entry, struct device *dev) { - void __iomem *disc_table = entry->disc_table; struct intel_pmt_header *header = &entry->header; + u32 *disc_header = entry->disc_header; - if (pmt_telem_region_overlaps(entry, dev)) - return 1; - - header->access_type = TELEM_ACCESS(readl(disc_table)); - header->guid = readl(disc_table + TELEM_GUID_OFFSET); - header->base_offset = readl(disc_table + TELEM_BASE_OFFSET); + header->access_type = TELEM_ACCESS(disc_header[0]); + header->guid = disc_header[1]; + header->base_offset = disc_header[2]; /* Size is measured in DWORDS, but accessor returns bytes */ - header->size = TELEM_SIZE(readl(disc_table)); + header->size = TELEM_SIZE(disc_header[0]); + header->telem_type = TELEM_TYPE(disc_header[0]); + + return 0; +} + +static int pmt_telem_post_decode(struct intel_vsec_device *ivdev, + struct intel_pmt_entry *entry) +{ + struct intel_pmt_header *header = &entry->header; + struct device *dev = &ivdev->auxdev.dev; + + if (pmt_telem_region_overlaps(dev, header->guid, header->telem_type)) + return 1; /* * Some devices may expose non-functioning entries that are @@ -112,7 +117,7 @@ static int pmt_telem_add_endpoint(struct intel_vsec_device *ivdev, return -ENOMEM; ep = entry->ep; - ep->pcidev = ivdev->pcidev; + ep->dev = ivdev->dev; ep->header.access_type = entry->header.access_type; ep->header.guid = entry->header.guid; ep->header.base_offset = entry->header.base_offset; @@ -131,6 +136,7 @@ static struct intel_pmt_namespace pmt_telem_ns = { .name = "telem", .xa = &telem_array, .pmt_header_decode = pmt_telem_header_decode, + .pmt_post_decode = pmt_telem_post_decode, .pmt_add_endpoint = pmt_telem_add_endpoint, }; @@ -204,7 +210,7 @@ int pmt_telem_get_endpoint_info(int devid, struct telem_endpoint_info *info) goto unlock; } - info->pdev = entry->ep->pcidev; + info->dev = entry->ep->dev; info->header = entry->ep->header; unlock: @@ -218,9 +224,10 @@ static int pmt_copy_region(struct telemetry_region *region, struct intel_pmt_entry *entry) { + struct pci_dev *pdev = to_pci_dev(entry->ep->dev); struct oobmsm_plat_info *plat_info; - plat_info = intel_vsec_get_mapping(entry->ep->pcidev); + plat_info = intel_vsec_get_mapping(pdev); if (IS_ERR(plat_info)) return PTR_ERR(plat_info); @@ -308,7 +315,7 @@ int pmt_telem_read(struct telem_endpoint *ep, u32 id, u64 *data, u32 count) if (offset + NUM_BYTES_QWORD(count) > size) return -EINVAL; - pmt_telem_read_mmio(ep->pcidev, ep->cb, ep->header.guid, data, ep->base, offset, + pmt_telem_read_mmio(ep->dev, ep->cb, ep->header.guid, data, ep->base, offset, NUM_BYTES_QWORD(count)); return ep->present ? 0 : -EPIPE; @@ -335,7 +342,7 @@ int pmt_telem_read32(struct telem_endpoint *ep, u32 id, u32 *data, u32 count) EXPORT_SYMBOL_NS_GPL(pmt_telem_read32, "INTEL_PMT_TELEMETRY"); struct telem_endpoint * -pmt_telem_find_and_register_endpoint(struct pci_dev *pcidev, u32 guid, u16 pos) +pmt_telem_find_and_register_endpoint(struct device *dev, u32 guid, u16 pos) { int devid = 0; int inst = 0; @@ -348,7 +355,7 @@ pmt_telem_find_and_register_endpoint(struct pci_dev *pcidev, u32 guid, u16 pos) if (err) return ERR_PTR(err); - if (ep_info.header.guid == guid && ep_info.pdev == pcidev) { + if (ep_info.header.guid == guid && ep_info.dev == dev) { if (inst == pos) return pmt_telem_register_endpoint(devid); ++inst; diff --git a/drivers/platform/x86/intel/pmt/telemetry.h b/drivers/platform/x86/intel/pmt/telemetry.h index d45af5512b4e..0f88c5e7d90e 100644 --- a/drivers/platform/x86/intel/pmt/telemetry.h +++ b/drivers/platform/x86/intel/pmt/telemetry.h @@ -6,8 +6,8 @@ #define PMT_TELEM_TELEMETRY 0 #define PMT_TELEM_CRASHLOG 1 +struct device; struct telem_endpoint; -struct pci_dev; struct telem_header { u8 access_type; @@ -17,7 +17,7 @@ struct telem_header { }; struct telem_endpoint_info { - struct pci_dev *pdev; + struct device *dev; struct telem_header header; }; @@ -71,8 +71,8 @@ int pmt_telem_get_endpoint_info(int devid, struct telem_endpoint_info *info); /** * pmt_telem_find_and_register_endpoint() - Get a telemetry endpoint from - * pci_dev device, guid and pos - * @pdev: PCI device inside the Intel vsec + * device, guid and pos + * @dev: device inside the Intel vsec * @guid: GUID of the telemetry space * @pos: Instance of the guid * @@ -80,8 +80,8 @@ int pmt_telem_get_endpoint_info(int devid, struct telem_endpoint_info *info); * * endpoint - On success returns pointer to the telemetry endpoint * * -ENXIO - telemetry endpoint not found */ -struct telem_endpoint *pmt_telem_find_and_register_endpoint(struct pci_dev *pcidev, - u32 guid, u16 pos); +struct telem_endpoint * +pmt_telem_find_and_register_endpoint(struct device *dev, u32 guid, u16 pos); /** * pmt_telem_read() - Read qwords from counter sram using sample id diff --git a/drivers/platform/x86/intel/punit_ipc.c b/drivers/platform/x86/intel/punit_ipc.c index 14513010daad..6d770b950dfb 100644 --- a/drivers/platform/x86/intel/punit_ipc.c +++ b/drivers/platform/x86/intel/punit_ipc.c @@ -13,7 +13,6 @@ #include <linux/device.h> #include <linux/interrupt.h> #include <linux/io.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> diff --git a/drivers/platform/x86/intel/rst.c b/drivers/platform/x86/intel/rst.c index f3a60e14d4c1..bb19f0d89305 100644 --- a/drivers/platform/x86/intel/rst.c +++ b/drivers/platform/x86/intel/rst.c @@ -5,6 +5,7 @@ #include <linux/acpi.h> #include <linux/module.h> +#include <linux/platform_device.h> #include <linux/slab.h> MODULE_DESCRIPTION("Intel Rapid Start Technology Driver"); @@ -99,10 +100,15 @@ static struct device_attribute irst_timeout_attr = { .store = irst_store_wakeup_time }; -static int irst_add(struct acpi_device *acpi) +static int irst_probe(struct platform_device *pdev) { + struct acpi_device *acpi; int error; + acpi = ACPI_COMPANION(&pdev->dev); + if (!acpi) + return -ENODEV; + error = device_create_file(&acpi->dev, &irst_timeout_attr); if (unlikely(error)) return error; @@ -114,8 +120,10 @@ static int irst_add(struct acpi_device *acpi) return error; } -static void irst_remove(struct acpi_device *acpi) +static void irst_remove(struct platform_device *pdev) { + struct acpi_device *acpi = ACPI_COMPANION(&pdev->dev); + device_remove_file(&acpi->dev, &irst_wakeup_attr); device_remove_file(&acpi->dev, &irst_timeout_attr); } @@ -125,16 +133,15 @@ static const struct acpi_device_id irst_ids[] = { {"", 0} }; -static struct acpi_driver irst_driver = { - .name = "intel_rapid_start", - .class = "intel_rapid_start", - .ids = irst_ids, - .ops = { - .add = irst_add, - .remove = irst_remove, +static struct platform_driver irst_driver = { + .probe = irst_probe, + .remove = irst_remove, + .driver = { + .name = "intel_rapid_start", + .acpi_match_table = irst_ids, }, }; -module_acpi_driver(irst_driver); +module_platform_driver(irst_driver); MODULE_DEVICE_TABLE(acpi, irst_ids); diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c index da75f53d0bcc..d7e37d4ace23 100644 --- a/drivers/platform/x86/intel/sdsi.c +++ b/drivers/platform/x86/intel/sdsi.c @@ -599,13 +599,14 @@ static int sdsi_get_layout(struct sdsi_priv *priv, struct disc_table *table) return 0; } -static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent, +static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct device *dev, struct disc_table *disc_table, struct resource *disc_res) { u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info); u32 size = FIELD_GET(DT_SIZE, disc_table->access_info); u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset); u32 offset = DT_OFFSET(disc_table->offset); + struct pci_dev *parent = to_pci_dev(dev); struct resource res = {}; /* Starting location of SDSi MMIO region based on access type */ @@ -681,7 +682,7 @@ static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_de return ret; /* Map the SDSi mailbox registers */ - ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res); + ret = sdsi_map_mbox_registers(priv, intel_cap_dev->dev, &disc_table, disc_res); if (ret) return ret; diff --git a/drivers/platform/x86/intel/smartconnect.c b/drivers/platform/x86/intel/smartconnect.c index 31019a1a6d5e..71e91ac60e5d 100644 --- a/drivers/platform/x86/intel/smartconnect.c +++ b/drivers/platform/x86/intel/smartconnect.c @@ -5,22 +5,28 @@ #include <linux/acpi.h> #include <linux/module.h> +#include <linux/platform_device.h> MODULE_DESCRIPTION("Intel Smart Connect disabling driver"); MODULE_LICENSE("GPL"); -static int smartconnect_acpi_init(struct acpi_device *acpi) +static int smartconnect_acpi_probe(struct platform_device *pdev) { unsigned long long value; + acpi_handle handle; acpi_status status; - status = acpi_evaluate_integer(acpi->handle, "GAOS", NULL, &value); + handle = ACPI_HANDLE(&pdev->dev); + if (!handle) + return -ENODEV; + + status = acpi_evaluate_integer(handle, "GAOS", NULL, &value); if (ACPI_FAILURE(status)) return -EINVAL; if (value & 0x1) { - dev_info(&acpi->dev, "Disabling Intel Smart Connect\n"); - status = acpi_execute_simple_method(acpi->handle, "SAOS", 0); + dev_info(&pdev->dev, "Disabling Intel Smart Connect\n"); + status = acpi_execute_simple_method(handle, "SAOS", 0); } return 0; @@ -32,13 +38,12 @@ static const struct acpi_device_id smartconnect_ids[] = { }; MODULE_DEVICE_TABLE(acpi, smartconnect_ids); -static struct acpi_driver smartconnect_driver = { - .name = "intel_smart_connect", - .class = "intel_smart_connect", - .ids = smartconnect_ids, - .ops = { - .add = smartconnect_acpi_init, +static struct platform_driver smartconnect_driver = { + .probe = smartconnect_acpi_probe, + .driver = { + .name = "intel_smart_connect", + .acpi_match_table = smartconnect_ids, }, }; -module_acpi_driver(smartconnect_driver); +module_platform_driver(smartconnect_driver); diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index b8cdaa233ea9..bb0d0b8fc54a 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -36,7 +36,7 @@ /* Supported SST hardware version by this driver */ #define ISST_MAJOR_VERSION 0 -#define ISST_MINOR_VERSION 2 +#define ISST_MINOR_VERSION 3 /* * Used to indicate if value read from MMIO needs to get multiplied @@ -336,8 +336,11 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev, int i; pd_info->perf_levels = devm_kcalloc(dev, levels, sizeof(struct perf_level), GFP_KERNEL); - if (!pd_info->perf_levels) - return 0; + if (!pd_info->perf_levels) { + pd_info->pp_header.allowed_level_mask = 0; + pd_info->pp_header.level_en_mask = 0; + return -ENOMEM; + } pd_info->ratio_unit = pd_info->pp_header.ratio_unit; pd_info->avx_levels = SST_MAX_AVX_LEVELS; @@ -367,7 +370,7 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev, static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info) { struct device *dev = &auxdev->dev; - int i, mask, levels; + int i, ret, mask, levels; *((u64 *)&pd_info->sst_header) = readq(pd_info->sst_base); pd_info->sst_header.cp_offset *= 8; @@ -399,8 +402,12 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai levels = i; mask <<= 1; } + + ret = sst_add_perf_profiles(auxdev, pd_info, levels + 1); + if (ret) + return ret; + pd_info->max_level = levels; - sst_add_perf_profiles(auxdev, pd_info, levels + 1); return 0; } @@ -558,6 +565,9 @@ static bool disable_dynamic_sst_features(void) { u64 value; + if (!cpu_feature_enabled(X86_FEATURE_HWP)) + return true; + rdmsrq(MSR_PM_ENABLE, value); return !(value & 0x1); } @@ -596,6 +606,9 @@ static bool disable_dynamic_sst_features(void) #define SST_CP_PRIORITY_TYPE_START 1 #define SST_CP_PRIORITY_TYPE_WIDTH 1 +#define SST_CP_MAX_ENABLE 1 +#define SST_CP_MAX_PRIORITY_TYPE 1 + static long isst_if_core_power_state(void __user *argp) { struct tpmi_per_power_domain_info *power_domain_info; @@ -615,6 +628,10 @@ static long isst_if_core_power_state(void __user *argp) if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; + if (core_power.enable > SST_CP_MAX_ENABLE || + core_power.priority_type > SST_CP_MAX_PRIORITY_TYPE) + return -EINVAL; + _write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET, SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE) _write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET, @@ -646,6 +663,11 @@ static long isst_if_core_power_state(void __user *argp) #define SST_CLOS_CONFIG_MAX_START 16 #define SST_CLOS_CONFIG_MAX_WIDTH 8 +#define SST_MAX_CLOS 3 + +#define SST_MAX_FREQ 0xff +#define SST_CLOS_MAX_PRIORITY 0x0f + static long isst_if_clos_param(void __user *argp) { struct tpmi_per_power_domain_info *power_domain_info; @@ -654,6 +676,9 @@ static long isst_if_clos_param(void __user *argp) if (copy_from_user(&clos_param, argp, sizeof(clos_param))) return -EFAULT; + if (clos_param.clos > SST_MAX_CLOS) + return -EINVAL; + power_domain_info = get_instance(clos_param.socket_id, clos_param.power_domain_id); if (!power_domain_info) return -EINVAL; @@ -662,6 +687,15 @@ static long isst_if_clos_param(void __user *argp) if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; + if (!in_range(clos_param.min_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1)) + return -EINVAL; + + if (!in_range(clos_param.max_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1)) + return -EINVAL; + + if (!in_range(clos_param.prop_prio, 0, SST_CLOS_MAX_PRIORITY + 1)) + return -EINVAL; + _write_cp_info("clos.min_freq", clos_param.min_freq_mhz, (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE), SST_CLOS_CONFIG_MIN_START, SST_CLOS_CONFIG_MIN_WIDTH, @@ -700,6 +734,8 @@ static long isst_if_clos_param(void __user *argp) #define SST_CLOS_ASSOC_CPUS_PER_REG 16 #define SST_CLOS_ASSOC_BITS_PER_CPU 4 +#define SST_CLOS_ASSOC_MAX_LOGICAL_CPU 63 + static long isst_if_clos_assoc(void __user *argp) { struct isst_if_clos_assoc_cmds assoc_cmds; @@ -726,7 +762,13 @@ static long isst_if_clos_assoc(void __user *argp) if (copy_from_user(&clos_assoc, ptr, sizeof(clos_assoc))) return -EFAULT; - if (clos_assoc.socket_id > topology_max_packages()) + if (clos_assoc.clos > SST_MAX_CLOS) + return -EINVAL; + + if (clos_assoc.socket_id >= topology_max_packages()) + return -EINVAL; + + if (clos_assoc.logical_cpu > SST_CLOS_ASSOC_MAX_LOGICAL_CPU) return -EINVAL; cpu = clos_assoc.logical_cpu; @@ -744,6 +786,8 @@ static long isst_if_clos_assoc(void __user *argp) pkg_id = clos_assoc.socket_id; sst_inst = isst_common.sst_inst[pkg_id]; + if (!sst_inst) + return -EINVAL; punit_id = map_partition_power_domain_id(sst_inst, punit_id, &part); if (punit_id < 0) @@ -842,6 +886,7 @@ static long isst_if_clos_assoc(void __user *argp) #define SST_PP_FEATURE_STATE_START 8 #define SST_PP_FEATURE_STATE_WIDTH 8 +#define SST_PP_FEATURE_STATE_VALID_MASK GENMASK(1, 0) #define SST_BF_FEATURE_SUPPORTED_START 12 #define SST_BF_FEATURE_SUPPORTED_WIDTH 1 @@ -869,12 +914,12 @@ static int isst_if_get_perf_level(void __user *argp) _read_pp_info("current_level", perf_level.current_level, SST_PP_STATUS_OFFSET, SST_PP_LEVEL_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE) _read_pp_info("locked", perf_level.locked, SST_PP_STATUS_OFFSET, - SST_PP_LOCK_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE) + SST_PP_LOCK_START, SST_PP_LOCK_WIDTH, SST_MUL_FACTOR_NONE) _read_pp_info("feature_state", perf_level.feature_state, SST_PP_STATUS_OFFSET, SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE) perf_level.enabled = !!(power_domain_info->sst_header.cap_mask & BIT(1)); - level_mask = perf_level.level_mask; + level_mask = perf_level.level_mask & power_domain_info->pp_header.level_en_mask; perf_level.sst_bf_support = 0; for_each_set_bit(level, &level_mask, BITS_PER_BYTE) { /* @@ -929,6 +974,9 @@ static int isst_if_set_perf_level(void __user *argp) if (!power_domain_info) return -EINVAL; + if (perf_level.level > power_domain_info->max_level) + return -EINVAL; + if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; @@ -992,6 +1040,9 @@ static int isst_if_set_perf_feature(void __user *argp) if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; + if (perf_feature.feature & ~SST_PP_FEATURE_STATE_VALID_MASK) + return -EINVAL; + _write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET, SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE) @@ -1258,6 +1309,12 @@ static int isst_if_get_perf_level_mask(void __user *argp) if (!power_domain_info) return -EINVAL; + if (cpumask.level > power_domain_info->max_level) + return -EINVAL; + + if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level))) + return -EINVAL; + _read_pp_level_info("mask", mask, cpumask.level, SST_PP_INFO_2_OFFSET, SST_PP_RSLVD_CORE_MASK_START, SST_PP_RSLVD_CORE_MASK_WIDTH, SST_MUL_FACTOR_NONE) @@ -1303,6 +1360,9 @@ static int isst_if_get_base_freq_info(void __user *argp) if (base_freq.level > power_domain_info->max_level) return -EINVAL; + if (!(power_domain_info->pp_header.level_en_mask & BIT(base_freq.level))) + return -EINVAL; + _read_bf_level_info("p1_high", base_freq.high_base_freq_mhz, base_freq.level, SST_BF_INFO_0_OFFSET, SST_BF_P1_HIGH_START, SST_BF_P1_HIGH_WIDTH, SST_MUL_FACTOR_FREQ) @@ -1339,6 +1399,12 @@ static int isst_if_get_base_freq_mask(void __user *argp) if (!power_domain_info) return -EINVAL; + if (cpumask.level > power_domain_info->max_level) + return -EINVAL; + + if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level))) + return -EINVAL; + _read_bf_level_info("BF-cpumask", mask, cpumask.level, SST_BF_INFO_1_OFFSET, P1_HI_CORE_MASK_START, P1_HI_CORE_MASK_WIDTH, SST_MUL_FACTOR_NONE) @@ -1367,6 +1433,8 @@ static int isst_if_get_tpmi_instance_count(void __user *argp) return -EINVAL; sst_inst = isst_common.sst_inst[tpmi_inst.socket_id]; + if (!sst_inst) + return -EINVAL; tpmi_inst.count = isst_instance_count(sst_inst); @@ -1434,6 +1502,9 @@ static int isst_if_get_turbo_freq_info(void __user *argp) if (turbo_freq.level > power_domain_info->max_level) return -EINVAL; + if (!(power_domain_info->pp_header.level_en_mask & BIT(turbo_freq.level))) + return -EINVAL; + turbo_freq.max_buckets = TRL_MAX_BUCKETS; turbo_freq.max_trl_levels = TRL_MAX_LEVELS; turbo_freq.max_clip_freqs = SST_TF_MAX_LP_CLIP_RATIOS; @@ -1458,6 +1529,8 @@ static int isst_if_get_turbo_freq_info(void __user *argp) SST_MUL_FACTOR_FREQ) } + memset(turbo_freq.bucket_core_counts, 0, sizeof(turbo_freq.bucket_core_counts)); + if (feature_rev >= 2) { bool has_tf_info_8 = false; @@ -1799,7 +1872,7 @@ process_pp_resume: if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE)) continue; - writeq(pd_info->saved_pp_control, power_domain_info->sst_base + + writeq(pd_info->saved_pp_control, pd_info->sst_base + pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET); } } diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c index 7070c94324e0..4bcafedf68ef 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c @@ -29,6 +29,13 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr, return sysfs_emit(buf, "%u\n", data->domain_id); } +static ssize_t show_instance_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct uncore_data *data = container_of(attr, struct uncore_data, instance_id_kobj_attr); + + return sysfs_emit(buf, "%u\n", data->instance_id); +} + static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr); @@ -200,6 +207,9 @@ static int create_attr_group(struct uncore_data *data, char *name) if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) { init_attribute_root_ro(domain_id); data->uncore_attrs[index++] = &data->domain_id_kobj_attr.attr; + init_attribute_root_ro(instance_id); + data->uncore_attrs[index++] = &data->instance_id_kobj_attr.attr; + init_attribute_root_ro(fabric_cluster_id); data->uncore_attrs[index++] = &data->fabric_cluster_id_kobj_attr.attr; init_attribute_root_ro(package_id); @@ -268,22 +278,27 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu) if (ret < 0) goto uncore_unlock; - data->instance_id = ret; + data->seqnum_id = ret; scnprintf(data->name, sizeof(data->name), "uncore%02d", ret); } else { scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d", data->package_id, data->die_id); } + /* + * Set the control CPU before any read path so entry recreation after CPU + * hotplug can populate read-only attributes from the new online CPU. + */ + data->control_cpu = cpu; uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ); uncore_read(data, &data->initial_max_freq_khz, UNCORE_INDEX_MAX_FREQ); ret = create_attr_group(data, data->name); if (ret) { + data->control_cpu = -1; if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) - ida_free(&intel_uncore_ida, data->instance_id); + ida_free(&intel_uncore_ida, data->seqnum_id); } else { - data->control_cpu = cpu; data->valid = true; } @@ -301,7 +316,7 @@ void uncore_freq_remove_die_entry(struct uncore_data *data) data->control_cpu = -1; data->valid = false; if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) - ida_free(&intel_uncore_ida, data->instance_id); + ida_free(&intel_uncore_ida, data->seqnum_id); mutex_unlock(&uncore_lock); } diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h index 0abe850ef54e..e319448dc1a4 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h @@ -35,7 +35,8 @@ * @die_id: Die id for this instance * @domain_id: Power domain id for this instance * @cluster_id: cluster id in a domain - * @instance_id: Unique instance id to append to directory name + * @seqnum_id: Unique sequential id to append to directory name + * @instance_id: Die indices or feature instances for a single TPMI device * @name: Sysfs entry name for this instance * @agent_type_mask: Bit mask of all hardware agents for this domain * @uncore_attr_group: Attribute group storage @@ -56,6 +57,7 @@ * @elc_floor_freq_khz_kobj_attr: Storage for kobject attribute elc_floor_freq_khz * @agent_types_kobj_attr: Storage for kobject attribute agent_type * @die_id_kobj_attr: Attribute storage for die_id information + * @instance_id_kobj_attr: Attribute storage for instance_id value * @uncore_attrs: Attribute storage for group creation * * This structure is used to encapsulate all data related to uncore sysfs @@ -71,6 +73,7 @@ struct uncore_data { int die_id; int domain_id; int cluster_id; + int seqnum_id; int instance_id; char name[32]; u16 agent_type_mask; @@ -90,7 +93,8 @@ struct uncore_data { struct kobj_attribute elc_floor_freq_khz_kobj_attr; struct kobj_attribute agent_types_kobj_attr; struct kobj_attribute die_id_kobj_attr; - struct attribute *uncore_attrs[15]; + struct kobj_attribute instance_id_kobj_attr; + struct attribute *uncore_attrs[16]; }; #define UNCORE_DOMAIN_ID_INVALID -1 diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c index 1237d9570886..44e2a90004df 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c @@ -31,7 +31,7 @@ #include "uncore-frequency-common.h" #define UNCORE_MAJOR_VERSION 0 -#define UNCORE_MINOR_VERSION 2 +#define UNCORE_MINOR_VERSION 3 #define UNCORE_ELC_SUPPORTED_VERSION 2 #define UNCORE_HEADER_INDEX 0 #define UNCORE_FABRIC_CLUSTER_OFFSET 8 @@ -385,7 +385,19 @@ static u8 io_die_index_next; /* Lock to protect io_die_start, io_die_index_next */ static DEFINE_MUTEX(domain_lock); -static void set_domain_id(int id, int num_resources, +static inline void set_instance_id(int id, struct tpmi_uncore_cluster_info *cluster_info) +{ + /* + * On non-partitioned systems domain_id can be used for mapping both + * CPUs to compute die IDs and physical die indexes to MMIO mapped + * memory. However on partitioned systems domain_id loses the second + * association. Therefore instance_id should be used for that instead, + * while domain_id should still be used to match CPUs to compute dies. + */ + cluster_info->uncore_data.instance_id = id; +} + +static void set_domain_id(int id, int num_resources, struct oobmsm_plat_info *plat_info, struct tpmi_uncore_cluster_info *cluster_info) { @@ -537,6 +549,7 @@ static void set_cdie_id(int domain_id, struct tpmi_uncore_cluster_info *cluster_ #define UNCORE_VERSION_MASK GENMASK_ULL(7, 0) #define UNCORE_LOCAL_FABRIC_CLUSTER_ID_MASK GENMASK_ULL(15, 8) #define UNCORE_CLUSTER_OFF_MASK GENMASK_ULL(7, 0) +#define UNCORE_AUTONOMOUS_UFS_DISABLED BIT(32) #define UNCORE_MAX_CLUSTER_PER_DOMAIN 8 static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) @@ -598,6 +611,7 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_ for (i = 0; i < num_resources; ++i) { struct tpmi_uncore_power_domain_info *pd_info; + bool auto_ufs_enabled; struct resource *res; u64 cluster_offset; u8 cluster_mask; @@ -647,6 +661,8 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_ continue; } + auto_ufs_enabled = !(header & UNCORE_AUTONOMOUS_UFS_DISABLED); + /* Find out number of clusters in this resource */ pd_info->cluster_count = hweight8(cluster_mask); @@ -686,10 +702,13 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_ set_cdie_id(i, cluster_info, plat_info); set_domain_id(i, num_resources, plat_info, cluster_info); + set_instance_id(i, cluster_info); cluster_info->uncore_root = tpmi_uncore; - if (TPMI_MINOR_VERSION(pd_info->ufs_header_ver) >= UNCORE_ELC_SUPPORTED_VERSION) + if ((TPMI_MINOR_VERSION(pd_info->ufs_header_ver) >= + UNCORE_ELC_SUPPORTED_VERSION) && + auto_ufs_enabled) cluster_info->elc_supported = true; ret = uncore_freq_add_entry(&cluster_info->uncore_data, 0); diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c index 9ca87e707582..874023c38fd1 100644 --- a/drivers/platform/x86/intel/vbtn.c +++ b/drivers/platform/x86/intel/vbtn.c @@ -275,12 +275,16 @@ static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel) static int intel_vbtn_probe(struct platform_device *device) { - acpi_handle handle = ACPI_HANDLE(&device->dev); bool dual_accel, has_buttons, has_switches; struct intel_vbtn_priv *priv; + acpi_handle handle; acpi_status status; int err; + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + dual_accel = dual_accel_detect(); has_buttons = acpi_has_method(handle, "VBDL"); has_switches = intel_vbtn_has_switches(handle, dual_accel); diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 5059d320edf8..5ab2215fdd7f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -24,7 +24,9 @@ #include <linux/intel_vsec.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/overflow.h> #include <linux/pci.h> +#include <linux/string.h> #include <linux/types.h> #define PMT_XA_START 0 @@ -42,7 +44,7 @@ enum vsec_device_state { }; struct vsec_priv { - struct intel_vsec_platform_info *info; + const struct intel_vsec_platform_info *info; struct device *suppliers[VSEC_FEATURE_COUNT]; struct oobmsm_plat_info plat_info; enum vsec_device_state state[VSEC_FEATURE_COUNT]; @@ -101,6 +103,12 @@ static void intel_vsec_remove_aux(void *data) auxiliary_device_uninit(data); } +static void intel_vsec_dev_free(struct intel_vsec_device *intel_vsec_dev) +{ + kfree(intel_vsec_dev->acpi_disc); + kfree(intel_vsec_dev); +} + static void intel_vsec_dev_release(struct device *dev) { struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(dev); @@ -109,8 +117,7 @@ static void intel_vsec_dev_release(struct device *dev) ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id); - kfree(intel_vsec_dev->resource); - kfree(intel_vsec_dev); + intel_vsec_dev_free(intel_vsec_dev); } static const struct vsec_feature_dependency * @@ -158,18 +165,23 @@ static bool vsec_driver_present(int cap_id) */ static const struct pci_device_id intel_vsec_pci_ids[]; -static int intel_vsec_link_devices(struct pci_dev *pdev, struct device *dev, +static int intel_vsec_link_devices(struct device *parent, struct device *dev, int consumer_id) { const struct vsec_feature_dependency *deps; enum vsec_device_state *state; struct device **suppliers; struct vsec_priv *priv; + struct pci_dev *pdev; int supplier_id; if (!consumer_id) return 0; + if (!dev_is_pci(parent)) + return 0; + + pdev = to_pci_dev(parent); if (!pci_match_id(intel_vsec_pci_ids, pdev)) return 0; @@ -204,29 +216,29 @@ static int intel_vsec_link_devices(struct pci_dev *pdev, struct device *dev, return 0; } -int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent, +int intel_vsec_add_aux(struct device *parent, struct intel_vsec_device *intel_vsec_dev, const char *name) { struct auxiliary_device *auxdev = &intel_vsec_dev->auxdev; int ret, id; - if (!parent) + if (!parent) { + intel_vsec_dev_free(intel_vsec_dev); return -EINVAL; + } ret = xa_alloc(&auxdev_array, &intel_vsec_dev->id, intel_vsec_dev, PMT_XA_LIMIT, GFP_KERNEL); if (ret < 0) { - kfree(intel_vsec_dev->resource); - kfree(intel_vsec_dev); + intel_vsec_dev_free(intel_vsec_dev); return ret; } id = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL); if (id < 0) { xa_erase(&auxdev_array, intel_vsec_dev->id); - kfree(intel_vsec_dev->resource); - kfree(intel_vsec_dev); + intel_vsec_dev_free(intel_vsec_dev); return id; } @@ -252,7 +264,7 @@ int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent, if (ret) goto cleanup_aux; - ret = intel_vsec_link_devices(pdev, &auxdev->dev, intel_vsec_dev->cap_id); + ret = intel_vsec_link_devices(parent, &auxdev->dev, intel_vsec_dev->cap_id); if (ret) goto cleanup_aux; @@ -269,58 +281,58 @@ cleanup_aux: } EXPORT_SYMBOL_NS_GPL(intel_vsec_add_aux, "INTEL_VSEC"); -static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *header, - struct intel_vsec_platform_info *info, - unsigned long cap_id) +static int intel_vsec_add_dev(struct device *dev, struct intel_vsec_header *header, + const struct intel_vsec_platform_info *info, + unsigned long cap_id, u64 base_addr) { struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL; - struct resource __free(kfree) *res = NULL; + struct resource *res; struct resource *tmp; struct device *parent; unsigned long quirks = info->quirks; - u64 base_addr; int i; if (info->parent) parent = info->parent; else - parent = &pdev->dev; + parent = dev; if (!intel_vsec_supported(header->id, info->caps)) return -EINVAL; if (!header->num_entries) { - dev_dbg(&pdev->dev, "Invalid 0 entry count for header id %d\n", header->id); + dev_dbg(dev, "Invalid 0 entry count for header id %d\n", header->id); return -EINVAL; } if (!header->entry_size) { - dev_dbg(&pdev->dev, "Invalid 0 entry size for header id %d\n", header->id); + dev_dbg(dev, "Invalid 0 entry size for header id %d\n", header->id); return -EINVAL; } - intel_vsec_dev = kzalloc_obj(*intel_vsec_dev); + intel_vsec_dev = kzalloc_flex(*intel_vsec_dev, resource, header->num_entries); if (!intel_vsec_dev) return -ENOMEM; - res = kzalloc_objs(*res, header->num_entries); - if (!res) - return -ENOMEM; + intel_vsec_dev->num_resources = header->num_entries; + res = intel_vsec_dev->resource; if (quirks & VSEC_QUIRK_TABLE_SHIFT) header->offset >>= TABLE_OFFSET_SHIFT; - if (info->base_addr) - base_addr = info->base_addr; - else - base_addr = pdev->resource[header->tbir].start; - /* * The DVSEC/VSEC contains the starting offset and count for a block of * discovery tables. Create a resource array of these tables to the * auxiliary device driver. */ for (i = 0, tmp = res; i < header->num_entries; i++, tmp++) { + /* + * Skip resource mapping check for ACPI-based discovery + * since those tables are read from _DSD, not MMIO. + */ + if (info->src == INTEL_VSEC_DISC_ACPI) + break; + tmp->start = base_addr + header->offset + i * (header->entry_size * sizeof(u32)); tmp->end = tmp->start + (header->entry_size * sizeof(u32)) - 1; tmp->flags = IORESOURCE_MEM; @@ -332,13 +344,24 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he release_mem_region(tmp->start, resource_size(tmp)); } - intel_vsec_dev->pcidev = pdev; - intel_vsec_dev->resource = no_free_ptr(res); - intel_vsec_dev->num_resources = header->num_entries; + intel_vsec_dev->dev = dev; intel_vsec_dev->quirks = info->quirks; intel_vsec_dev->base_addr = info->base_addr; intel_vsec_dev->priv_data = info->priv_data; intel_vsec_dev->cap_id = cap_id; + intel_vsec_dev->src = info->src; + + if (info->src == INTEL_VSEC_DISC_ACPI) { + size_t bytes; + + if (check_mul_overflow(intel_vsec_dev->num_resources, + sizeof(*info->acpi_disc), &bytes)) + return -EOVERFLOW; + + intel_vsec_dev->acpi_disc = kmemdup(info->acpi_disc, bytes, GFP_KERNEL); + if (!intel_vsec_dev->acpi_disc) + return -ENOMEM; + } if (header->id == VSEC_ID_SDSI) intel_vsec_dev->ida = &intel_vsec_sdsi_ida; @@ -349,7 +372,7 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he * Pass the ownership of intel_vsec_dev and resource within it to * intel_vsec_add_aux() */ - return intel_vsec_add_aux(pdev, parent, no_free_ptr(intel_vsec_dev), + return intel_vsec_add_aux(parent, no_free_ptr(intel_vsec_dev), intel_vsec_name(header->id)); } @@ -410,12 +433,14 @@ static int get_cap_id(u32 header_id, unsigned long *cap_id) return 0; } -static int intel_vsec_register_device(struct pci_dev *pdev, +static int intel_vsec_register_device(struct device *dev, struct intel_vsec_header *header, - struct intel_vsec_platform_info *info) + const struct intel_vsec_platform_info *info, + u64 base_addr) { const struct vsec_feature_dependency *consumer_deps; struct vsec_priv *priv; + struct pci_dev *pdev; unsigned long cap_id; int ret; @@ -427,8 +452,12 @@ static int intel_vsec_register_device(struct pci_dev *pdev, * Only track dependencies for devices probed by the VSEC driver. * For others using the exported APIs, add the device directly. */ + if (!dev_is_pci(dev)) + return intel_vsec_add_dev(dev, header, info, cap_id, base_addr); + + pdev = to_pci_dev(dev); if (!pci_match_id(intel_vsec_pci_ids, pdev)) - return intel_vsec_add_dev(pdev, header, info, cap_id); + return intel_vsec_add_dev(dev, header, info, cap_id, base_addr); priv = pci_get_drvdata(pdev); if (priv->state[cap_id] == STATE_REGISTERED || @@ -444,7 +473,7 @@ static int intel_vsec_register_device(struct pci_dev *pdev, consumer_deps = get_consumer_dependencies(priv, cap_id); if (!consumer_deps || suppliers_ready(priv, consumer_deps, cap_id)) { - ret = intel_vsec_add_dev(pdev, header, info, cap_id); + ret = intel_vsec_add_dev(dev, header, info, cap_id, base_addr); if (ret) priv->state[cap_id] = STATE_SKIP; else @@ -456,24 +485,38 @@ static int intel_vsec_register_device(struct pci_dev *pdev, return -EAGAIN; } -static bool intel_vsec_walk_header(struct pci_dev *pdev, - struct intel_vsec_platform_info *info) +static int intel_vsec_walk_header(struct device *dev, + const struct intel_vsec_platform_info *info) { struct intel_vsec_header **header = info->headers; - bool have_devices = false; + u64 base_addr; int ret; for ( ; *header; header++) { - ret = intel_vsec_register_device(pdev, *header, info); - if (!ret) - have_devices = true; + if (info->base_addr) { + base_addr = info->base_addr; + } else { + struct pci_dev *pdev; + + if (!dev_is_pci(dev)) { + dev_err(dev, "non-PCI device without a base address\n"); + return -EINVAL; + } + + pdev = to_pci_dev(dev); + base_addr = pci_resource_start(pdev, (*header)->tbir); + } + + ret = intel_vsec_register_device(dev, *header, info, base_addr); + if (ret) + return ret; } - return have_devices; + return 0; } static bool intel_vsec_walk_dvsec(struct pci_dev *pdev, - struct intel_vsec_platform_info *info) + const struct intel_vsec_platform_info *info) { bool have_devices = false; int pos = 0; @@ -512,7 +555,8 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev, pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER2, &hdr); header.id = PCI_DVSEC_HEADER2_ID(hdr); - ret = intel_vsec_register_device(pdev, &header, info); + ret = intel_vsec_register_device(&pdev->dev, &header, info, + pci_resource_start(pdev, header.tbir)); if (ret) continue; @@ -523,7 +567,7 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev, } static bool intel_vsec_walk_vsec(struct pci_dev *pdev, - struct intel_vsec_platform_info *info) + const struct intel_vsec_platform_info *info) { bool have_devices = false; int pos = 0; @@ -557,7 +601,8 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev, header.tbir = INTEL_DVSEC_TABLE_BAR(table); header.offset = INTEL_DVSEC_TABLE_OFFSET(table); - ret = intel_vsec_register_device(pdev, &header, info); + ret = intel_vsec_register_device(&pdev->dev, &header, info, + pci_resource_start(pdev, header.tbir)); if (ret) continue; @@ -567,21 +612,18 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev, return have_devices; } -int intel_vsec_register(struct pci_dev *pdev, - struct intel_vsec_platform_info *info) +int intel_vsec_register(struct device *dev, + const struct intel_vsec_platform_info *info) { - if (!pdev || !info || !info->headers) + if (!dev || !info || !info->headers) return -EINVAL; - if (!intel_vsec_walk_header(pdev, info)) - return -ENODEV; - else - return 0; + return intel_vsec_walk_header(dev, info); } EXPORT_SYMBOL_NS_GPL(intel_vsec_register, "INTEL_VSEC"); static bool intel_vsec_get_features(struct pci_dev *pdev, - struct intel_vsec_platform_info *info) + const struct intel_vsec_platform_info *info) { bool found = false; @@ -599,7 +641,7 @@ static bool intel_vsec_get_features(struct pci_dev *pdev, found = true; if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) && - intel_vsec_walk_header(pdev, info)) + intel_vsec_walk_header(&pdev->dev, info)) found = true; return found; @@ -623,29 +665,13 @@ static void intel_vsec_skip_missing_dependencies(struct pci_dev *pdev) } } -static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +static int intel_vsec_pci_init(struct pci_dev *pdev) { - struct intel_vsec_platform_info *info; - struct vsec_priv *priv; - int num_caps, ret; + struct vsec_priv *priv = pci_get_drvdata(pdev); + const struct intel_vsec_platform_info *info = priv->info; int run_once = 0; bool found_any = false; - - ret = pcim_enable_device(pdev); - if (ret) - return ret; - - pci_save_state(pdev); - info = (struct intel_vsec_platform_info *)id->driver_data; - if (!info) - return -EINVAL; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->info = info; - pci_set_drvdata(pdev, priv); + int num_caps; num_caps = hweight_long(info->caps); while (num_caps--) { @@ -666,12 +692,40 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id return 0; } +static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + const struct intel_vsec_platform_info *info; + struct vsec_priv *priv; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + pci_save_state(pdev); + info = (const struct intel_vsec_platform_info *)id->driver_data; + if (!info) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = info; + pci_set_drvdata(pdev, priv); + + return intel_vsec_pci_init(pdev); +} + int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, struct intel_vsec_device *vsec_dev) { struct vsec_priv *priv; - priv = pci_get_drvdata(vsec_dev->pcidev); + if (!dev_is_pci(vsec_dev->dev)) + return -ENODEV; + + priv = pci_get_drvdata(to_pci_dev(vsec_dev->dev)); if (!priv) return -EINVAL; @@ -803,7 +857,6 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) { struct intel_vsec_device *intel_vsec_dev; pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT; - const struct pci_device_id *pci_dev_id; unsigned long index; dev_info(&pdev->dev, "Resetting PCI slot\n"); @@ -819,15 +872,13 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) xa_for_each(&auxdev_array, index, intel_vsec_dev) { /* check if pdev doesn't match */ - if (pdev != intel_vsec_dev->pcidev) + if (&pdev->dev != intel_vsec_dev->dev) continue; devm_release_action(&pdev->dev, intel_vsec_remove_aux, &intel_vsec_dev->auxdev); } - pci_disable_device(pdev); pci_restore_state(pdev); - pci_dev_id = pci_match_id(intel_vsec_pci_ids, pdev); - intel_vsec_pci_probe(pdev, pci_dev_id); + intel_vsec_pci_init(pdev); out: return status; diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index 98846e88d3d0..0153dd57838e 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -46,15 +46,18 @@ * provided by the Intel VSEC driver. */ +#include <linux/align.h> #include <linux/auxiliary_bus.h> #include <linux/bitfield.h> #include <linux/debugfs.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/intel_tpmi.h> #include <linux/intel_vsec.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/module.h> +#include <linux/notifier.h> #include <linux/pci.h> #include <linux/security.h> #include <linux/sizes.h> @@ -187,6 +190,20 @@ struct tpmi_feature_state { /* Used during auxbus device creation */ static DEFINE_IDA(intel_vsec_tpmi_ida); +static BLOCKING_NOTIFIER_HEAD(tpmi_notify_list); + +int tpmi_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&tpmi_notify_list, nb); +} +EXPORT_SYMBOL_NS_GPL(tpmi_register_notifier, "INTEL_TPMI"); + +int tpmi_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&tpmi_notify_list, nb); +} +EXPORT_SYMBOL_NS_GPL(tpmi_unregister_notifier, "INTEL_TPMI"); + struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev) { struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); @@ -457,7 +474,7 @@ static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t l struct seq_file *m = file->private_data; struct intel_tpmi_pm_feature *pfs = m->private; u32 addr, value, punit, size; - u32 num_elems, *array; + u32 num_elems; void __iomem *mem; int ret; @@ -465,51 +482,39 @@ static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t l if (!size) return -EIO; + u32 *array __free(kfree) = NULL; ret = parse_int_array_user(userbuf, len, (int **)&array); if (ret < 0) return ret; num_elems = *array; - if (num_elems != 3) { - ret = -EINVAL; - goto exit_write; - } + if (num_elems != 3) + return -EINVAL; punit = array[1]; addr = array[2]; value = array[3]; - if (punit >= pfs->pfs_header.num_entries) { - ret = -EINVAL; - goto exit_write; - } + if (!IS_ALIGNED(addr, sizeof(u32))) + return -EINVAL; - if (addr >= size) { - ret = -EINVAL; - goto exit_write; - } + if (punit >= pfs->pfs_header.num_entries) + return -EINVAL; - mutex_lock(&tpmi_dev_lock); + if (addr >= size) + return -EINVAL; + + guard(mutex)(&tpmi_dev_lock); mem = ioremap(pfs->vsec_offset + punit * size, size); - if (!mem) { - ret = -ENOMEM; - goto unlock_mem_write; - } + if (!mem) + return -ENOMEM; writel(value, mem + addr); iounmap(mem); - ret = len; - -unlock_mem_write: - mutex_unlock(&tpmi_dev_lock); - -exit_write: - kfree(array); - - return ret; + return len; } static int mem_write_show(struct seq_file *s, void *unused) @@ -530,7 +535,7 @@ static const struct file_operations mem_write_ops = { .release = single_release, }; -#define tpmi_to_dev(info) (&info->vsec_dev->pcidev->dev) +#define tpmi_to_dev(info) ((info)->vsec_dev->dev) static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) { @@ -622,15 +627,12 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, if (!name) return -EOPNOTSUPP; - res = kzalloc_objs(*res, pfs->pfs_header.num_entries); - if (!res) + feature_vsec_dev = kzalloc_flex(*feature_vsec_dev, resource, pfs->pfs_header.num_entries); + if (!feature_vsec_dev) return -ENOMEM; - feature_vsec_dev = kzalloc_obj(*feature_vsec_dev); - if (!feature_vsec_dev) { - kfree(res); - return -ENOMEM; - } + feature_vsec_dev->num_resources = pfs->pfs_header.num_entries; + res = feature_vsec_dev->resource; snprintf(feature_id_name, sizeof(feature_id_name), "tpmi-%s", name); @@ -642,9 +644,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, tmp->flags = IORESOURCE_MEM; } - feature_vsec_dev->pcidev = vsec_dev->pcidev; - feature_vsec_dev->resource = res; - feature_vsec_dev->num_resources = pfs->pfs_header.num_entries; + feature_vsec_dev->dev = vsec_dev->dev; feature_vsec_dev->priv_data = &tpmi_info->plat_info; feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info); feature_vsec_dev->ida = &intel_vsec_tpmi_ida; @@ -655,7 +655,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, * feature_vsec_dev and res memory are also freed as part of * device deletion. */ - return intel_vsec_add_aux(vsec_dev->pcidev, &vsec_dev->auxdev.dev, + return intel_vsec_add_aux(&vsec_dev->auxdev.dev, feature_vsec_dev, feature_id_name); } @@ -742,7 +742,7 @@ static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, i static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) { struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); - struct pci_dev *pci_dev = vsec_dev->pcidev; + struct pci_dev *pci_dev = to_pci_dev(vsec_dev->dev); struct intel_tpmi_info *tpmi_info; u64 pfs_start = 0; int ret, i; @@ -813,10 +813,6 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) auxiliary_set_drvdata(auxdev, tpmi_info); - ret = tpmi_create_devices(tpmi_info); - if (ret) - return ret; - /* * Allow debugfs when security policy allows. Everything this debugfs * interface provides, can also be done via /dev/mem access. If @@ -826,6 +822,14 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO)) tpmi_dbgfs_register(tpmi_info); + ret = tpmi_create_devices(tpmi_info); + if (ret) { + debugfs_remove_recursive(tpmi_info->dbgfs_dir); + return ret; + } + + blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, auxdev); + return 0; } @@ -839,6 +843,8 @@ static void tpmi_remove(struct auxiliary_device *auxdev) { struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev); + blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, auxdev); + debugfs_remove_recursive(tpmi_info->dbgfs_dir); } diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c index 3716ccaaed6a..62c9c7f1842b 100644 --- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c +++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c @@ -28,15 +28,10 @@ static int get_fwu_request(struct device *dev, u32 *out) __le32 *result; int ret; - ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer); + ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer, sizeof(*result)); if (ret < 0) return ret; - if (buffer.length < sizeof(*result)) { - kfree(buffer.data); - return -ENODATA; - } - result = buffer.data; *out = le32_to_cpu(*result); kfree(result); diff --git a/drivers/platform/x86/intel/wmi/thunderbolt.c b/drivers/platform/x86/intel/wmi/thunderbolt.c index 47017f2d7597..9b1920d61674 100644 --- a/drivers/platform/x86/intel/wmi/thunderbolt.c +++ b/drivers/platform/x86/intel/wmi/thunderbolt.c @@ -34,7 +34,7 @@ static ssize_t force_power_store(struct device *dev, if (mode > 1) return -EINVAL; - ret = wmidev_invoke_method(to_wmi_device(dev), 0, 1, &buffer, NULL); + ret = wmidev_invoke_procedure(to_wmi_device(dev), 0, 1, &buffer); if (ret < 0) return ret; diff --git a/drivers/platform/x86/intel_scu_pltdrv.c b/drivers/platform/x86/intel_scu_pltdrv.c index 0892362acd7b..d5ab62cbf5cc 100644 --- a/drivers/platform/x86/intel_scu_pltdrv.c +++ b/drivers/platform/x86/intel_scu_pltdrv.c @@ -11,7 +11,6 @@ #include <linux/err.h> #include <linux/errno.h> #include <linux/ioport.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index f885127b007f..516594993073 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA To compile this driver as a module, choose M here: the module will be called lenovo-wmi-camera. +config LENOVO_YB9_KBDOCK + tristate "Lenovo Yoga Book 9 keyboard dock detection" + depends on ACPI_WMI + depends on DMI + depends on INPUT + help + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to + either screen; this driver reports SW_TABLET_MODE input events based + on the attachment state and exposes the raw position in sysfs. + + To compile this driver as a module, choose M here: the module will be + called lenovo-yb9-kbdock. + config LENOVO_YMC tristate "Lenovo Yoga Tablet Mode Control" depends on ACPI_WMI @@ -236,6 +250,7 @@ config YT2_1380 config LENOVO_WMI_CAPDATA tristate depends on ACPI_WMI + depends on LENOVO_WMI_HELPERS config LENOVO_WMI_EVENTS tristate @@ -252,7 +267,6 @@ config LENOVO_WMI_GAMEZONE select ACPI_PLATFORM_PROFILE select LENOVO_WMI_EVENTS select LENOVO_WMI_HELPERS - select LENOVO_WMI_TUNING help Say Y here if you have a WMI aware Lenovo Legion device and would like to use the platform-profile firmware interface to manage power usage. @@ -263,6 +277,7 @@ config LENOVO_WMI_GAMEZONE config LENOVO_WMI_TUNING tristate "Lenovo Other Mode WMI Driver" depends on ACPI_WMI + depends on ACPI_BATTERY select HWMON select FW_ATTR_CLASS select LENOVO_WMI_CAPDATA diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 91a9370f11b3..12816d8d5e24 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index ae1ebb071fab..8213524504ee 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -2340,6 +2340,7 @@ static struct wmi_driver ideapad_wmi_driver = { .name = "ideapad_wmi", }, .id_table = ideapad_wmi_ids, + .min_event_size = sizeof(u32), .probe = ideapad_wmi_probe, .notify = ideapad_wmi_notify, }; @@ -2563,8 +2564,8 @@ module_init(ideapad_laptop_init) static void __exit ideapad_laptop_exit(void) { - ideapad_wmi_driver_unregister(); platform_driver_unregister(&ideapad_acpi_driver); + ideapad_wmi_driver_unregister(); } module_exit(ideapad_laptop_exit) diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index e215e86e3db7..a0e3fa766e37 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -438,14 +438,13 @@ static ssize_t current_password_store(struct kobject *kobj, struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); size_t pwdlen; - pwdlen = strlen(buf); + /* Strip newline; setting password won't work if one is present. */ + pwdlen = strchrnul(buf, '\n') - buf; /* pwdlen == 0 is allowed to clear the password */ if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) return -EINVAL; - strscpy(setting->password, buf, setting->maxlen); - /* Strip out CR if one is present, setting password won't work if it is present */ - strreplace(setting->password, '\n', '\0'); + strscpy(setting->password, buf, pwdlen + 1); return count; } @@ -745,6 +744,8 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att return -EOPNOTSUPP; for (i = 0; i < ARRAY_SIZE(thumbtypes); i++) { + ssize_t ret; + if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) { /* Format: 'SVC | SMC, Thumbtype' */ wmistr = kasprintf(GFP_KERNEL, "%s,%s", @@ -756,8 +757,12 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att } if (!wmistr) return -ENOMEM; - count += cert_thumbprint(buf, wmistr, count); + + ret = cert_thumbprint(buf, wmistr, count); kfree(wmistr); + if (ret < 0) + return ret; + count = ret; } return count; @@ -1456,6 +1461,10 @@ static void tlmi_release_attr(void) /* Free up any saved signatures */ kfree(tlmi_priv.pwd_admin->signature); kfree(tlmi_priv.pwd_admin->save_signature); + if (tlmi_priv.pwd_system) { + kfree(tlmi_priv.pwd_system->signature); + kfree(tlmi_priv.pwd_system->save_signature); + } /* Authentication structures */ list_for_each_entry_safe(pos, n, &tlmi_priv.authentication_kset->list, entry) diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 8982d92dfd97..1661f070c571 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -38,6 +38,8 @@ #include <linux/backlight.h> #include <linux/bitfield.h> #include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/debugfs.h> #include <linux/delay.h> #include <linux/dmi.h> #include <linux/freezer.h> @@ -66,6 +68,7 @@ #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/string.h> +#include <linux/string_choices.h> #include <linux/string_helpers.h> #include <linux/sysfs.h> #include <linux/types.h> @@ -185,6 +188,7 @@ enum tpacpi_hkey_event_t { TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ + TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */ TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ @@ -299,7 +303,6 @@ struct ibm_struct; struct tp_acpi_drv_struct { const struct acpi_device_id *hid; - struct acpi_driver *driver; void (*notify) (struct ibm_struct *, u32); acpi_handle *handle; @@ -309,6 +312,7 @@ struct tp_acpi_drv_struct { struct ibm_struct { char *name; + acpi_device_class device_class; int (*read) (struct seq_file *); int (*write) (char *); @@ -322,7 +326,6 @@ struct ibm_struct { struct tp_acpi_drv_struct *acpi; struct { - u8 acpi_driver_registered:1; u8 acpi_notify_installed:1; u8 proc_created:1; u8 init_called:1; @@ -374,7 +377,9 @@ static struct { u32 hotkey_poll_active:1; u32 has_adaptive_kbd:1; u32 kbd_lang:1; - u32 trackpoint_doubletap:1; + u32 trackpoint_doubletap_enable:1; + u32 usbc_security_supported:1; + bool usbc_security_enabled; struct quirk_entry *quirks; } tp_features; @@ -769,7 +774,7 @@ static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, if (!strcmp(context, "video")) { struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) + if (!acpi_dev_is_video_device(dev)) return AE_OK; } @@ -832,16 +837,15 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) vdbg_printk(TPACPI_DBG_INIT, "setting up ACPI notify for %s\n", ibm->name); - ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle); + ibm->acpi->device = acpi_get_acpi_dev(*ibm->acpi->handle); if (!ibm->acpi->device) { - pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name); + pr_err("acpi_get_acpi_dev(%s) failed\n", ibm->name); return -ENODEV; } ibm->acpi->device->driver_data = ibm; - scnprintf(acpi_device_class(ibm->acpi->device), - sizeof(acpi_device_class(ibm->acpi->device)), - "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); + scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s", + TPACPI_ACPI_EVENT_PREFIX, ibm->name); status = acpi_install_notify_handler(*ibm->acpi->handle, ibm->acpi->type, dispatch_acpi_notify, ibm); @@ -859,44 +863,6 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) return 0; } -static int __init tpacpi_device_add(struct acpi_device *device) -{ - return 0; -} - -static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) -{ - int rc; - - dbg_printk(TPACPI_DBG_INIT, - "registering %s as an ACPI driver\n", ibm->name); - - BUG_ON(!ibm->acpi); - - ibm->acpi->driver = kzalloc_obj(struct acpi_driver); - if (!ibm->acpi->driver) { - pr_err("failed to allocate memory for ibm->acpi->driver\n"); - return -ENOMEM; - } - - sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_NAME, ibm->name); - ibm->acpi->driver->ids = ibm->acpi->hid; - - ibm->acpi->driver->ops.add = &tpacpi_device_add; - - rc = acpi_bus_register_driver(ibm->acpi->driver); - if (rc < 0) { - pr_err("acpi_bus_register_driver(%s) failed: %d\n", - ibm->name, rc); - kfree(ibm->acpi->driver); - ibm->acpi->driver = NULL; - } else if (!rc) - ibm->flags.acpi_driver_registered = 1; - - return rc; -} - - /**************************************************************************** **************************************************************************** * @@ -1315,7 +1281,7 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id, static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m) { if (id >= TPACPI_RFK_SW_MAX) - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); else { int status; @@ -1330,7 +1296,7 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file * } seq_printf(m, "status:\t\t%s\n", str_enabled_disabled(status == TPACPI_RFK_RADIO_ON)); - seq_printf(m, "commands:\tenable, disable\n"); + seq_puts(m, "commands:\tenable, disable\n"); } return 0; @@ -2509,7 +2475,7 @@ static int hotkey_kthread(void *data) bool was_frozen; if (tpacpi_lifecycle == TPACPI_LIFE_EXITING) - goto exit; + return 0; set_freezable(); @@ -2566,7 +2532,6 @@ static int hotkey_kthread(void *data) si ^= 1; } -exit: return 0; } @@ -3019,6 +2984,31 @@ static const struct attribute_group adaptive_kbd_attr_group = { .attrs = adaptive_kbd_attributes, }; +/* sysfs doubletap enable --------------------------------------------- */ +static ssize_t doubletap_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", tp_features.trackpoint_doubletap_enable); +} + +static ssize_t doubletap_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + bool enable; + int err; + + err = kstrtobool(buf, &enable); + if (err) + return err; + + tp_features.trackpoint_doubletap_enable = enable; + return count; +} + +static DEVICE_ATTR_RW(doubletap_enable); + /* --------------------------------------------------------------------- */ static struct attribute *hotkey_attributes[] = { @@ -3033,6 +3023,7 @@ static struct attribute *hotkey_attributes[] = { &dev_attr_hotkey_recommended_mask.attr, &dev_attr_hotkey_tablet_mode.attr, &dev_attr_hotkey_radio_sw.attr, + &dev_attr_doubletap_enable.attr, #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL &dev_attr_hotkey_source_mask.attr, &dev_attr_hotkey_poll_freq.attr, @@ -3558,8 +3549,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) hotkey_poll_setup_safe(true); - /* Enable doubletap by default */ - tp_features.trackpoint_doubletap = 1; + /* Enable TrackPoint doubletap event reporting by default. */ + tp_features.trackpoint_doubletap_enable = 1; return 0; } @@ -3864,9 +3855,9 @@ static bool hotkey_notify_8xxx(const u32 hkey, bool *send_acpi_ev) { switch (hkey) { case TP_HKEY_EV_TRACK_DOUBLETAP: - if (tp_features.trackpoint_doubletap) - tpacpi_input_send_key(hkey, send_acpi_ev); - + /* Only send event if doubletap is enabled */ + if (!tp_features.trackpoint_doubletap_enable) + *send_acpi_ev = false; return true; default: return false; @@ -3883,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) pr_err("unknown HKEY notification event %d\n", event); /* forward it to userspace, maybe it knows how to handle it */ acpi_bus_generate_netlink_event( - ibm->acpi->device->pnp.device_class, + ibm->device_class, dev_name(&ibm->acpi->device->dev), event, 0); return; @@ -3963,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) /* netlink events */ if (send_acpi_ev) { acpi_bus_generate_netlink_event( - ibm->acpi->device->pnp.device_class, + ibm->device_class, dev_name(&ibm->acpi->device->dev), event, hkey); } @@ -4017,7 +4008,7 @@ static int hotkey_read(struct seq_file *m) int res, status; if (!tp_features.hotkey) { - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); return 0; } @@ -4033,10 +4024,10 @@ static int hotkey_read(struct seq_file *m) seq_printf(m, "status:\t\t%s\n", str_enabled_disabled(status & BIT(0))); if (hotkey_all_mask) { seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask); - seq_printf(m, "commands:\tenable, disable, reset, <mask>\n"); + seq_puts(m, "commands:\tenable, disable, reset, <mask>\n"); } else { - seq_printf(m, "mask:\t\tnot supported\n"); - seq_printf(m, "commands:\tenable, disable, reset\n"); + seq_puts(m, "mask:\t\tnot supported\n"); + seq_puts(m, "commands:\tenable, disable, reset\n"); } return 0; @@ -4933,7 +4924,7 @@ static int video_read(struct seq_file *m) int status, autosw; if (video_supported == TPACPI_VIDEO_NONE) { - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); return 0; } @@ -4949,18 +4940,18 @@ static int video_read(struct seq_file *m) if (autosw < 0) return autosw; - seq_printf(m, "status:\t\tsupported\n"); + seq_puts(m, "status:\t\tsupported\n"); seq_printf(m, "lcd:\t\t%s\n", str_enabled_disabled(status & BIT(0))); seq_printf(m, "crt:\t\t%s\n", str_enabled_disabled(status & BIT(1))); if (video_supported == TPACPI_VIDEO_NEW) seq_printf(m, "dvi:\t\t%s\n", str_enabled_disabled(status & BIT(3))); seq_printf(m, "auto:\t\t%s\n", str_enabled_disabled(autosw & BIT(0))); - seq_printf(m, "commands:\tlcd_enable, lcd_disable\n"); - seq_printf(m, "commands:\tcrt_enable, crt_disable\n"); + seq_puts(m, "commands:\tlcd_enable, lcd_disable\n"); + seq_puts(m, "commands:\tcrt_enable, crt_disable\n"); if (video_supported == TPACPI_VIDEO_NEW) - seq_printf(m, "commands:\tdvi_enable, dvi_disable\n"); - seq_printf(m, "commands:\tauto_enable, auto_disable\n"); - seq_printf(m, "commands:\tvideo_switch, expand_toggle\n"); + seq_puts(m, "commands:\tdvi_enable, dvi_disable\n"); + seq_puts(m, "commands:\tauto_enable, auto_disable\n"); + seq_puts(m, "commands:\tvideo_switch, expand_toggle\n"); return 0; } @@ -5204,14 +5195,14 @@ static int kbdlight_read(struct seq_file *m) int level; if (!tp_features.kbdlight) { - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); } else { level = kbdlight_get_level(); if (level < 0) seq_printf(m, "status:\t\terror %d\n", level); else seq_printf(m, "status:\t\t%d\n", level); - seq_printf(m, "commands:\t0, 1, 2\n"); + seq_puts(m, "commands:\t0, 1, 2\n"); } return 0; @@ -5378,16 +5369,16 @@ static int light_read(struct seq_file *m) int status; if (!tp_features.light) { - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); } else if (!tp_features.light_status) { - seq_printf(m, "status:\t\tunknown\n"); - seq_printf(m, "commands:\ton, off\n"); + seq_puts(m, "status:\t\tunknown\n"); + seq_puts(m, "commands:\ton, off\n"); } else { status = light_get_status(); if (status < 0) return status; seq_printf(m, "status:\t\t%s\n", str_on_off(status & BIT(0))); - seq_printf(m, "commands:\ton, off\n"); + seq_puts(m, "commands:\ton, off\n"); } return 0; @@ -5477,10 +5468,10 @@ static int cmos_read(struct seq_file *m) /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, R30, R31, T20-22, X20-21 */ if (!cmos_handle) - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); else { - seq_printf(m, "status:\t\tsupported\n"); - seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n"); + seq_puts(m, "status:\t\tsupported\n"); + seq_puts(m, "commands:\t<cmd> (<cmd> is 0-21)\n"); } return 0; @@ -5846,10 +5837,10 @@ static int __init led_init(struct ibm_init_struct *iibm) static int led_read(struct seq_file *m) { if (!led_supported) { - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); return 0; } - seq_printf(m, "status:\t\tsupported\n"); + seq_puts(m, "status:\t\tsupported\n"); if (led_supported == TPACPI_LED_570) { /* 570 */ @@ -5862,7 +5853,7 @@ static int led_read(struct seq_file *m) } } - seq_printf(m, "commands:\t<led> on, <led> off, <led> blink (<led> is 0-15)\n"); + seq_puts(m, "commands:\t<led> on, <led> off, <led> blink (<led> is 0-15)\n"); return 0; } @@ -5946,10 +5937,10 @@ static int __init beep_init(struct ibm_init_struct *iibm) static int beep_read(struct seq_file *m) { if (!beep_handle) - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); else { - seq_printf(m, "status:\t\tsupported\n"); - seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n"); + seq_puts(m, "status:\t\tsupported\n"); + seq_puts(m, "commands:\t<cmd> (<cmd> is 0-17)\n"); } return 0; @@ -6398,14 +6389,14 @@ static int thermal_read(struct seq_file *m) if (unlikely(n < 0)) return n; - seq_printf(m, "temperatures:\t"); + seq_puts(m, "temperatures:\t"); if (n > 0) { for (i = 0; i < (n - 1); i++) seq_printf(m, "%d ", t.temp[i] / 1000); seq_printf(m, "%d\n", t.temp[i] / 1000); } else - seq_printf(m, "not supported\n"); + seq_puts(m, "not supported\n"); return 0; } @@ -6918,10 +6909,10 @@ static int brightness_read(struct seq_file *m) level = brightness_get(NULL); if (level < 0) { - seq_printf(m, "level:\t\tunreadable\n"); + seq_puts(m, "level:\t\tunreadable\n"); } else { seq_printf(m, "level:\t\t%d\n", level); - seq_printf(m, "commands:\tup, down\n"); + seq_puts(m, "commands:\tup, down\n"); seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n", bright_maxlvl); } @@ -7637,10 +7628,10 @@ static int volume_read(struct seq_file *m) u8 status; if (volume_get_status(&status) < 0) { - seq_printf(m, "level:\t\tunreadable\n"); + seq_puts(m, "level:\t\tunreadable\n"); } else { if (tp_features.mixer_no_level_control) - seq_printf(m, "level:\t\tunsupported\n"); + seq_puts(m, "level:\t\tunsupported\n"); else seq_printf(m, "level:\t\t%d\n", status & TP_EC_AUDIO_LVL_MSK); @@ -7648,9 +7639,9 @@ static int volume_read(struct seq_file *m) seq_printf(m, "mute:\t\t%s\n", str_on_off(status & BIT(TP_EC_AUDIO_MUTESW))); if (volume_control_allowed) { - seq_printf(m, "commands:\tunmute, mute\n"); + seq_puts(m, "commands:\tunmute, mute\n"); if (!tp_features.mixer_no_level_control) { - seq_printf(m, "commands:\tup, down\n"); + seq_puts(m, "commands:\tup, down\n"); seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n", TP_EC_VOLUME_MAX); } @@ -8854,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */ TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ + TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ @@ -9156,9 +9148,9 @@ static int fan_read(struct seq_file *m) } else if (fan_status_access_mode == TPACPI_FAN_RD_TPEC) { if (status & TP_EC_FAN_FULLSPEED) /* Disengaged mode takes precedence */ - seq_printf(m, "level:\t\tdisengaged\n"); + seq_puts(m, "level:\t\tdisengaged\n"); else if (status & TP_EC_FAN_AUTO) - seq_printf(m, "level:\t\tauto\n"); + seq_puts(m, "level:\t\tauto\n"); else seq_printf(m, "level:\t\t%d\n", status); } @@ -9166,19 +9158,19 @@ static int fan_read(struct seq_file *m) case TPACPI_FAN_NONE: default: - seq_printf(m, "status:\t\tnot supported\n"); + seq_puts(m, "status:\t\tnot supported\n"); } if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) { - seq_printf(m, "commands:\tlevel <level>"); + seq_puts(m, "commands:\tlevel <level>"); switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: - seq_printf(m, " (<level> is 0-7)\n"); + seq_puts(m, " (<level> is 0-7)\n"); break; default: - seq_printf(m, " (<level> is 0-7, auto, disengaged, full-speed)\n"); + seq_puts(m, " (<level> is 0-7, auto, disengaged, full-speed)\n"); break; } } @@ -9188,7 +9180,7 @@ static int fan_read(struct seq_file *m) "commands:\twatchdog <timeout> (<timeout> is 0 (off), 1-120 (seconds))\n"); if (fan_control_commands & TPACPI_FAN_CMD_SPEED) - seq_printf(m, "commands:\tspeed <speed> (<speed> is 0-65535)\n"); + seq_puts(m, "commands:\tspeed <speed> (<speed> is 0-65535)\n"); return 0; } @@ -9249,9 +9241,6 @@ static int fan_write_cmd_speed(const char *cmd, int *rc) { int speed; - /* TODO: - * Support speed <low> <medium> <high> ? */ - if (sscanf(cmd, "speed %d", &speed) != 1) return 0; @@ -11234,6 +11223,26 @@ static ssize_t hwdd_status_show(struct device *dev, return sysfs_emit(buf, "0\n"); } + +/* sysfs type-c damage detection raw data - accessed via debugfs*/ +static int hwdd_raw_show(struct seq_file *m, void *v) +{ + unsigned int damage_status; + int err; + + if (!ucdd_supported) + return -ENODEV; + + /* Get USB TYPE-C damage status */ + err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status); + if (err) + return err; + + seq_printf(m, "HWDD: 0x%x\n", damage_status); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(hwdd_raw); + static DEVICE_ATTR_RO(hwdd_status); static DEVICE_ATTR_RO(hwdd_detail); @@ -11282,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = { .name = "hwdd", }; +/************************************************************************* + * USB-C Security subdriver + * + * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored. + * It always returns: + * bit 16 - USB-C security capability present on this SKU or not + * bit 0 - USB-C Security state (enable or disable) + * + * Hotkey + * ------ + * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event. + * The driver reads back the new state and notifies the sysfs attribute. + */ + +/* USCS() return word bit layout */ +#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */ +#define USCS_STATUS_BIT BIT(0) /* current security state */ + +/* Protects USCS() ACPI method calls in usbc_security_query() */ +static DEFINE_MUTEX(usbc_security_mutex); + +/** + * usbc_security_query - read current USB-C security state via USCS() + * @enabled: out - true when security is ON (data connections blocked) + * + * Returns: + * 0 success, @enabled contains the current state + * -EIO ACPI evaluation failed + * -ENODEV capability bit absent; feature not present on this SKU* + */ +static int usbc_security_query(bool *enabled) +{ + int status; + + guard(mutex)(&usbc_security_mutex); + if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0)) + return -EIO; + + if (!(status & USCS_CAP_BIT)) { + pr_debug("USCS cap bit absent (raw=0x%x)\n", status); + return -ENODEV; + } + + *enabled = status & USCS_STATUS_BIT; + return 0; +} + +/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */ +static ssize_t usb_c_security_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", + str_enabled_disabled(tp_features.usbc_security_enabled)); +} + +static DEVICE_ATTR_RO(usb_c_security); + +static struct attribute *usbc_security_attributes[] = { + &dev_attr_usb_c_security.attr, + NULL, +}; + +static umode_t usbc_security_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + return tp_features.usbc_security_supported ? attr->mode : 0; +} + +static const struct attribute_group usbc_security_attr_group = { + .is_visible = usbc_security_attr_is_visible, + .attrs = usbc_security_attributes, +}; + +static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm) +{ + int err; + + if (!acpi_has_method(hkey_handle, "USCS")) + return -ENODEV; + + err = usbc_security_query(&tp_features.usbc_security_enabled); + if (err == -ENODEV) + return 0; + if (err) + return err; + + tp_features.usbc_security_supported = true; + return 0; +} + +/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */ +static bool tpacpi_usbc_security_hotkey(void) +{ + if (!tp_features.usbc_security_supported) + return false; + + if (usbc_security_query(&tp_features.usbc_security_enabled)) + return false; + + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security"); + return true; +} + +static struct ibm_struct usbc_security_driver_data = { + .name = "usbc_security", +}; + /* --------------------------------------------------------------------- */ static struct attribute *tpacpi_driver_attributes[] = { @@ -11342,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = { &dprc_attr_group, &auxmac_attr_group, &hwdd_attr_group, + &usbc_security_attr_group, NULL, }; @@ -11488,12 +11606,16 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) mutex_unlock(&tpacpi_inputdev_send_mutex); return true; case TP_HKEY_EV_DOUBLETAP_TOGGLE: - tp_features.trackpoint_doubletap = !tp_features.trackpoint_doubletap; + /* Toggle kernel-level doubletap event filtering */ + tp_features.trackpoint_doubletap_enable = + !tp_features.trackpoint_doubletap_enable; return true; case TP_HKEY_EV_PROFILE_TOGGLE: case TP_HKEY_EV_PROFILE_TOGGLE2: platform_profile_cycle(); return true; + case TP_HKEY_EV_USB_C_SECURITY: + return tpacpi_usbc_security_hotkey(); } return false; @@ -11519,6 +11641,20 @@ static const char * __init str_supported(int is_supported) } #endif /* CONFIG_THINKPAD_ACPI_DEBUG */ +static struct dentry *tpacpi_dbg; +static void tpacpi_debugfs_init(void) +{ + tpacpi_dbg = debugfs_create_dir("tpacpi", NULL); + + /* HWDD raw data */ + debugfs_create_file("hwdd-raw", 0444, tpacpi_dbg, NULL, &hwdd_raw_fops); +} + +static void tpacpi_debugfs_remove(void) +{ + debugfs_remove_recursive(tpacpi_dbg); +} + static void ibm_exit(struct ibm_struct *ibm) { dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name); @@ -11532,6 +11668,8 @@ static void ibm_exit(struct ibm_struct *ibm) acpi_remove_notify_handler(*ibm->acpi->handle, ibm->acpi->type, dispatch_acpi_notify); + ibm->acpi->device->driver_data = NULL; + acpi_dev_put(ibm->acpi->device); ibm->flags.acpi_notify_installed = 0; } @@ -11542,16 +11680,6 @@ static void ibm_exit(struct ibm_struct *ibm) ibm->flags.proc_created = 0; } - if (ibm->flags.acpi_driver_registered) { - dbg_printk(TPACPI_DBG_EXIT, - "%s: acpi_bus_unregister_driver\n", ibm->name); - BUG_ON(!ibm->acpi); - acpi_bus_unregister_driver(ibm->acpi->driver); - kfree(ibm->acpi->driver); - ibm->acpi->driver = NULL; - ibm->flags.acpi_driver_registered = 0; - } - if (ibm->flags.init_called && ibm->exit) { ibm->exit(); ibm->flags.init_called = 0; @@ -11587,12 +11715,6 @@ static int __init ibm_init(struct ibm_init_struct *iibm) } if (ibm->acpi) { - if (ibm->acpi->hid) { - ret = register_tpacpi_subdriver(ibm); - if (ret) - goto err_out; - } - if (ibm->acpi->notify) { ret = setup_acpi_notify(ibm); if (ret == -ENODEV) { @@ -11959,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = tpacpi_hwdd_init, .data = &hwdd_driver_data, }, + { + .init = tpacpi_usbc_security_init, + .data = &usbc_security_driver_data, + }, }; static int __init set_ibm_param(const char *val, const struct kernel_param *kp) @@ -12097,6 +12223,8 @@ static void thinkpad_acpi_module_exit(void) remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir); if (tpacpi_wq) destroy_workqueue(tpacpi_wq); + if (tpacpi_dbg) + tpacpi_debugfs_remove(); kfree(thinkpad_id.bios_version_str); kfree(thinkpad_id.ec_version_str); @@ -12227,6 +12355,8 @@ static int __init thinkpad_acpi_module_init(void) return -ENODEV; } + tpacpi_debugfs_init(); + dmi_id = dmi_first_match(fwbug_list); if (dmi_id) tp_features.quirks = dmi_id->driver_data; diff --git a/drivers/platform/x86/lenovo/wmi-camera.c b/drivers/platform/x86/lenovo/wmi-camera.c index eb60fb9a5b3f..89ecbce60bf4 100644 --- a/drivers/platform/x86/lenovo/wmi-camera.c +++ b/drivers/platform/x86/lenovo/wmi-camera.c @@ -134,6 +134,7 @@ static struct wmi_driver lenovo_wmi_driver = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .id_table = lenovo_wmi_id_table, + .min_event_size = sizeof(u8), .no_singleton = true, .probe = lenovo_wmi_probe, .notify = lenovo_wmi_notify, diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index ee1fb02d8e31..d5e961566136 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -27,11 +27,11 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> -#include <linux/bitfield.h> #include <linux/bug.h> #include <linux/cleanup.h> #include <linux/component.h> #include <linux/container_of.h> +#include <linux/debugfs.h> #include <linux/device.h> #include <linux/dev_printk.h> #include <linux/err.h> @@ -43,24 +43,25 @@ #include <linux/mutex_types.h> #include <linux/notifier.h> #include <linux/overflow.h> +#include <linux/seq_file.h> #include <linux/stddef.h> #include <linux/types.h> #include <linux/wmi.h> #include "wmi-capdata.h" +#include "wmi-helpers.h" #define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" #define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21" -#define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 #define LWMI_FEATURE_ID_FAN_TEST 0x05 -#define LWMI_ATTR_ID_FAN_TEST \ - (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST)) +#define LWMI_ATTR_ID_FAN_TEST \ + lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_TEST, \ + LWMI_GZ_THERMAL_MODE_NONE, LWMI_TYPE_ID_NONE) enum lwmi_cd_type { LENOVO_CAPABILITY_DATA_00, @@ -88,6 +89,7 @@ struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; struct cd_list *list; + struct dentry *debugfs_dir; /* * A capdata device may be a component master of another capdata device. @@ -118,6 +120,8 @@ struct cd_list { static struct wmi_driver lwmi_cd_driver; +/* ======== Device components ======== */ + /** * lwmi_cd_match() - Match rule for the master driver. * @dev: Pointer to the capability data parent device. @@ -471,6 +475,116 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA"); +/* ======== debugfs ======== */ + +/** + * lwmi_cd00_show() - Dump capdata00 + * @s: Pointer to seq_file where the capdata00 is dumped. + * @cd00: Pointer to a capdata00 struct to be dumped. + */ +static void lwmi_cd00_show(struct seq_file *s, struct capdata00 *cd00) +{ + u8 dev = FIELD_GET(LWMI_ATTR_DEV_ID_MASK, cd00->id); + u8 feat = FIELD_GET(LWMI_ATTR_FEAT_ID_MASK, cd00->id); + u8 mode = FIELD_GET(LWMI_ATTR_MODE_ID_MASK, cd00->id); + u8 type = FIELD_GET(LWMI_ATTR_TYPE_ID_MASK, cd00->id); + bool extra = cd00->supported & ~(LWMI_SUPP_GET | LWMI_SUPP_SET | LWMI_SUPP_VALID); + bool get = cd00->supported & LWMI_SUPP_GET; + bool set = cd00->supported & LWMI_SUPP_SET; + bool valid = cd00->supported & LWMI_SUPP_VALID; + + seq_printf(s, " id: 0x%08x [dev: %2u, feat: %2u, mode: %2u, type: %2u]\n", + cd00->id, dev, feat, mode, type); + + seq_printf(s, " supported: 0x%08x [%c%c%c%c]\n", cd00->supported, + extra ? '+' : ' ', + get ? 'R' : ' ', + set ? 'W' : ' ', + valid ? 'V' : ' '); + + seq_printf(s, " default_value: %u\n", cd00->default_value); +} + +/** + * lwmi_cd01_show() - Dump capdata01 + * @s: Pointer to seq_file where the capdata01 is dumped. + * @cd01: Pointer to a capdata01 struct to be dumped. + */ +static void lwmi_cd01_show(struct seq_file *s, struct capdata01 *cd01) +{ + /* capdata01 is an extension to capdata00. */ + lwmi_cd00_show(s, &cd01->cd00); + + seq_printf(s, " step: %u\n", cd01->step); + seq_printf(s, " min_value: %u\n", cd01->min_value); + seq_printf(s, " max_value: %u\n", cd01->max_value); +} + +/** + * lwmi_cd_fan_show() - Dump capdata_fan + * @s: Pointer to seq_file where the capdata_fan is dumped. + * @cd_fan: Pointer to a capdata_fan struct to be dumped. + */ +static void lwmi_cd_fan_show(struct seq_file *s, struct capdata_fan *cd_fan) +{ + seq_printf(s, " id: %u\n", cd_fan->id); + seq_printf(s, " min_rpm: %u\n", cd_fan->min_rpm); + seq_printf(s, " max_rpm: %u\n", cd_fan->max_rpm); +} + +/** + * lwmi_cd_debugfs_show() - Dump capability data to debugfs + * @s: Pointer to seq_file where the capability data is dumped. + * @data: unused. + * + * Return: 0 + */ +static int lwmi_cd_debugfs_show(struct seq_file *s, void *data) +{ + struct lwmi_cd_priv *priv = s->private; + u8 idx; + + guard(mutex)(&priv->list->list_mutex); + + /* lwmi_cd_alloc() ensured priv->list->type must be a valid type. */ + for (idx = 0; idx < priv->list->count; idx++) { + seq_printf(s, "%s[%u]:\n", lwmi_cd_table[priv->list->type].name, idx); + + if (priv->list->type == LENOVO_CAPABILITY_DATA_00) + lwmi_cd00_show(s, &priv->list->cd00[idx]); + else if (priv->list->type == LENOVO_CAPABILITY_DATA_01) + lwmi_cd01_show(s, &priv->list->cd01[idx]); + else if (priv->list->type == LENOVO_FAN_TEST_DATA) + lwmi_cd_fan_show(s, &priv->list->cd_fan[idx]); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(lwmi_cd_debugfs); + +/** + * lwmi_cd_debugfs_add() - Create debugfs directory and files for a device + * @priv: lenovo-wmi-capdata driver data. + */ +static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv) +{ + priv->debugfs_dir = lwmi_debugfs_create_dir(priv->wdev); + + debugfs_create_file("capdata", 0444, priv->debugfs_dir, priv, &lwmi_cd_debugfs_fops); +} + +/** + * lwmi_cd_debugfs_remove() - Remove debugfs directory for a device + * @priv: lenovo-wmi-capdata driver data. + */ +static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv) +{ + debugfs_remove_recursive(priv->debugfs_dir); + priv->debugfs_dir = NULL; +} + +/* ======== WMI interface ======== */ + /** * lwmi_cd_cache() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. @@ -773,6 +887,8 @@ out: dev_err(&wdev->dev, "failed to register %s: %d\n", info->name, ret); } else { + lwmi_cd_debugfs_add(priv); + dev_dbg(&wdev->dev, "registered %s with %u items\n", info->name, priv->list->count); } @@ -783,6 +899,8 @@ static void lwmi_cd_remove(struct wmi_device *wdev) { struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev); + lwmi_cd_debugfs_remove(priv); + switch (priv->list->type) { case LENOVO_CAPABILITY_DATA_00: lwmi_cd_sub_master_del(priv); @@ -822,6 +940,7 @@ static struct wmi_driver lwmi_cd_driver = { module_wmi_driver(lwmi_cd_driver); +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); MODULE_AUTHOR("Rong Zhang <i@rong.moe>"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index 8c1df3efcc55..92098aeeee84 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -6,6 +6,7 @@ #define _LENOVO_WMI_CAPDATA_H_ #include <linux/bits.h> +#include <linux/bitfield.h> #include <linux/types.h> #define LWMI_SUPP_VALID BIT(0) @@ -17,7 +18,14 @@ #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) -#define LWMI_DEVICE_ID_FAN 0x04 +enum lwmi_device_id { + LWMI_DEVICE_ID_CPU = 0x01, + LWMI_DEVICE_ID_GPU = 0x02, + LWMI_DEVICE_ID_PSU = 0x03, + LWMI_DEVICE_ID_FAN = 0x04, +}; + +#define LWMI_TYPE_ID_NONE 0x00 struct component_match; struct device; @@ -30,9 +38,10 @@ struct capdata00 { }; struct capdata01 { - u32 id; - u32 supported; - u32 default_value; + union { + struct capdata00; + struct capdata00 cd00; + }; u32 step; u32 min_value; u32 max_value; @@ -57,6 +66,23 @@ struct lwmi_cd_binder { cd_list_cb_t cd_fan_list_cb; }; +/** + * lwmi_attr_id() - Formats a capability data attribute ID + * @dev_id: The u8 corresponding to the device ID. + * @feat_id: The u8 corresponding to the feature ID on the device. + * @mode_id: The u8 corresponding to the wmi-gamezone mode for set/get. + * @type_id: The u8 corresponding to the sub-device. + * + * Return: encoded capability data attribute ID. + */ +static inline u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id) +{ + return (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev_id) | + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat_id) | + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode_id) | + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type_id)); +} + void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr); int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output); diff --git a/drivers/platform/x86/lenovo/wmi-events.c b/drivers/platform/x86/lenovo/wmi-events.c index 0994cd7dd504..fc25bba68a7c 100644 --- a/drivers/platform/x86/lenovo/wmi-events.c +++ b/drivers/platform/x86/lenovo/wmi-events.c @@ -17,7 +17,7 @@ #include <linux/wmi.h> #include "wmi-events.h" -#include "wmi-gamezone.h" +#include "wmi-helpers.h" #define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F" @@ -183,6 +183,7 @@ static struct wmi_driver lwmi_events_driver = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .id_table = lwmi_events_id_table, + .min_event_size = sizeof(u32), .probe = lwmi_events_probe, .notify = lwmi_events_notify, .no_singleton = true, diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.c b/drivers/platform/x86/lenovo/wmi-gamezone.c index 381836d29a96..109c0b564a9f 100644 --- a/drivers/platform/x86/lenovo/wmi-gamezone.c +++ b/drivers/platform/x86/lenovo/wmi-gamezone.c @@ -21,9 +21,7 @@ #include <linux/wmi.h> #include "wmi-events.h" -#include "wmi-gamezone.h" #include "wmi-helpers.h" -#include "wmi-other.h" #define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" @@ -31,8 +29,6 @@ #define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44 #define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45 -static BLOCKING_NOTIFIER_HEAD(gz_chain_head); - struct lwmi_gz_priv { enum thermal_mode current_mode; struct notifier_block event_nb; @@ -203,7 +199,7 @@ static int lwmi_gz_profile_set(struct device *dev, enum platform_profile_option profile) { struct lwmi_gz_priv *priv = dev_get_drvdata(dev); - struct wmi_method_args_32 args; + struct wmi_method_args_32 args = {}; enum thermal_mode mode; int ret; @@ -385,7 +381,7 @@ static int lwmi_gz_probe(struct wmi_device *wdev, const void *context) return ret; priv->mode_nb.notifier_call = lwmi_gz_mode_call; - return devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb); + return devm_lwmi_tm_register_notifier(&wdev->dev, &priv->mode_nb); } static const struct wmi_device_id lwmi_gz_id_table[] = { @@ -407,7 +403,6 @@ module_wmi_driver(lwmi_gz_driver); MODULE_IMPORT_NS("LENOVO_WMI_EVENTS"); MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); -MODULE_IMPORT_NS("LENOVO_WMI_OTHER"); MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table); MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h deleted file mode 100644 index 6b163a5eeb95..000000000000 --- a/drivers/platform/x86/lenovo/wmi-gamezone.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */ - -#ifndef _LENOVO_WMI_GAMEZONE_H_ -#define _LENOVO_WMI_GAMEZONE_H_ - -enum gamezone_events_type { - LWMI_GZ_GET_THERMAL_MODE = 1, -}; - -enum thermal_mode { - LWMI_GZ_THERMAL_MODE_QUIET = 0x01, - LWMI_GZ_THERMAL_MODE_BALANCED = 0x02, - LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03, - LWMI_GZ_THERMAL_MODE_EXTREME = 0xE0, /* Ver 6+ */ - LWMI_GZ_THERMAL_MODE_CUSTOM = 0xFF, -}; - -#endif /* !_LENOVO_WMI_GAMEZONE_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c index 7379defac500..8f5766c391eb 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.c +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -18,14 +18,20 @@ #include <linux/acpi.h> #include <linux/cleanup.h> +#include <linux/debugfs.h> +#include <linux/device.h> #include <linux/errno.h> #include <linux/export.h> #include <linux/module.h> +#include <linux/notifier.h> #include <linux/unaligned.h> #include <linux/wmi.h> #include "wmi-helpers.h" +/* Thermal mode notifier chain. */ +static BLOCKING_NOTIFIER_HEAD(tm_chain_head); + /** * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that * return an integer. @@ -46,7 +52,6 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, unsigned char *buf, size_t size, u32 *retval) { struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *ret_obj __free(kfree) = NULL; struct acpi_buffer input = { size, buf }; acpi_status status; @@ -55,8 +60,9 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, if (ACPI_FAILURE(status)) return -EIO; + union acpi_object *ret_obj __free(kfree) = output.pointer; + if (retval) { - ret_obj = output.pointer; if (!ret_obj) return -ENODATA; @@ -84,6 +90,135 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, }; EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_int, "LENOVO_WMI_HELPERS"); +/** + * lwmi_tm_register_notifier() - Add a notifier to the blocking notifier chain + * @nb: The notifier_block struct to register + * + * Call blocking_notifier_chain_register to register the notifier block to the + * thermal mode notifier chain. + * + * Return: 0 on success, %-EEXIST on error. + */ +int lwmi_tm_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&tm_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_tm_register_notifier, "LENOVO_WMI_HELPERS"); + +/** + * lwmi_tm_unregister_notifier() - Remove a notifier from the blocking notifier + * chain. + * @nb: The notifier_block struct to register + * + * Call blocking_notifier_chain_unregister to unregister the notifier block from the + * thermal mode notifier chain. + * + * Return: 0 on success, %-ENOENT on error. + */ +int lwmi_tm_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&tm_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_tm_unregister_notifier, "LENOVO_WMI_HELPERS"); + +/** + * devm_lwmi_tm_unregister_notifier() - Remove a notifier from the blocking + * notifier chain. + * @data: Void pointer to the notifier_block struct to register. + * + * Call lwmi_tm_unregister_notifier to unregister the notifier block from the + * thermal mode notifier chain. + * + * Return: 0 on success, %-ENOENT on error. + */ +static void devm_lwmi_tm_unregister_notifier(void *data) +{ + struct notifier_block *nb = data; + + lwmi_tm_unregister_notifier(nb); +} + +/** + * devm_lwmi_tm_register_notifier() - Add a notifier to the blocking notifier + * chain. + * @dev: The parent device of the notifier_block struct. + * @nb: The notifier_block struct to register + * + * Call lwmi_tm_register_notifier to register the notifier block to the + * thermal mode notifier chain. Then add devm_lwmi_tm_unregister_notifier + * as a device managed action to automatically unregister the notifier block + * upon parent device removal. + * + * Return: 0 on success, or an error code. + */ +int devm_lwmi_tm_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret = lwmi_tm_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_lwmi_tm_unregister_notifier, + nb); +} +EXPORT_SYMBOL_NS_GPL(devm_lwmi_tm_register_notifier, "LENOVO_WMI_HELPERS"); + +/** + * lwmi_tm_notifier_call() - Call functions for the notifier call chain. + * @mode: Pointer to a thermal mode enum to retrieve the data from. + * + * Call blocking_notifier_call_chain to retrieve the thermal mode from the + * lenovo-wmi-gamezone driver. + * + * Return: 0 on success, or an error code. + */ +int lwmi_tm_notifier_call(enum thermal_mode *mode) +{ + int ret; + + ret = blocking_notifier_call_chain(&tm_chain_head, + LWMI_GZ_GET_THERMAL_MODE, &mode); + if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(lwmi_tm_notifier_call, "LENOVO_WMI_HELPERS"); + +static struct dentry *lwmi_debugfs_dir; + +/** + * lwmi_debugfs_create_dir() - Helper function for creating a debugfs directory + * for a device. + * @wdev: Pointer to the WMI device to be called. + * + * Caller must remove the directory with debugfs_remove_recursive() on device + * removal. + * + * Return: Pointer to the created directory. + */ +struct dentry *lwmi_debugfs_create_dir(struct wmi_device *wdev) +{ + return debugfs_create_dir(dev_name(&wdev->dev), lwmi_debugfs_dir); +} +EXPORT_SYMBOL_NS_GPL(lwmi_debugfs_create_dir, "LENOVO_WMI_HELPERS"); + +static int __init lwmi_helpers_init(void) +{ + lwmi_debugfs_dir = debugfs_create_dir("lenovo_wmi", NULL); + + return 0; +} +subsys_initcall(lwmi_helpers_init) + +static void __exit lwmi_helpers_exit(void) +{ + debugfs_remove_recursive(lwmi_debugfs_dir); +} +module_exit(lwmi_helpers_exit) + MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); MODULE_DESCRIPTION("Lenovo WMI Helpers Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-helpers.h b/drivers/platform/x86/lenovo/wmi-helpers.h index 20fd21749803..039fe61003ce 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.h +++ b/drivers/platform/x86/lenovo/wmi-helpers.h @@ -7,6 +7,8 @@ #include <linux/types.h> +struct device; +struct notifier_block; struct wmi_device; struct wmi_method_args_32 { @@ -14,7 +16,28 @@ struct wmi_method_args_32 { u32 arg1; }; +struct dentry *lwmi_debugfs_create_dir(struct wmi_device *wdev); + +enum lwmi_event_type { + LWMI_GZ_GET_THERMAL_MODE = 0x01, +}; + +enum thermal_mode { + LWMI_GZ_THERMAL_MODE_NONE = 0x00, + LWMI_GZ_THERMAL_MODE_QUIET = 0x01, + LWMI_GZ_THERMAL_MODE_BALANCED = 0x02, + LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03, + LWMI_GZ_THERMAL_MODE_EXTREME = 0xE0, /* Ver 6+ */ + LWMI_GZ_THERMAL_MODE_CUSTOM = 0xFF, +}; + int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, unsigned char *buf, size_t size, u32 *retval); +int lwmi_tm_register_notifier(struct notifier_block *nb); +int lwmi_tm_unregister_notifier(struct notifier_block *nb); +int devm_lwmi_tm_register_notifier(struct device *dev, + struct notifier_block *nb); +int lwmi_tm_notifier_call(enum thermal_mode *mode); + #endif /* !_LENOVO_WMI_HELPERS_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 6040f45aa2b0..fbb32bf404f2 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -40,29 +40,53 @@ #include <linux/kobject.h> #include <linux/limits.h> #include <linux/module.h> -#include <linux/notifier.h> #include <linux/platform_profile.h> +#include <linux/power_supply.h> #include <linux/types.h> #include <linux/wmi.h> +#include <acpi/battery.h> + #include "wmi-capdata.h" #include "wmi-events.h" -#include "wmi-gamezone.h" #include "wmi-helpers.h" -#include "wmi-other.h" #include "../firmware_attributes_class.h" #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" -#define LWMI_DEVICE_ID_CPU 0x01 +enum lwmi_feature_id_cpu { + LWMI_FEATURE_ID_CPU_SPPT = 0x01, + LWMI_FEATURE_ID_CPU_SPL = 0x02, + LWMI_FEATURE_ID_CPU_FPPT = 0x03, + LWMI_FEATURE_ID_CPU_TEMP = 0x04, + LWMI_FEATURE_ID_CPU_APU = 0x05, + LWMI_FEATURE_ID_CPU_CL = 0x06, + LWMI_FEATURE_ID_CPU_TAU = 0x07, + LWMI_FEATURE_ID_CPU_IPL = 0x09, +}; -#define LWMI_FEATURE_ID_CPU_SPPT 0x01 -#define LWMI_FEATURE_ID_CPU_SPL 0x02 -#define LWMI_FEATURE_ID_CPU_FPPT 0x03 +enum lwmi_feature_id_gpu { + LWMI_FEATURE_ID_GPU_NV_PPAB = 0x01, + LWMI_FEATURE_ID_GPU_NV_CTGP = 0x02, + LWMI_FEATURE_ID_GPU_TEMP = 0x03, + LWMI_FEATURE_ID_GPU_AC_OFFSET = 0x04, + LWMI_FEATURE_ID_DGPU_BOOST_CLK = 0x06, + LWMI_FEATURE_ID_DGPU_EN = 0x07, + LWMI_FEATURE_ID_GPU_MODE = 0x08, + LWMI_FEATURE_ID_DGPU_DIDVID = 0x09, + LWMI_FEATURE_ID_GPU_NV_BPL = 0x0a, + LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b, +}; + +enum lwmi_feature_id_psu { + LWMI_FEATURE_ID_PSU_CHARGE_TYPES = 0x01, + LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR = 0x02, +}; #define LWMI_FEATURE_ID_FAN_RPM 0x03 -#define LWMI_TYPE_ID_NONE 0x00 +#define LWMI_TYPE_ID_CROSSLOAD 0x01 +#define LWMI_TYPE_ID_PSU_AC 0x01 #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 @@ -71,17 +95,24 @@ #define LWMI_FAN_NR 4 #define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) -#define LWMI_ATTR_ID_FAN_RPM(x) \ - (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \ - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x))) - #define LWMI_FAN_DIV 100 -#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" +#define LWMI_CHARGE_BEHAVIOR_DISCHARGE 0x00 +#define LWMI_CHARGE_BEHAVIOR_AUTO 0x01 +#define LWMI_CHARGE_TYPE_STANDARD 0x00 +#define LWMI_CHARGE_TYPE_LONGLIFE 0x01 + +#define LWMI_ATTR_ID_FAN_RPM(x) \ + lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \ + LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x)) + +#define LWMI_ATTR_ID_PSU(feat, type) \ + lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat, \ + LWMI_GZ_THERMAL_MODE_NONE, type) + +#define LWMI_OM_SYSFS_NAME "lenovo-wmi-other" #define LWMI_OM_HWMON_NAME "lenovo_wmi_other" -static BLOCKING_NOTIFIER_HEAD(om_chain_head); static DEFINE_IDA(lwmi_om_ida); enum attribute_property { @@ -109,7 +140,6 @@ struct lwmi_om_priv { struct device *hwmon_dev; struct device *fw_attr_dev; struct kset *fw_attr_kset; - struct notifier_block nb; struct wmi_device *wdev; int ida_id; @@ -119,6 +149,11 @@ struct lwmi_om_priv { bool capdata00_collected : 1; bool capdata_fan_collected : 1; } fan_flags; + + enum power_supply_charge_behaviour charge_behaviour; + const struct power_supply_ext *battery_ext; + struct acpi_battery_hook battery_hook; + bool bh_registered; }; /* @@ -150,6 +185,16 @@ MODULE_PARM_DESC(relax_fan_constraint, "Enabling this may results in HWMON attributes being out-of-sync, " "and setting a too low RPM stops the fan. Use with caution."); +/* Visibility of power supply extensions */ +static bool force_load_psy_ext; +module_param(force_load_psy_ext, bool, 0444); +MODULE_PARM_DESC(force_load_psy_ext, + "This option will skip checking if the ideapad_laptop driver will conflict " + "with adding an extension to set the battery charge behavior and battery charge " + "control end threshold. It will also skip checking if the BIOS reports that " + "those features are fully supported. It is recommended to blacklist the ideapad " + "driver before using this option."); + /* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */ /** @@ -166,7 +211,7 @@ MODULE_PARM_DESC(relax_fan_constraint, */ static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set) { - struct wmi_method_args_32 args; + struct wmi_method_args_32 args = {}; u32 method_id, retval; int err; @@ -349,6 +394,8 @@ static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type, */ if (!relax_fan_constraint) raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV; + else + raw = val; err = lwmi_om_fan_get_set(priv, channel, &raw, true); if (err) @@ -543,135 +590,540 @@ out: lwmi_om_hwmon_add(priv); } -/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */ +/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */ -struct tunable_attr_01 { - struct capdata01 *capdata; - struct device *dev; - u32 feature_id; - u32 device_id; - u32 type_id; -}; +/** + * lwmi_psy_ext_get_prop() - Get a power_supply_ext property + * @ps: The battery that was extended + * @ext: The extension + * @ext_data: Pointer to the lwmi_om_priv drvdata + * @prop: The property to read + * @val: The value to return + * + * Reads the given value from the power_supply_ext property + * + * Return: 0 on success, or an error + */ +static int lwmi_psy_ext_get_prop(struct power_supply *ps, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct lwmi_om_priv *priv = ext_data; + struct wmi_method_args_32 args = {}; + u32 retval; + int ret; -static struct tunable_attr_01 ppt_pl1_spl = { - .device_id = LWMI_DEVICE_ID_CPU, - .feature_id = LWMI_FEATURE_ID_CPU_SPL, - .type_id = LWMI_TYPE_ID_NONE, -}; + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + /* Reading from BIOS reads the wrong bit. Use cached value */ + val->intval = priv->charge_behaviour; + return 0; + case POWER_SUPPLY_PROP_CHARGE_TYPES: + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES, + LWMI_TYPE_ID_PSU_AC); + break; + default: + return -EINVAL; + } -static struct tunable_attr_01 ppt_pl2_sppt = { - .device_id = LWMI_DEVICE_ID_CPU, - .feature_id = LWMI_FEATURE_ID_CPU_SPPT, - .type_id = LWMI_TYPE_ID_NONE, -}; + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET, + (u8 *)&args, sizeof(args), + &retval); + if (ret) + return ret; -static struct tunable_attr_01 ppt_pl3_fppt = { - .device_id = LWMI_DEVICE_ID_CPU, - .feature_id = LWMI_FEATURE_ID_CPU_FPPT, - .type_id = LWMI_TYPE_ID_NONE, -}; + dev_dbg(&priv->wdev->dev, "Got return value %#x for property %#x\n", retval, prop); -struct capdata01_attr_group { - const struct attribute_group *attr_group; - struct tunable_attr_01 *tunable_attr; -}; + switch (retval) { + case LWMI_CHARGE_TYPE_LONGLIFE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + break; + case LWMI_CHARGE_TYPE_STANDARD: + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + default: + dev_err(&priv->wdev->dev, "Got invalid charge types value: %#x\n", retval); + return -EINVAL; + } + + return 0; +} /** - * lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain - * @nb: The notifier_block struct to register + * lwmi_psy_ext_set_prop() - Set a power_supply_ext property + * @ps: The battery that was extended + * @ext: The extension + * @ext_data: Pointer to the lwmi_om_priv drvdata + * @prop: The property to write + * @val: The value to write * - * Call blocking_notifier_chain_register to register the notifier block to the - * lenovo-wmi-other driver notifier chain. + * Writes the given value to the power_supply_ext property * - * Return: 0 on success, %-EEXIST on error. + * Return: 0 on success, or an error */ -int lwmi_om_register_notifier(struct notifier_block *nb) +static int lwmi_psy_ext_set_prop(struct power_supply *ps, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property prop, + const union power_supply_propval *val) { - return blocking_notifier_chain_register(&om_chain_head, nb); + struct lwmi_om_priv *priv = ext_data; + struct wmi_method_args_32 args = {}; + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR, + LWMI_TYPE_ID_NONE); + switch (val->intval) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO; + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + args.arg1 = LWMI_CHARGE_BEHAVIOR_DISCHARGE; + break; + default: + dev_err(&priv->wdev->dev, "Got invalid charge behavior value: %#x\n", + val->intval); + return -EINVAL; + } + priv->charge_behaviour = val->intval; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPES: + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES, + LWMI_TYPE_ID_PSU_AC); + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: + args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE; + break; + case POWER_SUPPLY_CHARGE_TYPE_STANDARD: + args.arg1 = LWMI_CHARGE_TYPE_STANDARD; + break; + default: + dev_err(&priv->wdev->dev, "Got invalid charge types value: %#x\n", + val->intval); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + dev_dbg(&priv->wdev->dev, "Attempting to set %#010x for property %#x to %#x\n", + args.arg0, prop, args.arg1); + + return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET, + (u8 *)&args, sizeof(args), NULL); } -EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); -/** - * lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier - * chain. - * @nb: The notifier_block struct to register +/** lwmi_psy_prop_get_supported() - Gets the support level from capdata for a given property + * @priv: Pointer to the lwmi_om_priv drvdata + * @prop: The power supply property to be evaluated * - * Call blocking_notifier_chain_unregister to unregister the notifier block from the - * lenovo-wmi-other driver notifier chain. - * - * Return: 0 on success, %-ENOENT on error. + * Return: bitmapped capability data support level */ -int lwmi_om_unregister_notifier(struct notifier_block *nb) +static u32 lwmi_psy_prop_get_supported(struct lwmi_om_priv *priv, enum power_supply_property prop) { - return blocking_notifier_chain_unregister(&om_chain_head, nb); + struct capdata00 capdata; + u32 attribute_id; + int ret; + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR, + LWMI_TYPE_ID_NONE); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPES: + attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES, + LWMI_TYPE_ID_PSU_AC); + break; + default: + return 0; + } + + ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata); + if (ret) + return 0; + + dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n", + attribute_id, capdata.supported); + + return capdata.supported; } -EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER"); /** - * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking - * notifier chain. - * @data: Void pointer to the notifier_block struct to register. + * lwmi_psy_prop_is_supported() - Determine if the property is supported + * @priv: Pointer to the lwmi_om_priv drvdata + * @prop: The power supply property to be evaluated * - * Call lwmi_om_unregister_notifier to unregister the notifier block from the - * lenovo-wmi-other driver notifier chain. + * Checks capdata 00 to determine if the property is supported. * - * Return: 0 on success, %-ENOENT on error. + * Return: true if readable, or false */ -static void devm_lwmi_om_unregister_notifier(void *data) +static bool lwmi_psy_prop_is_supported(struct lwmi_om_priv *priv, enum power_supply_property prop) { - struct notifier_block *nb = data; + int ret; + + if (force_load_psy_ext) + return true; + + ret = lwmi_psy_prop_get_supported(priv, prop); + if (ret < 0) + return false; - lwmi_om_unregister_notifier(nb); + return (ret & LWMI_SUPP_VALID) && (ret & LWMI_SUPP_GET); } /** - * devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier - * chain. - * @dev: The parent device of the notifier_block struct. - * @nb: The notifier_block struct to register + * lwmi_psy_prop_is_writeable() - Determine if the property is writeable + * @ps: The battery that was extended + * @ext: The extension + * @ext_data: Pointer the lwmi_om_priv drvdata + * @prop: The property to check * - * Call lwmi_om_register_notifier to register the notifier block to the - * lenovo-wmi-other driver notifier chain. Then add devm_lwmi_om_unregister_notifier - * as a device managed action to automatically unregister the notifier block - * upon parent device removal. + * Checks capdata 00 to determine if the property is writable. * - * Return: 0 on success, or an error code. + * Return: true if writable, or false */ -int devm_lwmi_om_register_notifier(struct device *dev, - struct notifier_block *nb) +static int lwmi_psy_prop_is_writeable(struct power_supply *ps, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property prop) { + struct lwmi_om_priv *priv = ext_data; int ret; - ret = lwmi_om_register_notifier(nb); + if (force_load_psy_ext) + return true; + + ret = lwmi_psy_prop_get_supported(priv, prop); if (ret < 0) - return ret; + return false; - return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier, - nb); + return !!(ret & LWMI_SUPP_SET); } -EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); + +#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours, _types) \ + static const struct power_supply_ext _name = { \ + .name = LWMI_OM_SYSFS_NAME, \ + .properties = _props, \ + .num_properties = ARRAY_SIZE(_props), \ + .charge_behaviours = _behaviours, \ + .charge_types = _types, \ + .get_property = lwmi_psy_ext_get_prop, \ + .set_property = lwmi_psy_ext_set_prop, \ + .property_is_writeable = lwmi_psy_prop_is_writeable, \ + } + +static const enum power_supply_property lwmi_psy_ext_props_all[] = { + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_TYPES, +}; + +static const enum power_supply_property lwmi_psy_ext_props_types[] = { + POWER_SUPPLY_PROP_CHARGE_TYPES, +}; + +static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = { + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, +}; + +#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE)) + +#define LWMI_CHARGE_TYPES (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | \ + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)) + +DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all, + LWMI_CHARGE_BEHAVIOURS, LWMI_CHARGE_TYPES); +DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_types, lwmi_psy_ext_props_types, + 0, LWMI_CHARGE_TYPES); +DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour, lwmi_psy_ext_props_behaviour, + LWMI_CHARGE_BEHAVIOURS, 0); + +#define LWMI_PSY_PROP_BEHAVIOUR BIT(0) +#define LWMI_PSY_PROP_TYPES BIT(1) + +static const struct power_supply_ext *lwmi_psy_exts[] = { + [LWMI_PSY_PROP_BEHAVIOUR] = &lwmi_psy_ext_behaviour, + [LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_types, + [LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_all, +}; /** - * lwmi_om_notifier_call() - Call functions for the notifier call chain. - * @mode: Pointer to a thermal mode enum to retrieve the data from. + * lwmi_add_battery() - Connect the power_supply_ext + * @battery: The battery to extend + * @hook: The driver hook used to extend the battery * - * Call blocking_notifier_call_chain to retrieve the thermal mode from the - * lenovo-wmi-gamezone driver. + * Return: 0 on success, or an error. + */ +static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook); + + return power_supply_register_extension(battery, priv->battery_ext, &priv->wdev->dev, priv); +} + +/** + * lwmi_remove_battery() - Disconnect the power_supply_ext + * @battery: The battery that was extended + * @hook: The driver hook used to extend the battery * - * Return: 0 on success, or an error code. + * Return: 0 on success, or an error. */ -static int lwmi_om_notifier_call(enum thermal_mode *mode) +static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) { + struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook); + + power_supply_unregister_extension(battery, priv->battery_ext); + return 0; +} + +/** + * lwmi_acpi_match() - Attempts to return the ideapad acpi handle + * @handle: The ACPI handle that manages battery charging + * @lvl: Unused + * @context: Void pointer to the acpi_handle object to return + * @retval: Unused + * + * Checks if the ideapad_laptop driver is going to manage charge_type first, + * then if not, hooks the battery to our WMI methods. + * + * Return: AE_CTRL_TERMINATE if found, AE_OK if not found. + */ +static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl, + void *context, void **retval) +{ + acpi_handle *ahand = context; + + if (!handle) + return AE_OK; + + *ahand = handle; + + return AE_CTRL_TERMINATE; +} + +/** + * lwmi_om_psy_ext_init() - Hooks power supply extension to device battery + * @priv: Pointer to the lwmi_om_priv drvdata. + * + * Checks if the ideapad_laptop driver is going to manage charge attributes first, + * then if not, hooks the battery to our WMI methods if they are supported. + */ +static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv) +{ + static const char * const ideapad_hid = "VPC2004"; + acpi_handle handle = NULL; + unsigned int props = 0; int ret; - ret = blocking_notifier_call_chain(&om_chain_head, - LWMI_GZ_GET_THERMAL_MODE, &mode); - if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK) - return -EINVAL; + if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR)) + props |= LWMI_PSY_PROP_BEHAVIOUR; + if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_TYPES)) + props |= LWMI_PSY_PROP_TYPES; + if (!props) + return; + if (force_load_psy_ext) + goto load_psy_ext; - return 0; + /* Deconflict ideapad_laptop driver */ + ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL); + if (ret) + return; + + if (handle && acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) { + dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device\n"); + return; + } + +load_psy_ext: + /* Add battery hooks */ + priv->battery_ext = lwmi_psy_exts[props]; + priv->battery_hook.add_battery = lwmi_add_battery; + priv->battery_hook.remove_battery = lwmi_remove_battery; + priv->battery_hook.name = "Lenovo WMI Other Battery Extension"; + priv->bh_registered = true; + + battery_hook_register(&priv->battery_hook); +} + +/** + * lwmi_om_psy_remove() - Unregister battery hook + * @priv: Driver private data + * + * Unregisters the battery hook if applicable. + */ +static void lwmi_om_psy_remove(struct lwmi_om_priv *priv) +{ + if (!priv->bh_registered) + return; + + battery_hook_unregister(&priv->battery_hook); + priv->bh_registered = false; +} + +/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */ + +struct tunable_attr_01 { + struct device *dev; + u8 feature_id; + u8 device_id; + u8 type_id; + u8 cd_mode_id; /* mode arg for searching capdata */ + u8 cv_mode_id; /* mode arg for set/get current_value */ +}; + +/** + * tunable_attr_01_id() - Formats a tunable_attr_01 to a capdata attribute ID + * @attr: The tunable_attr_01 to format. + * @mode: The u8 corresponding to the wmi-gamezone mode for set/get. + * + * Return: encoded capability data attribute ID. + */ +static u32 tunable_attr_01_id(struct tunable_attr_01 *attr, u8 mode) +{ + return lwmi_attr_id(attr->device_id, attr->feature_id, mode, attr->type_id); } +static struct tunable_attr_01 ppt_pl1_spl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl1_spl_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPL, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + +static struct tunable_attr_01 ppt_pl2_sppt = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPPT, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl2_sppt_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPPT, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + +static struct tunable_attr_01 ppt_pl3_fppt = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_FPPT, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl3_fppt_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_FPPT, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + +static struct tunable_attr_01 cpu_temp = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_TEMP, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl1_apu_spl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_APU, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_cpu_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_CL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl1_tau = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_TAU, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl4_ipl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_IPL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl4_ipl_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_IPL, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + +static struct tunable_attr_01 gpu_nv_ppab = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_PPAB, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_ctgp = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_CTGP, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_temp = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_TEMP, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_ac_offset = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_AC_OFFSET, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 dgpu_boost_clk = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_DGPU_BOOST_CLK, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 dgpu_enable = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_DGPU_EN, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_mode = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_MODE, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 dgpu_didvid = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_DGPU_DIDVID, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_bpl = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_BPL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_cpu_boost = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_CPU_BOOST, + .type_id = LWMI_TYPE_ID_NONE, +}; + +struct capdata01_attr_group { + const struct attribute_group *attr_group; + struct tunable_attr_01 *tunable_attr; +}; + /* Attribute Methods */ /** @@ -716,12 +1168,7 @@ static ssize_t attr_capdata01_show(struct kobject *kobj, u32 attribute_id; int value, ret; - attribute_id = - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, - LWMI_GZ_THERMAL_MODE_CUSTOM) | - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + attribute_id = tunable_attr_01_id(tunable_attr, tunable_attr->cd_mode_id); ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); if (ret) @@ -773,27 +1220,22 @@ static ssize_t attr_current_value_store(struct kobject *kobj, struct tunable_attr_01 *tunable_attr) { struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); - struct wmi_method_args_32 args; + struct wmi_method_args_32 args = {}; struct capdata01 capdata; enum thermal_mode mode; - u32 attribute_id; u32 value; int ret; - ret = lwmi_om_notifier_call(&mode); + ret = lwmi_tm_notifier_call(&mode); if (ret) return ret; if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM) return -EBUSY; - attribute_id = - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + args.arg0 = tunable_attr_01_id(tunable_attr, tunable_attr->cd_mode_id); - ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata); if (ret) return ret; @@ -804,7 +1246,7 @@ static ssize_t attr_current_value_store(struct kobject *kobj, if (value < capdata.min_value || value > capdata.max_value) return -EINVAL; - args.arg0 = attribute_id; + args.arg0 = tunable_attr_01_id(tunable_attr, tunable_attr->cv_mode_id); args.arg1 = value; ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET, @@ -836,23 +1278,20 @@ static ssize_t attr_current_value_show(struct kobject *kobj, struct tunable_attr_01 *tunable_attr) { struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); - struct wmi_method_args_32 args; + struct wmi_method_args_32 args = {}; enum thermal_mode mode; - u32 attribute_id; int retval; int ret; - ret = lwmi_om_notifier_call(&mode); + ret = lwmi_tm_notifier_call(&mode); if (ret) return ret; - attribute_id = - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + /* If "no-mode" is the supported mode, ensure we never send current mode */ + if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE) + mode = tunable_attr->cv_mode_id; - args.arg0 = attribute_id; + args.arg0 = tunable_attr_01_id(tunable_attr, mode); ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET, (unsigned char *)&args, sizeof(args), @@ -863,6 +1302,81 @@ static ssize_t attr_current_value_show(struct kobject *kobj, return sysfs_emit(buf, "%d\n", retval); } +/** + * lwmi_attr_01_is_supported() - Determine if the given attribute is supported. + * @tunable_attr: The attribute to verify. + * + * For an attribute to be supported it must have a functional get/set method, + * as well as associated capability data stored in the capdata01 table. + * + * First check if the attribute has a corresponding data table under custom mode + * (0xff), then under no mode (0x00). If either of those passes, check if the + * supported field of the capdata struct is > 0. If it is supported, store the + * successful mode in the cd_mode_id field of tunable_attr. + * + * If the attribute capdata shows it is supported, attempt to determine the mode + * for the current value property get/set methods using a similar pattern to the + * capdata table check. If the value returned by either mode is 0 or an error, + * assume that mode is not supported. Otherwise, store the successful mode in the + * cv_mode_id field of tunable_attr. + * + * If any of the above checks fail then the attribute is not fully supported. + * + * Return: true if capdata and set/get modes are found, otherwise false. + */ +static bool lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr) +{ + u8 modes[2] = { LWMI_GZ_THERMAL_MODE_CUSTOM, LWMI_GZ_THERMAL_MODE_NONE }; + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); + struct wmi_method_args_32 args = {}; + bool cd_mode_found = false; + bool cv_mode_found = false; + struct capdata01 capdata; + int retval, ret, i; + + /* Determine tunable_attr->cd_mode_id */ + for (i = 0; i < ARRAY_SIZE(modes); i++) { + args.arg0 = tunable_attr_01_id(tunable_attr, modes[i]); + + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata); + if (ret || !capdata.supported) + continue; + + tunable_attr->cd_mode_id = modes[i]; + cd_mode_found = true; + break; + } + + if (!cd_mode_found) + return cd_mode_found; + + dev_dbg(tunable_attr->dev, + "cd_mode_id: %#010x\n", args.arg0); + + /* Determine tunable_attr->cv_mode_id, returns 1 if supported */ + for (i = 0; i < ARRAY_SIZE(modes); i++) { + args.arg0 = tunable_attr_01_id(tunable_attr, modes[i]); + + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET, + (u8 *)&args, sizeof(args), + &retval); + if (ret || !retval) + continue; + + tunable_attr->cv_mode_id = modes[i]; + cv_mode_found = true; + break; + } + + if (!cv_mode_found) + return cv_mode_found; + + dev_dbg(tunable_attr->dev, "cv_mode_id: %#010x, attribute support level: %#010x\n", + args.arg0, capdata.supported); + + return capdata.supported > 0; +} + /* Lenovo WMI Other Mode Attribute macros */ #define __LWMI_ATTR_RO(_func, _name) \ { \ @@ -940,39 +1454,98 @@ static ssize_t attr_current_value_show(struct kobject *kobj, .name = _fsname, .attrs = _attrname##_attrs \ } +/* CPU tunable attributes */ +LWMI_ATTR_GROUP_TUNABLE_CAP01(cpu_temp, "cpu_temp", + "Set the CPU thermal load limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_cpu_cl, "ppt_cpu_cl", + "Set the CPU cross loading power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_apu_spl, "ppt_pl1_apu_spl", + "Set the APU sustained power limit"); LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", "Set the CPU sustained power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl_cl, "ppt_pl1_spl_cl", + "Set the CPU cross loading sustained power limit"); LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", "Set the CPU slow package power tracking limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt_cl, "ppt_pl2_sppt_cl", + "Set the CPU cross loading slow package power tracking limit"); LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", "Set the CPU fast package power tracking limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt_cl, "ppt_pl3_fppt_cl", + "Set the CPU cross loading fast package power tracking limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_tau, "ppt_pl1_tau", + "Set the CPU sustained power limit exceed duration"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl, "ppt_pl4_ipl", + "Set the CPU instantaneous power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl_cl, "ppt_pl4_ipl_cl", + "Set the CPU cross loading instantaneous power limit"); + +/* GPU tunable attributes */ +LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_boost_clk, "dgpu_boost_clk", + "Set the dedicated GPU boost clock"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_didvid, "dgpu_didvid", + "Get the GPU device identifier and vendor identifier"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_enable, "dgpu_enable", + "Set the dedicated Nvidia GPU enabled status"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_mode, "gpu_mode", + "Set the GPU mode by power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ac_offset, "gpu_nv_ac_offset", + "Set the Nvidia GPU AC total processing power baseline offset"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_bpl, "gpu_nv_bpl", + "Set the Nvidia GPU base power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_cpu_boost, "gpu_nv_cpu_boost", + "Set the Nvidia GPU to CPU dynamic boost limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ctgp, "gpu_nv_ctgp", + "Set the GPU configurable total graphics power"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ppab, "gpu_nv_ppab", + "Set the Nvidia GPU power performance aware boost limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_temp, "gpu_temp", + "Set the GPU thermal load limit"); static struct capdata01_attr_group cd01_attr_groups[] = { + { &cpu_temp_attr_group, &cpu_temp }, + { &dgpu_boost_clk_attr_group, &dgpu_boost_clk }, + { &dgpu_didvid_attr_group, &dgpu_didvid }, + { &dgpu_enable_attr_group, &dgpu_enable }, + { &gpu_mode_attr_group, &gpu_mode }, + { &gpu_nv_ac_offset_attr_group, &gpu_nv_ac_offset }, + { &gpu_nv_bpl_attr_group, &gpu_nv_bpl }, + { &gpu_nv_cpu_boost_attr_group, &gpu_nv_cpu_boost }, + { &gpu_nv_ctgp_attr_group, &gpu_nv_ctgp }, + { &gpu_nv_ppab_attr_group, &gpu_nv_ppab }, + { &gpu_temp_attr_group, &gpu_temp }, + { &ppt_cpu_cl_attr_group, &ppt_cpu_cl }, + { &ppt_pl1_apu_spl_attr_group, &ppt_pl1_apu_spl }, { &ppt_pl1_spl_attr_group, &ppt_pl1_spl }, + { &ppt_pl1_spl_cl_attr_group, &ppt_pl1_spl_cl }, + { &ppt_pl1_tau_attr_group, &ppt_pl1_tau }, { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt }, + { &ppt_pl2_sppt_cl_attr_group, &ppt_pl2_sppt_cl }, { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt }, + { &ppt_pl3_fppt_cl_attr_group, &ppt_pl3_fppt_cl }, + { &ppt_pl4_ipl_attr_group, &ppt_pl4_ipl }, + { &ppt_pl4_ipl_cl_attr_group, &ppt_pl4_ipl_cl }, {}, }; /** * lwmi_om_fw_attr_add() - Register all firmware_attributes_class members * @priv: The Other Mode driver data. - * - * Return: Either 0, or an error code. */ -static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) +static void lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) { unsigned int i; int err; - priv->ida_id = ida_alloc(&lwmi_om_ida, GFP_KERNEL); - if (priv->ida_id < 0) - return priv->ida_id; + err = ida_alloc(&lwmi_om_ida, GFP_KERNEL); + if (err < 0) + goto err_no_ida; + + priv->ida_id = err; priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), NULL, "%s-%u", - LWMI_OM_FW_ATTR_BASE_PATH, - priv->ida_id); + LWMI_OM_SYSFS_NAME, priv->ida_id); if (IS_ERR(priv->fw_attr_dev)) { err = PTR_ERR(priv->fw_attr_dev); goto err_free_ida; @@ -986,14 +1559,16 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) } for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) { + cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev; + if (!lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr)) + continue; + err = sysfs_create_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group); if (err) goto err_remove_groups; - - cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev; } - return 0; + return; err_remove_groups: while (i--) @@ -1007,7 +1582,12 @@ err_destroy_classdev: err_free_ida: ida_free(&lwmi_om_ida, priv->ida_id); - return err; + +err_no_ida: + priv->ida_id = -EIDRM; + + dev_warn(&priv->wdev->dev, + "failed to register firmware-attributes device: %d\n", err); } /** @@ -1016,12 +1596,17 @@ err_free_ida: */ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) { + if (priv->ida_id < 0) + return; + for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) sysfs_remove_group(&priv->fw_attr_kset->kobj, cd01_attr_groups[i].attr_group); kset_unregister(priv->fw_attr_kset); device_unregister(priv->fw_attr_dev); + ida_free(&lwmi_om_ida, priv->ida_id); + priv->ida_id = -EIDRM; } /* ======== Self (master: lenovo-wmi-other) ======== */ @@ -1058,12 +1643,18 @@ static int lwmi_om_master_bind(struct device *dev) priv->cd00_list = binder.cd00_list; priv->cd01_list = binder.cd01_list; - if (!priv->cd00_list || !priv->cd01_list) + if (!priv->cd00_list || !priv->cd01_list) { + component_unbind_all(dev, NULL); + return -ENODEV; + } lwmi_om_fan_info_collect_cd00(priv); + lwmi_om_psy_ext_init(priv); + + lwmi_om_fw_attr_add(priv); - return lwmi_om_fw_attr_add(priv); + return 0; } /** @@ -1082,6 +1673,8 @@ static void lwmi_om_master_unbind(struct device *dev) lwmi_om_hwmon_remove(priv); + lwmi_om_psy_remove(priv); + component_unbind_all(dev, NULL); } @@ -1115,13 +1708,7 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context) static void lwmi_other_remove(struct wmi_device *wdev) { - struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev); - component_master_del(&wdev->dev, &lwmi_om_master_ops); - - /* No IDA to free if the driver is never bound to its components. */ - if (priv->ida_id >= 0) - ida_free(&lwmi_om_ida, priv->ida_id); } static const struct wmi_device_id lwmi_other_id_table[] = { diff --git a/drivers/platform/x86/lenovo/wmi-other.h b/drivers/platform/x86/lenovo/wmi-other.h deleted file mode 100644 index 8ebf5602bb99..000000000000 --- a/drivers/platform/x86/lenovo/wmi-other.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */ - -#ifndef _LENOVO_WMI_OTHER_H_ -#define _LENOVO_WMI_OTHER_H_ - -struct device; -struct notifier_block; - -int lwmi_om_register_notifier(struct notifier_block *nb); -int lwmi_om_unregister_notifier(struct notifier_block *nb); -int devm_lwmi_om_register_notifier(struct device *dev, - struct notifier_block *nb); - -#endif /* !_LENOVO_WMI_OTHER_H_ */ diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c new file mode 100644 index 000000000000..70d81f3af09e --- /dev/null +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Yoga Book 9 keyboard-dock detection + * + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically + * attaches to the bottom screen in one of two positions. The EC tracks + * attachment state in a 2-bit field called BKBD and signals changes via WMI + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). + * + * BKBD values: + * 0 = keyboard detached + * 1 = keyboard docked on the top half of the bottom screen + * 2 = keyboard docked on the bottom half of the bottom screen + * 3 = reserved / not observed + * + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): + * + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. + * The notify callback receives BKBD as an integer; no separate query needed. + * + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) + * WmiDataId(2) uint32 Status = BKBD value + * Used on probe and resume to read initial state. + * + * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI + * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev + * and registers a notifier_block to receive those events, eliminating the + * need for shared global state or a mutex. + * + * SW_TABLET_MODE=1 is reported when the keyboard is detached; + * SW_TABLET_MODE=0 when docked in either position (keyboard present). + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". + * + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com> + */ + +#include <linux/acpi.h> +#include <linux/cleanup.h> +#include <linux/compiler_attributes.h> +#include <linux/dev_printk.h> +#include <linux/dmi.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/wmi.h> + +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" + +/* BKBD encoding */ +#define BKBD_DETACHED 0 + +/* LENOVO_FEATURE_STATUS_DATA feature selector */ +#define YB9_FEATURE_STATUS_ID 0x00060000u + +/* + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}. + * IDs is always 0x00060000; Status holds the BKBD value (0–3). + */ +struct lenovo_feature_status { + __le32 id; + __le32 status; +} __packed; + +/* ------------------------------------------------------------------ + * Notifier chain — event driver fires it, block driver listens + * ------------------------------------------------------------------ */ + +static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head); + +static void devm_yb9_kbdock_unregister_notifier(void *data) +{ + struct notifier_block *nb = data; + + blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb); +} + +static int devm_yb9_kbdock_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb); +} + +/* ------------------------------------------------------------------ + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA + * (owns input_dev, sysfs, PM resume) + * ------------------------------------------------------------------ */ + +struct yb9_kbdock_data { + struct wmi_device *wdev; + struct input_dev *input_dev; + struct notifier_block nb; + spinlock_t lock; /* protects input_report_switch + input_sync */ +}; + +static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd) +{ + struct wmi_buffer out; + int ret; + + ret = wmidev_query_block(d->wdev, 0, &out, + sizeof(struct lenovo_feature_status)); + if (ret) + return ret; + + struct lenovo_feature_status *fs __free(kfree) = out.data; + + if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID) + return -EIO; + + *bkbd = le32_to_cpu(fs->status); + return 0; +} + +static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd) +{ + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; + + spin_lock(&d->lock); + input_report_switch(d->input_dev, SW_TABLET_MODE, tablet); + input_sync(d->input_dev); + spin_unlock(&d->lock); + dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet); +} + +static int yb9_kbdock_sync(struct yb9_kbdock_data *d) +{ + u32 bkbd; + int ret; + + ret = yb9_kbdock_query(d, &bkbd); + if (ret) + return ret; + + yb9_kbdock_report(d, bkbd); + return 0; +} + +static int yb9_kbdock_nb_call(struct notifier_block *nb, + unsigned long bkbd, void *unused) +{ + struct yb9_kbdock_data *d = + container_of(nb, struct yb9_kbdock_data, nb); + + yb9_kbdock_report(d, bkbd); + return NOTIFY_DONE; +} + +static ssize_t keyboard_position_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct yb9_kbdock_data *d = dev_get_drvdata(dev); + u32 bkbd; + int ret; + + ret = yb9_kbdock_query(d, &bkbd); + if (ret) + return ret; + return sysfs_emit(buf, "%u\n", bkbd); +} +static DEVICE_ATTR_RO(keyboard_position); + +static const struct attribute * const yb9_kbdock_attrs[] = { + &dev_attr_keyboard_position.attr, + NULL, +}; +ATTRIBUTE_GROUPS(yb9_kbdock); + +static int yb9_kbdock_resume(struct device *dev) +{ + struct yb9_kbdock_data *d = dev_get_drvdata(dev); + + return yb9_kbdock_sync(d); +} +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); + +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) +{ + struct yb9_kbdock_data *d; + struct input_dev *input_dev; + int ret; + + d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->wdev = wdev; + spin_lock_init(&d->lock); + + input_dev = devm_input_allocate_device(&wdev->dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; + input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0"; + input_dev->id.bustype = BUS_HOST; + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); + + ret = input_register_device(input_dev); + if (ret) + return ret; + + d->input_dev = input_dev; + d->nb.notifier_call = yb9_kbdock_nb_call; + + ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb); + if (ret) + return ret; + + dev_set_drvdata(&wdev->dev, d); + return yb9_kbdock_sync(d); +} + +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { + { .guid_string = YB9_KBDOCK_QUERY_GUID }, + { } +}; + +static struct wmi_driver yb9_kbdock_block_driver = { + .driver = { + .name = "lenovo-yb9-kbdock", + .dev_groups = yb9_kbdock_groups, + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), + }, + .id_table = yb9_kbdock_block_id_table, + .no_singleton = true, + .probe = yb9_kbdock_block_probe, +}; + +/* ------------------------------------------------------------------ + * Event WMI driver — LENOVO_BTKBD_EVENT + * (fires the notifier chain on each WMI event) + * ------------------------------------------------------------------ */ + +static void yb9_kbdock_notify_new(struct wmi_device *wdev, + const struct wmi_buffer *data) +{ + /* + * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). + * Short-buffer guard is handled by .min_event_size below. + */ + u32 bkbd = le32_to_cpu(*(const __le32 *)data->data); + + blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL); +} + +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { + { .guid_string = YB9_KBDOCK_EVENT_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); + +static struct wmi_driver yb9_kbdock_event_driver = { + .driver = { + .name = "lenovo-yb9-kbdock-event", + }, + .id_table = yb9_kbdock_event_id_table, + .no_singleton = true, + .notify_new = yb9_kbdock_notify_new, + .min_event_size = sizeof(__le32), +}; + +/* ------------------------------------------------------------------ + * Module init / exit + * ------------------------------------------------------------------ */ + +static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = { + { + /* Lenovo Yoga Book 9 14IAH10 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + +static int __init yb9_kbdock_init(void) +{ + int ret; + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + ret = wmi_driver_register(&yb9_kbdock_event_driver); + if (ret) + return ret; + + ret = wmi_driver_register(&yb9_kbdock_block_driver); + if (ret) { + wmi_driver_unregister(&yb9_kbdock_event_driver); + return ret; + } + + return 0; +} +module_init(yb9_kbdock_init); + +static void __exit yb9_kbdock_exit(void) +{ + wmi_driver_unregister(&yb9_kbdock_block_driver); + wmi_driver_unregister(&yb9_kbdock_event_driver); +} +module_exit(yb9_kbdock_exit); + +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>"); +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c index 470d53e3c9d2..df96107d2501 100644 --- a/drivers/platform/x86/lenovo/ymc.c +++ b/drivers/platform/x86/lenovo/ymc.c @@ -8,6 +8,8 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/bits.h> #include <linux/dmi.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> @@ -20,10 +22,28 @@ #define LENOVO_YMC_QUERY_INSTANCE 0 #define LENOVO_YMC_QUERY_METHOD 0x01 +#define LENOVO_YMC_STATE_MASK GENMASK(7, 0) + static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { + { + /* + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to + * avoid userspace seeing two input nodes that both advertise + * the SW_TABLET_MODE capability. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { { .matches = { @@ -85,7 +105,9 @@ static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data) "WMI event data is not an integer\n"); goto free_obj; } - code = obj->integer.value; + + /* strip upper bits (e.g. 0x50000) on newer devices */ + code = FIELD_GET(LENOVO_YMC_STATE_MASK, obj->integer.value); if (!sparse_keymap_report_event(priv->input_dev, code, 1, true)) dev_warn(&wdev->dev, "Unknown key %d pressed\n", code); @@ -101,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) struct input_dev *input_dev; int err; + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) + return -ENODEV; + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { if (force) dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); @@ -153,6 +178,7 @@ static struct wmi_driver lenovo_ymc_driver = { .name = "lenovo-ymc", }, .id_table = lenovo_ymc_wmi_id_table, + .min_event_size = sizeof(u32), .probe = lenovo_ymc_probe, .notify = lenovo_ymc_notify, }; diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c index 69887de36c9b..1a4b2ab1f35d 100644 --- a/drivers/platform/x86/lenovo/yogabook.c +++ b/drivers/platform/x86/lenovo/yogabook.c @@ -411,8 +411,8 @@ static struct wmi_driver yogabook_wmi_driver = { .name = "yogabook-wmi", .pm = pm_sleep_ptr(&yogabook_pm_ops), }, - .no_notify_data = true, .id_table = yogabook_wmi_id_table, + .min_event_size = 0, .probe = yogabook_wmi_probe, .remove = yogabook_wmi_remove, .notify = yogabook_wmi_notify, diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index 61ef7a218a80..36ad1e269443 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -8,8 +8,11 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/array_size.h> #include <linux/bitfield.h> #include <linux/bits.h> +#include <linux/compiler_attributes.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/dev_printk.h> #include <linux/dmi.h> @@ -17,10 +20,12 @@ #include <linux/input/sparse-keymap.h> #include <linux/kernel.h> #include <linux/leds.h> +#include <linux/limits.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/string_choices.h> #include <linux/types.h> +#include <linux/unaligned.h> #include <acpi/battery.h> @@ -57,13 +62,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #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" -#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" -#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" -#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" -#define WMI_EVENT_GUID WMI_EVENT_GUID0 +#define LG_NOTIFY_TABLET_MODE_OFF 0x50 +#define LG_NOTIFY_TABLET_MODE_ON 0x51 +#define LG_NOTIFY_HOTKEY 0x80 +#define LG_NOTIFY_THERMAL 0x81 +#define LG_NOTIFY_MISC 0x82 + +#define LG_OREP_READ_EC 0 +#define LG_OREP_WRITE_EC 1 +#define LG_OREP_DEBUG 2 +#define LG_OREP_UPDATE_SYSTEM_STATE 3 +#define LG_OREP_INTERCEPT_WMI_EVENTS 4 +#define LG_OREP_WAKE_ON_LAN 6 #define SB_GGOV_METHOD "\\_SB.GGOV" #define GOV_TLED 0x2020008 @@ -78,42 +88,95 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #define WMBB_USB_CHARGE 0x10B #define WMBB_BATT_LIMIT 0x10C +#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0) +#define KBD_LED_BRIGHTNESS_OFF 0x0 +#define KBD_LED_BRIGHTNESS_HALF 0x2 +#define KBD_LED_BRIGHTNESS_FULL 0x4 +#define KBD_LED_MODE_MASK GENMASK(6, 5) +#define KBD_LED_MODE_OFF 0x0 +#define KBD_LED_MODE_ON 0x1 +#define KBD_LED_STATUS BIT(7) +#define KBD_LED_MAGIC_MASK GENMASK(15, 8) +/* Exact purpose is unknown, maybe some sort of brightness limit? */ +#define KBD_LED_MAGIC 0x05 + #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); -MODULE_ALIAS("wmi:" WMI_EVENT_GUID1); -MODULE_ALIAS("wmi:" WMI_EVENT_GUID2); -MODULE_ALIAS("wmi:" WMI_EVENT_GUID3); -MODULE_ALIAS("wmi:" WMI_METHOD_WMAB); -MODULE_ALIAS("wmi:" WMI_METHOD_WMBB); +struct lg_wmab_buffer_result { + __le32 value; + __le32 status; +} __packed; static struct platform_device *pf_device; -static struct input_dev *wmi_input_dev; - -static u32 inited; -#define INIT_INPUT_WMI_0 0x01 -#define INIT_INPUT_WMI_2 0x02 -#define INIT_INPUT_ACPI 0x04 -#define INIT_SPARSE_KEYMAP 0x80 static int battery_limit_use_wmbb; +static bool kbd_backlight_available; static struct led_classdev kbd_backlight; static enum led_brightness get_kbd_backlight_level(struct device *dev); static const struct key_entry wmi_keymap[] = { - {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ - {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ - {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ - {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing - * this key both sends an event and - * changes backlight level. - */ + /* Placeholder value send by multiple hotkeys handled by ACPI */ + { KE_IGNORE, 0x0, { KEY_UNKNOWN }}, + /* LG control panel */ + { KE_KEY, 0x70, { KEY_F15 }}, + /* Touchpad toggle */ + { KE_KEY, 0x74, { KEY_F21 }}, + /* Mute Audio, already handled by ACPI */ + { KE_IGNORE, 0x78, { KEY_MUTE }}, + /* Read mode */ + { KE_KEY, 0xf020000, { KEY_F14 }}, + /* Open settings */ + { KE_KEY, 0xf070002, { KEY_CONFIG }}, + /* Keyboard backlight - pressing this key both sends an event and changes backlight level */ + { KE_KEY, 0x10000000, { KEY_F16 }}, + /* Hotkey combination pressed */ + { KE_IGNORE, 0x30010000, { KEY_UNKNOWN }}, + /* Hotkey combination released */ + { KE_IGNORE, 0x30010001, { KEY_UNKNOWN }}, + /* Change fan mode */ + { KE_KEY, 0x30010051, { KEY_PERFORMANCE }}, + /* Disable camera */ + { KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }}, + /* Mute microphone */ + { KE_KEY, 0x40020001, { KEY_MICMUTE }}, + /* Fn-Lock */ + { KE_KEY, 0x40030001, { KEY_FN_ESC }}, {KE_END, 0} }; +static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value, + unsigned long long *result) +{ + union acpi_object objs[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = command, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = value, + }, + } + }; + struct acpi_object_list args = { + .count = ARRAY_SIZE(objs), + .pointer = objs, + }; + acpi_status status; + + status = acpi_evaluate_integer(handle, "OREP", &args, result); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + static int ggov(u32 arg0) { union acpi_object args[1]; @@ -154,30 +217,97 @@ static int ggov(u32 arg0) return res; } -static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2) +static int lg_wmab_get(struct device *dev, u32 method, u32 *result) { - union acpi_object args[3]; - acpi_status status; - struct acpi_object_list arg; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object in[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = method, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = WM_GET, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = 0, + }, + }, + }; + struct lg_wmab_buffer_result *buffer_result; + struct acpi_object_list input = { + .count = ARRAY_SIZE(in), + .pointer = in, + }; + acpi_status status; - args[0].type = ACPI_TYPE_INTEGER; - args[0].integer.value = method; - args[1].type = ACPI_TYPE_INTEGER; - args[1].integer.value = arg1; - args[2].type = ACPI_TYPE_INTEGER; - args[2].integer.value = arg2; + status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer); + if (ACPI_FAILURE(status)) + return -EIO; - arg.count = 3; - arg.pointer = args; + union acpi_object *obj __free(kfree) = buffer.pointer; - status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer); - if (ACPI_FAILURE(status)) { - dev_err(dev, "WMAB: call failed.\n"); - return NULL; + if (!obj) + return -ENODATA; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + *result = obj->integer.value; + return 0; + case ACPI_TYPE_BUFFER: + if (obj->buffer.length != sizeof(*buffer_result)) + return -EPROTO; + + buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer; + if (get_unaligned_le32(&buffer_result->status)) + return -EIO; + + *result = get_unaligned_le32(&buffer_result->value); + return 0; + default: + return -EPROTO; } +} + +static int lg_wmab_set(struct device *dev, u32 method, u32 arg) +{ + union acpi_object in[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = method, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = WM_SET, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = arg, + }, + }, + }; + struct acpi_object_list input = { + .count = ARRAY_SIZE(in), + .pointer = in, + }; + acpi_status status; - return buffer.pointer; + status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; } static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2) @@ -211,77 +341,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(union acpi_object *obj, void *context) -{ - long data = (long)context; - - pr_debug("event guid %li\n", data); - if (!obj) - return; - - if (obj->type == ACPI_TYPE_INTEGER) { - int eventcode = obj->integer.value; - struct key_entry *key; - - if (eventcode == 0x10000000) { - led_classdev_notify_brightness_hw_changed( - &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); - } else { - key = sparse_keymap_entry_from_scancode( - wmi_input_dev, eventcode); - if (key && key->type == KE_KEY) - sparse_keymap_report_entry(wmi_input_dev, - key, 1, true); - } - } - - pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, - obj->integer.value); -} - -static void wmi_input_setup(void) -{ - acpi_status status; - - wmi_input_dev = input_allocate_device(); - if (wmi_input_dev) { - wmi_input_dev->name = "LG WMI hotkeys"; - wmi_input_dev->phys = "wmi/input0"; - wmi_input_dev->id.bustype = BUS_HOST; - - if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || - input_register_device(wmi_input_dev)) { - pr_info("Cannot initialize input device"); - input_free_device(wmi_input_dev); - return; - } - - inited |= INIT_SPARSE_KEYMAP; - status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, - (void *)0); - if (ACPI_SUCCESS(status)) - inited |= INIT_INPUT_WMI_0; - - status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, - (void *)2); - if (ACPI_SUCCESS(status)) - inited |= INIT_INPUT_WMI_2; - } else { - pr_info("Cannot allocate input device"); - } -} - -static void acpi_notify(struct acpi_device *device, u32 event) -{ - acpi_handle_debug(device->handle, "notify: %d\n", event); -} - static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count) { unsigned long value; - union acpi_object *r; int ret; ret = kstrtoul(buffer, 10, &value); @@ -290,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, if (value >= 3) return -EINVAL; - r = lg_wmab(dev, WM_FAN_MODE, WM_SET, - FIELD_PREP(FAN_MODE_LOWER, value) | - FIELD_PREP(FAN_MODE_UPPER, value)); - kfree(r); + ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) | + FIELD_PREP(FAN_MODE_UPPER, value)); + if (ret < 0) + return ret; return count; } @@ -301,22 +365,14 @@ 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 mode; - union acpi_object *r; - - r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0); - if (!r) - return -EIO; - - if (r->type != ACPI_TYPE_INTEGER) { - kfree(r); - return -EIO; - } + u32 mode; + int ret; - mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value); - kfree(r); + ret = lg_wmab_get(dev, WM_FAN_MODE, &mode); + if (ret < 0) + return ret; - return sysfs_emit(buffer, "%d\n", mode); + return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode)); } static ssize_t usb_charge_store(struct device *dev, @@ -366,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, const char *buffer, size_t count) { bool value; - union acpi_object *r; int ret; ret = kstrtobool(buffer, &value); if (ret) return ret; - r = lg_wmab(dev, WM_READER_MODE, WM_SET, value); - if (!r) - return -EIO; + ret = lg_wmab_set(dev, WM_READER_MODE, value); + if (ret < 0) + return ret; - kfree(r); return count; } static ssize_t reader_mode_show(struct device *dev, struct device_attribute *attr, char *buffer) { - unsigned int status; - union acpi_object *r; - - r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0); - if (!r) - return -EIO; - - if (r->type != ACPI_TYPE_INTEGER) { - kfree(r); - return -EIO; - } - - status = !!r->integer.value; + u32 status; + int ret; - kfree(r); + ret = lg_wmab_get(dev, WM_READER_MODE, &status); + if (ret < 0) + return ret; - return sysfs_emit(buffer, "%d\n", status); + return sysfs_emit(buffer, "%d\n", !!status); } static ssize_t fn_lock_store(struct device *dev, @@ -408,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, const char *buffer, size_t count) { bool value; - union acpi_object *r; int ret; ret = kstrtobool(buffer, &value); if (ret) return ret; - r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value); - if (!r) - return -EIO; + ret = lg_wmab_set(dev, WM_FN_LOCK, value); + if (ret < 0) + return ret; - kfree(r); return count; } static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buffer) { - unsigned int status; - union acpi_object *r; - - r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0); - if (!r) - return -EIO; - - if (r->type != ACPI_TYPE_BUFFER) { - kfree(r); - return -EIO; - } + u32 status; + int ret; - status = !!r->buffer.pointer[0]; - kfree(r); + ret = lg_wmab_get(dev, WM_FN_LOCK, &status); + if (ret < 0) + return ret; - return sysfs_emit(buffer, "%d\n", status); + return sysfs_emit(buffer, "%d\n", !!status); } static ssize_t charge_control_end_threshold_store(struct device *dev, @@ -458,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, if (value == 100 || value == 80) { union acpi_object *r; - if (battery_limit_use_wmbb) + if (battery_limit_use_wmbb) { r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value); - else - r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value); - if (!r) - return -EIO; + if (!r) + return -EIO; + + kfree(r); + } else { + ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value); + if (ret < 0) + return ret; + } - kfree(r); return count; } @@ -476,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, struct device_attribute *attr, char *buf) { - unsigned int status; union acpi_object *r; + u32 status; + int ret; if (battery_limit_use_wmbb) { r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0); @@ -490,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, } status = r->buffer.pointer[0x10]; + kfree(r); } else { - r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0); - if (!r) - return -EIO; - - if (r->type != ACPI_TYPE_INTEGER) { - kfree(r); - return -EIO; - } - - status = r->integer.value; + ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status); + if (ret < 0) + return ret; } - kfree(r); + if (status != 80 && status != 100) status = 0; @@ -568,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { static void tpad_led_set(struct led_classdev *cdev, enum led_brightness brightness) { - union acpi_object *r; - - r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF); - kfree(r); + lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF); } static enum led_brightness tpad_led_get(struct led_classdev *cdev) @@ -584,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); static void kbd_backlight_set(struct led_classdev *cdev, enum led_brightness brightness) { - u32 val; - union acpi_object *r; + /* Must always be written */ + u32 value = KBD_LED_STATUS; + u32 mode, bright; - val = 0x22; - if (brightness <= LED_OFF) - val = 0; - if (brightness >= LED_FULL) - val = 0x24; - r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val); - kfree(r); + if (brightness <= LED_OFF) { + mode = KBD_LED_MODE_OFF; + bright = KBD_LED_BRIGHTNESS_OFF; + } else { + mode = KBD_LED_MODE_ON; + if (brightness >= LED_FULL) + bright = KBD_LED_BRIGHTNESS_FULL; + else + bright = KBD_LED_BRIGHTNESS_HALF; + } + + value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright); + value |= FIELD_PREP(KBD_LED_MODE_MASK, mode); + + lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value); } static enum led_brightness get_kbd_backlight_level(struct device *dev) { - union acpi_object *r; - int val; + u32 value; + int ret; - r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0); + ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value); + if (ret < 0) + return LED_OFF; - if (!r) + if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC) return LED_OFF; - if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { - kfree(r); + if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF) return LED_OFF; - } - switch (r->buffer.pointer[0] & 0x27) { - case 0x24: - val = LED_FULL; - break; - case 0x22: - val = LED_HALF; - break; + switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) { + case KBD_LED_BRIGHTNESS_FULL: + return LED_FULL; + case KBD_LED_BRIGHTNESS_HALF: + return LED_HALF; default: - val = LED_OFF; + return LED_OFF; } - - kfree(r); - - return val; } static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) @@ -634,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); -static void wmi_input_destroy(void) +static struct platform_driver pf_driver = { + .driver = { + .name = PLATFORM_NAME, + } +}; + +static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data) { - if (inited & INIT_INPUT_WMI_2) - wmi_remove_notify_handler(WMI_EVENT_GUID2); + union acpi_object objs[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = value, + }, + } + }; + struct acpi_object_list args = { + .count = ARRAY_SIZE(objs), + .pointer = objs, + }; + unsigned long long result; + acpi_status status; - if (inited & INIT_INPUT_WMI_0) - wmi_remove_notify_handler(WMI_EVENT_GUID0); + status = acpi_evaluate_integer(handle, "_WED", &args, &result); + if (ACPI_FAILURE(status)) + return -EIO; - if (inited & INIT_SPARSE_KEYMAP) - input_unregister_device(wmi_input_dev); + if (result > U32_MAX) + return -EPROTO; - inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); + *data = result; + + return 0; } -static struct platform_driver pf_driver = { - .driver = { - .name = PLATFORM_NAME, +static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data) +{ + unsigned int kbd_brightness; + + switch (value) { + case LG_NOTIFY_HOTKEY: + sparse_keymap_report_event(input_dev, data, 1, true); + break; + case LG_NOTIFY_THERMAL: + /* Currently not supported */ + break; + case LG_NOTIFY_MISC: + switch (data) { + case 0x10000000: + if (!kbd_backlight_available) + break; + + kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); + led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness); + break; + default: + sparse_keymap_report_event(input_dev, data, 1, true); + } + break; + default: + break; + } +} + +static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context) +{ + struct input_dev *input_dev = context; + u32 data; + int ret; + + switch (value) { + case LG_NOTIFY_TABLET_MODE_OFF: + case LG_NOTIFY_TABLET_MODE_ON: + /* Already handled by intel-hid */ + return; + case LG_NOTIFY_HOTKEY: + case LG_NOTIFY_THERMAL: + case LG_NOTIFY_MISC: + ret = lg_laptop_get_event_data(handle, value, &data); + if (ret < 0) { + dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret); + return; + } + + dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data); + + lg_laptop_handle_input_event(input_dev, value, data); + return; + default: + dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value); } }; +static void lg_laptop_remove_notify_handler(void *context) +{ + acpi_handle handle = context; + + acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler); +} + +static void lg_laptop_reenable_wmi_events(void *context) +{ + acpi_handle handle = context; + unsigned long long dummy; + + lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy); +} + +static int lg_laptop_input_init(struct device *dev, acpi_handle handle) +{ + struct input_dev *input_dev; + unsigned long long result; + acpi_status status; + int ret; + + if (!acpi_has_method(handle, "_WED")) + return 0; + + input_dev = devm_input_allocate_device(dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "LG WMI hotkeys"; + input_dev->phys = "wmi/input0"; + input_dev->id.bustype = BUS_HOST; + ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL); + if (ret < 0) + return ret; + + ret = input_register_device(input_dev); + if (ret < 0) + return ret; + + status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler, + input_dev); + if (ACPI_FAILURE(status)) + return -EIO; + + ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle); + if (ret < 0) + return ret; + + if (acpi_has_method(handle, "OREP")) { + ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result); + if (ret < 0) + return ret; + if (result) + return -EIO; + + ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle); + if (ret < 0) + return ret; + } + + return 0; +} + static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address, size_t size, u64 value) { @@ -764,13 +935,13 @@ static void lg_laptop_remove_address_space_handler(void *data) &lg_laptop_address_space_handler); } -static int acpi_add(struct acpi_device *device) +static int acpi_probe(struct platform_device *pdev) { struct platform_device_info pdev_info = { - .fwnode = acpi_fwnode_handle(device), .name = PLATFORM_NAME, .id = PLATFORM_DEVID_NONE, }; + struct acpi_device *device; acpi_status status; int ret; const char *product; @@ -779,13 +950,19 @@ static int acpi_add(struct acpi_device *device) if (pf_device) return 0; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + + pdev_info.fwnode = acpi_fwnode_handle(device), + status = acpi_install_address_space_handler(device->handle, LG_ADDRESS_SPACE_ID, &lg_laptop_address_space_handler, - NULL, &device->dev); + NULL, &pdev->dev); if (ACPI_FAILURE(status)) return -ENODEV; - ret = devm_add_action_or_reset(&device->dev, lg_laptop_remove_address_space_handler, + ret = devm_add_action_or_reset(&pdev->dev, lg_laptop_remove_address_space_handler, device); if (ret < 0) return ret; @@ -859,15 +1036,23 @@ static int acpi_add(struct acpi_device *device) if (year >= 2019) battery_limit_use_wmbb = 1; + /* LEDs are optional */ + ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); + if (ret < 0) + kbd_backlight_available = false; + else + kbd_backlight_available = true; + + devm_led_classdev_register(&pdev->dev, &tpad_led); + + ret = lg_laptop_input_init(&pdev->dev, device->handle); + if (ret < 0) + goto out_platform_device; + ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); if (ret) goto out_platform_device; - /* LEDs are optional */ - led_classdev_register(&pf_device->dev, &kbd_backlight); - led_classdev_register(&pf_device->dev, &tpad_led); - - wmi_input_setup(); battery_hook_register(&battery_hook); return 0; @@ -879,15 +1064,11 @@ out_platform_registered: return ret; } -static void acpi_remove(struct acpi_device *device) +static void acpi_remove(struct platform_device *pdev) { sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); - led_classdev_unregister(&tpad_led); - led_classdev_unregister(&kbd_backlight); - battery_hook_unregister(&battery_hook); - wmi_input_destroy(); platform_device_unregister(pf_device); pf_device = NULL; platform_driver_unregister(&pf_driver); @@ -899,34 +1080,13 @@ static const struct acpi_device_id device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, device_ids); -static struct acpi_driver acpi_driver = { - .name = "LG Gram Laptop Support", - .class = "lg-laptop", - .ids = device_ids, - .ops = { - .add = acpi_add, - .remove = acpi_remove, - .notify = acpi_notify, - }, +static struct platform_driver acpi_driver = { + .probe = acpi_probe, + .remove = acpi_remove, + .driver = { + .name = "LG Gram Laptop Support", + .acpi_match_table = device_ids, + }, }; -static int __init acpi_init(void) -{ - int result; - - result = acpi_bus_register_driver(&acpi_driver); - if (result < 0) { - pr_debug("Error registering driver\n"); - return -ENODEV; - } - - return 0; -} - -static void __exit acpi_exit(void) -{ - acpi_bus_unregister_driver(&acpi_driver); -} - -module_init(acpi_init); -module_exit(acpi_exit); +module_platform_driver(acpi_driver); diff --git a/drivers/platform/x86/meraki-mx100.c b/drivers/platform/x86/meraki-mx100.c index 8c5276d98512..9f4caa1f3a92 100644 --- a/drivers/platform/x86/meraki-mx100.c +++ b/drivers/platform/x86/meraki-mx100.c @@ -20,16 +20,11 @@ #include <linux/input-event-codes.h> #include <linux/io.h> #include <linux/kernel.h> +#include <linux/mfd/lpc_ich.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> -#define TINK_GPIO_DRIVER_NAME "gpio_ich" - -static const struct software_node gpio_ich_node = { - .name = TINK_GPIO_DRIVER_NAME, -}; - /* LEDs */ static const struct software_node tink_gpio_leds_node = { .name = "meraki-mx100-leds", @@ -38,7 +33,7 @@ static const struct software_node tink_gpio_leds_node = { static const struct property_entry tink_internet_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:internet"), PROPERTY_ENTRY_STRING("linux,default-trigger", "default-on"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 11, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 11, GPIO_ACTIVE_LOW), { } }; @@ -50,7 +45,7 @@ static const struct software_node tink_internet_led_node = { static const struct property_entry tink_lan2_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan2"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 18, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 18, GPIO_ACTIVE_HIGH), { } }; @@ -62,7 +57,7 @@ static const struct software_node tink_lan2_led_node = { static const struct property_entry tink_lan3_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan3"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 20, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 20, GPIO_ACTIVE_HIGH), { } }; @@ -74,7 +69,7 @@ static const struct software_node tink_lan3_led_node = { static const struct property_entry tink_lan4_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan4"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 22, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 22, GPIO_ACTIVE_HIGH), { } }; @@ -86,7 +81,7 @@ static const struct software_node tink_lan4_led_node = { static const struct property_entry tink_lan5_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan5"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 23, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 23, GPIO_ACTIVE_HIGH), { } }; @@ -98,7 +93,7 @@ static const struct software_node tink_lan5_led_node = { static const struct property_entry tink_lan6_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan6"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 32, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 32, GPIO_ACTIVE_HIGH), { } }; @@ -110,7 +105,7 @@ static const struct software_node tink_lan6_led_node = { static const struct property_entry tink_lan7_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan7"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 34, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 34, GPIO_ACTIVE_HIGH), { } }; @@ -122,7 +117,7 @@ static const struct software_node tink_lan7_led_node = { static const struct property_entry tink_lan8_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan8"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 35, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 35, GPIO_ACTIVE_HIGH), { } }; @@ -134,7 +129,7 @@ static const struct software_node tink_lan8_led_node = { static const struct property_entry tink_lan9_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan9"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 36, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 36, GPIO_ACTIVE_HIGH), { } }; @@ -146,7 +141,7 @@ static const struct software_node tink_lan9_led_node = { static const struct property_entry tink_lan10_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan10"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 37, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 37, GPIO_ACTIVE_HIGH), { } }; @@ -158,7 +153,7 @@ static const struct software_node tink_lan10_led_node = { static const struct property_entry tink_lan11_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:lan11"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 48, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 48, GPIO_ACTIVE_HIGH), { } }; @@ -170,7 +165,7 @@ static const struct software_node tink_lan11_led_node = { static const struct property_entry tink_ha_green_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:ha"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 16, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 16, GPIO_ACTIVE_LOW), { } }; @@ -182,7 +177,7 @@ static const struct software_node tink_ha_green_led_node = { static const struct property_entry tink_ha_orange_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:orange:ha"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 7, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 7, GPIO_ACTIVE_LOW), { } }; @@ -194,7 +189,7 @@ static const struct software_node tink_ha_orange_led_node = { static const struct property_entry tink_usb_green_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:green:usb"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 21, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 21, GPIO_ACTIVE_LOW), { } }; @@ -206,7 +201,7 @@ static const struct software_node tink_usb_green_led_node = { static const struct property_entry tink_usb_orange_led_props[] = { PROPERTY_ENTRY_STRING("label", "mx100:orange:usb"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 19, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 19, GPIO_ACTIVE_LOW), { } }; @@ -230,7 +225,7 @@ static const struct software_node tink_gpio_keys_node = { static const struct property_entry tink_reset_key_props[] = { PROPERTY_ENTRY_U32("linux,code", KEY_RESTART), PROPERTY_ENTRY_STRING("label", "Reset"), - PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 60, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("gpios", &lpc_ich_gpio_swnode, 60, GPIO_ACTIVE_LOW), PROPERTY_ENTRY_U32("linux,input-type", EV_KEY), PROPERTY_ENTRY_U32("debounce-interval", 100), { } @@ -243,7 +238,6 @@ static const struct software_node tink_reset_key_node = { }; static const struct software_node *tink_swnodes[] = { - &gpio_ich_node, /* LEDs nodes */ &tink_gpio_leds_node, &tink_internet_led_node, @@ -348,3 +342,4 @@ MODULE_AUTHOR("Chris Blake <chrisrblake93@gmail.com>"); MODULE_DESCRIPTION("Cisco Meraki MX100 Platform Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:meraki-mx100"); +MODULE_IMPORT_NS("LPC_ICH"); diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index f19504dbf164..566dfc73cb67 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -23,6 +23,7 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/device-id/dmi.h> #include <linux/platform_device.h> #include <linux/seq_file.h> #include <linux/string.h> @@ -823,6 +824,7 @@ static struct msi_ec_conf CONF9 __initdata = { static const char * const ALLOWED_FW_10[] __initconst = { "1582EMS1.107", // GF66 11UC + "1583EMS1.109", // Pulse GL66 12UEK NULL }; @@ -1053,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = { static const char * const ALLOWED_FW_13[] __initconst = { "1594EMS1.109", // MSI Prestige 16 Studio A13VE + "17L1EMS1.107", // MSI Katana GF76 11UEK NULL }; @@ -1128,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = { }, }; +static const char * const ALLOWED_FW_14[] __initconst = { + "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) + "182LIMS1.108", // Vector A18 HX A9WHG + "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG + "182KIMS1.113", // Raider A18 HX A7VIG + NULL +}; + +static struct msi_ec_conf CONF14 __initdata = { + .allowed_fw = ALLOWED_FW_14, + .charge_control = { + .address = 0xd7, + .offset_start = 0x8a, + .offset_end = 0x80, + .range_min = 0x8a, + .range_max = 0xe4, + }, + .webcam = { + .address = 0x2e, + .block_address = 0x2f, + .bit = 1, + }, + .fn_win_swap = { + .address = 0xe8, + .bit = 4, + }, + .cooler_boost = { + .address = 0x98, + .bit = 7, + }, + .shift_mode = { + .address = 0xd2, + .modes = { + { SM_ECO_NAME, 0xc2 }, + { SM_COMFORT_NAME, 0xc1 }, + { SM_TURBO_NAME, 0xc4 }, + MSI_EC_MODE_NULL + }, + }, + .super_battery = { + .address = 0xeb, + .mask = 0x0f, + }, + .fan_mode = { + .address = 0xd4, + .modes = { + { FM_AUTO_NAME, 0x0d }, + { FM_SILENT_NAME, 0x1d }, + { FM_ADVANCED_NAME, 0x8d }, + MSI_EC_MODE_NULL + }, + }, + .cpu = { + .rt_temp_address = 0x68, + .rt_fan_speed_address = 0x71, + .rt_fan_speed_base_min = 0x00, + .rt_fan_speed_base_max = 0x96, + .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, + .bs_fan_speed_base_min = 0x00, + .bs_fan_speed_base_max = 0x0f, + }, + .gpu = { + .rt_temp_address = 0x80, + .rt_fan_speed_address = 0x89, + }, + .leds = { + .micmute_led_address = 0x2c, + .mute_led_address = 0x2d, + .bit = 1, + }, + .kbd_bl = { + .bl_mode_address = MSI_EC_ADDR_UNSUPP, + .bl_modes = { 0x00, 0x08 }, + .max_mode = 1, + .bl_state_address = MSI_EC_ADDR_UNSUPP, + .state_base_value = 0x80, + .max_state = 3, + }, +}; + static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF0, &CONF1, @@ -1143,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF11, &CONF12, &CONF13, + &CONF14, NULL }; diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 4a7ac85c4db4..c9db750fe5ae 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -46,6 +46,12 @@ enum msi_scancodes { WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ + /* MSI Claw keys */ + CLAW_KEY_VOLUMEDOWN = 0x21, + CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ + CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ + CLAW_KEY_VOLUMEUP = 0x32, + CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ }; static struct key_entry msi_wmi_keymap[] = { { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, @@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + /* These are MSI Claw keys, used for MSI M-Center in Windows */ + { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, + + /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, + { KE_IGNORE, CLAW_KEY_QUICK_LONG }, + { KE_IGNORE, CLAW_KEY_VOLUMEUP }, + { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, + { KE_END, 0 } }; @@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = { static void msi_wmi_notify(union acpi_object *obj, void *context) { - struct key_entry *key; + struct key_entry *key = NULL; + int eventcode = 0; + + if (!obj) + return; - if (obj && obj->type == ACPI_TYPE_INTEGER) { - int eventcode = obj->integer.value; + switch (obj->type) { + case ACPI_TYPE_INTEGER: + eventcode = obj->integer.value; pr_debug("Eventcode: 0x%x\n", eventcode); - key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, - eventcode); - if (!key) { - pr_info("Unknown key pressed - %x\n", eventcode); + break; + case ACPI_TYPE_BUFFER: + /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ + if (obj->buffer.length < 2) return; - } - if (event_wmi->quirk_last_pressed) { - ktime_t cur = ktime_get_real(); - ktime_t diff = ktime_sub(cur, last_pressed); - /* Ignore event if any event happened in a 50 ms - timeframe -> Key press may result in 10-20 GPEs */ - if (ktime_to_us(diff) < 1000 * 50) { - pr_debug("Suppressed key event 0x%X - " - "Last press was %lld us ago\n", - key->code, ktime_to_us(diff)); - return; - } - last_pressed = cur; - } + /* pointer[0] is key ID, pointer[1] is active state. We don't get release + * events, so ignore the active state and treat as autorelease. + */ + eventcode = obj->buffer.pointer[0]; + pr_debug("Eventcode: 0x%x\n", eventcode); + break; + default: + pr_info("Unknown event received\n"); + return; + } + + key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); - if (key->type == KE_KEY && - /* Brightness is served via acpi video driver */ - (backlight || - (key->code != MSI_KEY_BRIGHTNESSUP && - key->code != MSI_KEY_BRIGHTNESSDOWN))) { - pr_debug("Send key: 0x%X - Input layer keycode: %d\n", - key->code, key->keycode); - sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, - true); + if (!key) { + pr_info("Unknown key pressed - 0x%x\n", eventcode); + return; + } + + if (event_wmi->quirk_last_pressed) { + ktime_t cur = ktime_get_real(); + ktime_t diff = ktime_sub(cur, last_pressed); + /* Ignore event if any event happened in a 50 ms + * timeframe -> Key press may result in 10-20 GPEs + */ + if (ktime_to_us(diff) < 1000 * 50) { + pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", + key->code, ktime_to_us(diff)); + return; } - } else - pr_info("Unknown event received\n"); + last_pressed = cur; + } + + /* Brightness is served via acpi video driver */ + if (key->type == KE_KEY && + (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || + key->code == MSI_KEY_BRIGHTNESSDOWN))) + return; + + pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); + sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); } static int __init msi_wmi_backlight_setup(void) diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c index 9a457956025a..dbc5e35ec38b 100644 --- a/drivers/platform/x86/mxm-wmi.c +++ b/drivers/platform/x86/mxm-wmi.c @@ -80,15 +80,3 @@ bool mxm_wmi_supported(void) return guid_valid; } EXPORT_SYMBOL_GPL(mxm_wmi_supported); - -static int __init mxm_wmi_init(void) -{ - return 0; -} - -static void __exit mxm_wmi_exit(void) -{ -} - -module_init(mxm_wmi_init); -module_exit(mxm_wmi_exit); diff --git a/drivers/platform/x86/nvidia-wmi-ec-backlight.c b/drivers/platform/x86/nvidia-wmi-ec-backlight.c index 1b572c90c76e..b4eebfb96fe5 100644 --- a/drivers/platform/x86/nvidia-wmi-ec-backlight.c +++ b/drivers/platform/x86/nvidia-wmi-ec-backlight.c @@ -5,7 +5,6 @@ #include <linux/acpi.h> #include <linux/backlight.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_data/x86/nvidia-wmi-ec-backlight.h> #include <linux/types.h> diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c index 6d4a53a2ed60..34bb17fca148 100644 --- a/drivers/platform/x86/oxpec.c +++ b/drivers/platform/x86/oxpec.c @@ -208,6 +208,13 @@ static const struct dmi_system_id dmi_table[] = { { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER SUPER X"), + }, + .driver_data = (void *)oxp_g1_a, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 i"), }, .driver_data = (void *)oxp_g1_i, @@ -282,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)oxp_x1, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"), + }, + .driver_data = (void *)oxp_fly, + }, {}, }; diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index d923ddaa4849..19d194ff37ca 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -159,8 +159,6 @@ MODULE_LICENSE("GPL"); #define ECO_MODE_ON 0x80 #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" -#define ACPI_PCC_DEVICE_NAME "Hotkey" -#define ACPI_PCC_CLASS "pcc" #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" @@ -183,9 +181,9 @@ enum SINF_BITS { SINF_NUM_BATTERIES = 0, }; /* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */ -static int acpi_pcc_hotkey_add(struct acpi_device *device); -static void acpi_pcc_hotkey_remove(struct acpi_device *device); -static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event); +static int acpi_pcc_hotkey_probe(struct platform_device *pdev); +static void acpi_pcc_hotkey_remove(struct platform_device *pdev); +static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data); static const struct acpi_device_id pcc_device_ids[] = { { "MAT0012", 0}, @@ -201,16 +199,14 @@ static int acpi_pcc_hotkey_resume(struct device *dev); #endif static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume); -static struct acpi_driver acpi_pcc_driver = { - .name = ACPI_PCC_DRIVER_NAME, - .class = ACPI_PCC_CLASS, - .ids = pcc_device_ids, - .ops = { - .add = acpi_pcc_hotkey_add, - .remove = acpi_pcc_hotkey_remove, - .notify = acpi_pcc_hotkey_notify, - }, - .drv.pm = &acpi_pcc_hotkey_pm, +static struct platform_driver acpi_pcc_driver = { + .probe = acpi_pcc_hotkey_probe, + .remove = acpi_pcc_hotkey_remove, + .driver = { + .name = ACPI_PCC_DRIVER_NAME, + .acpi_match_table = pcc_device_ids, + .pm = &acpi_pcc_hotkey_pm, + }, }; static const struct key_entry panasonic_keymap[] = { @@ -248,11 +244,11 @@ struct pcc_acpi { int ac_brightness; int dc_brightness; int current_brightness; - u32 *sinf; struct acpi_device *device; struct input_dev *input_dev; struct backlight_device *backlight; struct platform_device *platform; + u32 sinf[] __counted_by(num_sifr); }; /* @@ -362,7 +358,16 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) } else pr_err("Invalid HKEY.SINF data\n"); } - pcc->sinf[hkey->package.count] = -1; + /* + * pcc->sinf[] has pcc->num_sifr elements (valid indices + * 0..num_sifr-1). On DSDTs where SINF's package count equals + * num_sifr exactly -- the off-by-one case probe()'s num_sifr++ + * already allocates a spare element for -- there is no room left + * for this trailing sentinel; nothing reads it back, so just skip + * the write rather than running one element past the flex array. + */ + if (hkey->package.count < pcc->num_sifr) + pcc->sinf[hkey->package.count] = -1; end: kfree(buffer.pointer); @@ -869,9 +874,9 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) pr_err("Unknown hotkey event: 0x%04llx\n", result); } -static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event) +static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data) { - struct pcc_acpi *pcc = acpi_driver_data(device); + struct pcc_acpi *pcc = data; switch (event) { case HKEY_NOTIFY: @@ -891,7 +896,7 @@ static void pcc_optd_notify(acpi_handle handle, u32 event, void *data) set_optd_power_state(0); } -static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node) +static void pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node) { acpi_status status; acpi_handle handle; @@ -904,10 +909,7 @@ static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node) pcc_optd_notify, pcc); if (ACPI_FAILURE(status)) pr_err("Failed to register notify on %s\n", node); - } else - return -ENODEV; - - return 0; + } } static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node) @@ -968,14 +970,7 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc) #ifdef CONFIG_PM_SLEEP static int acpi_pcc_hotkey_resume(struct device *dev) { - struct pcc_acpi *pcc; - - if (!dev) - return -EINVAL; - - pcc = acpi_driver_data(to_acpi_device(dev)); - if (!pcc) - return -EINVAL; + struct pcc_acpi *pcc = acpi_driver_data(ACPI_COMPANION(dev)); if (pcc->num_sifr > SINF_MUTE) acpi_pcc_write_sset(pcc, SINF_MUTE, pcc->mute); @@ -991,14 +986,16 @@ static int acpi_pcc_hotkey_resume(struct device *dev) } #endif -static int acpi_pcc_hotkey_add(struct acpi_device *device) +static int acpi_pcc_hotkey_probe(struct platform_device *pdev) { struct backlight_properties props; + struct acpi_device *device; struct pcc_acpi *pcc; int num_sifr, result; + device = ACPI_COMPANION(&pdev->dev); if (!device) - return -EINVAL; + return -ENODEV; num_sifr = acpi_pcc_get_sqty(device); @@ -1017,29 +1014,21 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) */ num_sifr++; - pcc = kzalloc_obj(struct pcc_acpi); + pcc = kzalloc_flex(*pcc, sinf, num_sifr); if (!pcc) { pr_err("Couldn't allocate mem for pcc"); return -ENOMEM; } - pcc->sinf = kcalloc(num_sifr + 1, sizeof(u32), GFP_KERNEL); - if (!pcc->sinf) { - result = -ENOMEM; - goto out_hotkey; - } - + pcc->num_sifr = num_sifr; pcc->device = device; pcc->handle = device->handle; - pcc->num_sifr = num_sifr; device->driver_data = pcc; - strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_PCC_CLASS); result = acpi_pcc_init_input(pcc); if (result) { pr_err("Error installing keyinput handler\n"); - goto out_sinf; + goto out_hotkey; } if (!acpi_pcc_retrieve_biosdata(pcc)) { @@ -1083,19 +1072,25 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) if (result) goto out_backlight; + result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_pcc_hotkey_notify, pcc); + if (result) + goto out_sysfs; + /* optical drive initialization */ if (ACPI_SUCCESS(check_optd_present())) { pcc->platform = platform_device_register_simple("panasonic", PLATFORM_DEVID_NONE, NULL, 0); if (IS_ERR(pcc->platform)) { result = PTR_ERR(pcc->platform); - goto out_sysfs; + goto out_notify_handler; } result = device_create_file(&pcc->platform->dev, &dev_attr_cdpower); - pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); if (result) goto out_platform; + + pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); } else { pcc->platform = NULL; } @@ -1105,34 +1100,37 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) out_platform: platform_device_unregister(pcc->platform); +out_notify_handler: + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_pcc_hotkey_notify); out_sysfs: sysfs_remove_group(&device->dev.kobj, &pcc_attr_group); out_backlight: backlight_device_unregister(pcc->backlight); out_input: input_unregister_device(pcc->input_dev); -out_sinf: - kfree(pcc->sinf); out_hotkey: + device->driver_data = NULL; kfree(pcc); return result; } -static void acpi_pcc_hotkey_remove(struct acpi_device *device) +static void acpi_pcc_hotkey_remove(struct platform_device *pdev) { + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct pcc_acpi *pcc = acpi_driver_data(device); - if (!device || !pcc) - return; - i8042_remove_filter(panasonic_i8042_filter); if (pcc->platform) { + pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); device_remove_file(&pcc->platform->dev, &dev_attr_cdpower); platform_device_unregister(pcc->platform); } - pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD"); + + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_pcc_hotkey_notify); sysfs_remove_group(&device->dev.kobj, &pcc_attr_group); @@ -1140,8 +1138,9 @@ static void acpi_pcc_hotkey_remove(struct acpi_device *device) input_unregister_device(pcc->input_dev); - kfree(pcc->sinf); + device->driver_data = NULL; + kfree(pcc); } -module_acpi_driver(acpi_pcc_driver); +module_platform_driver(acpi_pcc_driver); diff --git a/drivers/platform/x86/pcengines-apuv2.c b/drivers/platform/x86/pcengines-apuv2.c index 3b086863c6ac..8ac5f719c5a3 100644 --- a/drivers/platform/x86/pcengines-apuv2.c +++ b/drivers/platform/x86/pcengines-apuv2.c @@ -262,7 +262,7 @@ static struct platform_device * __init apu_create_pdev(const char *name, .id = PLATFORM_DEVID_NONE, .data = data, .size_data = size, - .fwnode = software_node_fwnode(swnode), + .swnode = swnode, }; struct platform_device *pdev; int err; @@ -294,7 +294,8 @@ static int __init apu_board_init(void) } apu_gpio_pdev = apu_create_pdev(AMD_FCH_GPIO_DRIVER_NAME, - id->driver_data, sizeof(struct amd_fch_gpio_pdata), NULL); + id->driver_data, sizeof(struct amd_fch_gpio_pdata), + &apu2_gpiochip_node); err = PTR_ERR_OR_ZERO(apu_gpio_pdev); if (err) goto err_unregister_swnodes; diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c index 48c2a0e59d18..046933f6d1b6 100644 --- a/drivers/platform/x86/pmc_atom.c +++ b/drivers/platform/x86/pmc_atom.c @@ -570,8 +570,8 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent) /* Data for PCI driver interface used by pci_match_id() call below */ static const struct pci_device_id pmc_pci_ids[] = { - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_VLV_PMC), (kernel_ulong_t)&byt_data }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CHT_PMC), (kernel_ulong_t)&cht_data }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_VLV_PMC), .driver_data = (kernel_ulong_t)&byt_data }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CHT_PMC), .driver_data = (kernel_ulong_t)&cht_data }, {} }; diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c index acb58518be37..186a243a012d 100644 --- a/drivers/platform/x86/quickstart.c +++ b/drivers/platform/x86/quickstart.c @@ -16,7 +16,6 @@ #include <linux/init.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/platform_device.h> diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c index e5cb348e3a39..cc82ef502002 100644 --- a/drivers/platform/x86/redmi-wmi.c +++ b/drivers/platform/x86/redmi-wmi.c @@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = { {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, - /* Keyboard backlight */ - {KE_IGNORE, 0x00000501, {}}, - {KE_IGNORE, 0x00800501, {}}, - {KE_IGNORE, 0x00050501, {}}, - {KE_IGNORE, 0x000a0501, {}}, + /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */ + {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}}, + {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}}, + {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}}, + {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}}, /* Xiaomi G Command Center */ {KE_KEY, 0x00010a01, {KEY_VENDOR}}, - /* OEM preset power mode */ - {KE_IGNORE, 0x00011601, {}}, - {KE_IGNORE, 0x00021601, {}}, - {KE_IGNORE, 0x00031601, {}}, - {KE_IGNORE, 0x00041601, {}}, + /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */ + {KE_KEY, 0x00011601, {KEY_PERFORMANCE}}, + {KE_KEY, 0x00021601, {KEY_PERFORMANCE}}, + {KE_KEY, 0x00031601, {KEY_PERFORMANCE}}, + {KE_KEY, 0x00041601, {KEY_PERFORMANCE}}, - /* Fn Lock state */ - {KE_IGNORE, 0x00000701, {}}, - {KE_IGNORE, 0x00010701, {}}, + /* Fn Lock state: 1=on 0=off */ + {KE_KEY, 0x00000701, {KEY_FN_ESC}}, + {KE_KEY, 0x00010701, {KEY_FN_ESC}}, /* Fn+`/1/2/3/4 */ {KE_KEY, 0x00011101, {KEY_F13}}, @@ -141,6 +141,7 @@ static struct wmi_driver redmi_wmi_driver = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .id_table = redmi_wmi_id_table, + .min_event_size = 32, .probe = redmi_wmi_probe, .notify = redmi_wmi_notify, .no_singleton = true, diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c index 755cb82bdb60..99f72b204a51 100644 --- a/drivers/platform/x86/samsung-galaxybook.c +++ b/drivers/platform/x86/samsung-galaxybook.c @@ -53,7 +53,7 @@ struct samsung_galaxybook { void *i8042_filter_ptr; struct work_struct block_recording_hotkey_work; - struct input_dev *camera_lens_cover_switch; + struct input_dev *input; struct acpi_battery_hook battery_hook; @@ -197,6 +197,9 @@ static const guid_t performance_mode_guid = #define GB_ACPI_NOTIFY_DEVICE_ON_TABLE 0x6c #define GB_ACPI_NOTIFY_DEVICE_OFF_TABLE 0x6d #define GB_ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE 0x70 +#define GB_ACPI_NOTIFY_HOTKEY_KBD_BACKLIGHT 0x7d +#define GB_ACPI_NOTIFY_HOTKEY_MICMUTE 0x6e +#define GB_ACPI_NOTIFY_HOTKEY_CAMERA 0x6f #define GB_KEY_KBD_BACKLIGHT_KEYDOWN 0x2c #define GB_KEY_KBD_BACKLIGHT_KEYUP 0xac @@ -859,13 +862,29 @@ static int block_recording_acpi_set(struct samsung_galaxybook *galaxybook, const if (err) return err; - input_report_switch(galaxybook->camera_lens_cover_switch, + input_report_switch(galaxybook->input, SW_CAMERA_LENS_COVER, value ? 1 : 0); - input_sync(galaxybook->camera_lens_cover_switch); + input_sync(galaxybook->input); return 0; } +static int galaxybook_input_init(struct samsung_galaxybook *galaxybook) +{ + galaxybook->input = devm_input_allocate_device(&galaxybook->platform->dev); + if (!galaxybook->input) + return -ENOMEM; + + galaxybook->input->name = "Samsung Galaxy Book Camera Lens Cover"; + galaxybook->input->phys = DRIVER_NAME "/input0"; + galaxybook->input->id.bustype = BUS_HOST; + + input_set_capability(galaxybook->input, EV_KEY, KEY_MICMUTE); + input_set_capability(galaxybook->input, EV_SW, SW_CAMERA_LENS_COVER); + + return input_register_device(galaxybook->input); +} + static int galaxybook_block_recording_init(struct samsung_galaxybook *galaxybook) { bool value; @@ -887,24 +906,8 @@ static int galaxybook_block_recording_init(struct samsung_galaxybook *galaxybook return GB_NOT_SUPPORTED; } - galaxybook->camera_lens_cover_switch = - devm_input_allocate_device(&galaxybook->platform->dev); - if (!galaxybook->camera_lens_cover_switch) - return -ENOMEM; - - galaxybook->camera_lens_cover_switch->name = "Samsung Galaxy Book Camera Lens Cover"; - galaxybook->camera_lens_cover_switch->phys = DRIVER_NAME "/input0"; - galaxybook->camera_lens_cover_switch->id.bustype = BUS_HOST; - - input_set_capability(galaxybook->camera_lens_cover_switch, EV_SW, SW_CAMERA_LENS_COVER); - - err = input_register_device(galaxybook->camera_lens_cover_switch); - if (err) - return err; - - input_report_switch(galaxybook->camera_lens_cover_switch, - SW_CAMERA_LENS_COVER, value ? 1 : 0); - input_sync(galaxybook->camera_lens_cover_switch); + input_report_switch(galaxybook->input, SW_CAMERA_LENS_COVER, value ? 1 : 0); + input_sync(galaxybook->input); return 0; } @@ -1260,6 +1263,25 @@ static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *data) if (galaxybook->has_performance_mode) platform_profile_cycle(); break; + case GB_ACPI_NOTIFY_HOTKEY_KBD_BACKLIGHT: + if (galaxybook->has_kbd_backlight) + schedule_work(&galaxybook->kbd_backlight_hotkey_work); + break; + case GB_ACPI_NOTIFY_HOTKEY_MICMUTE: + input_report_key(galaxybook->input, KEY_MICMUTE, 1); + input_sync(galaxybook->input); + input_report_key(galaxybook->input, KEY_MICMUTE, 0); + input_sync(galaxybook->input); + break; + case GB_ACPI_NOTIFY_HOTKEY_CAMERA: + if (galaxybook->has_block_recording) { + schedule_work(&galaxybook->block_recording_hotkey_work); + } else { + input_report_switch(galaxybook->input, SW_CAMERA_LENS_COVER, + !test_bit(SW_CAMERA_LENS_COVER, galaxybook->input->sw)); + input_sync(galaxybook->input); + } + break; default: dev_warn(&galaxybook->platform->dev, "unknown ACPI notification event: 0x%x\n", event); @@ -1392,6 +1414,11 @@ static int galaxybook_probe(struct platform_device *pdev) return dev_err_probe(&galaxybook->platform->dev, err, "failed to initialize kbd_backlight\n"); + err = galaxybook_input_init(galaxybook); + if (err) + return dev_err_probe(&galaxybook->platform->dev, err, + "failed to initialize input device\n"); + err = galaxybook_fw_attrs_init(galaxybook); if (err) return dev_err_probe(&galaxybook->platform->dev, err, @@ -1411,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = { { "SAM0428" }, { "SAM0429" }, { "SAM0430" }, + { "SAMB430" }, {} }; MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); diff --git a/drivers/platform/x86/sel3350-platform.c b/drivers/platform/x86/sel3350-platform.c index 02e2081e2333..f3a314235632 100644 --- a/drivers/platform/x86/sel3350-platform.c +++ b/drivers/platform/x86/sel3350-platform.c @@ -9,6 +9,8 @@ */ #include <linux/acpi.h> +#include <linux/array_size.h> +#include <linux/err.h> #include <linux/gpio/consumer.h> #include <linux/gpio/machine.h> #include <linux/leds.h> @@ -30,19 +32,82 @@ #define SEL_PS_B_DETECT "sel_ps_b_detect" #define SEL_PS_B_GOOD "sel_ps_b_good" +#define AUX_LED_GRN1 "sel_aux_led_grn1" +#define AUX_LED_GRN2 "sel_aux_led_grn2" +#define AUX_LED_GRN3 "sel_aux_led_grn3" +#define AUX_LED_GRN4 "sel_aux_led_grn4" +#define ALARM_STATE_USER "sel_alarm_state_user" +#define ENABLE_STATE_USER "sel_enable_state_user" +#define AUX_LED_RED1 "sel_aux_led_red1" +#define AUX_LED_RED2 "sel_aux_led_red2" +#define AUX_LED_RED3 "sel_aux_led_red3" +#define AUX_LED_RED4 "sel_aux_led_red4" + +static const char *const sel3350_leds_gpio_names[] = { + AUX_LED_GRN1, + AUX_LED_GRN2, + AUX_LED_GRN3, + AUX_LED_GRN4, + ALARM_STATE_USER, + ENABLE_STATE_USER, + AUX_LED_RED1, + AUX_LED_RED2, + AUX_LED_RED3, + AUX_LED_RED4, +}; + /* LEDs */ -static const struct gpio_led sel3350_leds[] = { - { .name = "sel:green:aux1" }, - { .name = "sel:green:aux2" }, - { .name = "sel:green:aux3" }, - { .name = "sel:green:aux4" }, - { .name = "sel:red:alarm" }, +static struct gpio_led sel3350_leds[] = { + { .name = "sel:green:aux1", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:green:aux2", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:green:aux3", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:green:aux4", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:red:alarm", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, { .name = "sel:green:enabled", - .default_state = LEDS_GPIO_DEFSTATE_ON }, - { .name = "sel:red:aux1" }, - { .name = "sel:red:aux2" }, - { .name = "sel:red:aux3" }, - { .name = "sel:red:aux4" }, + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:red:aux1", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:red:aux2", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:red:aux3", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, + { .name = "sel:red:aux4", + .default_state = LEDS_GPIO_DEFSTATE_KEEP, + .retain_state_suspended = 1, + .retain_state_shutdown = 1, + }, }; static const struct gpio_led_platform_data sel3350_leds_pdata = { @@ -50,25 +115,6 @@ static const struct gpio_led_platform_data sel3350_leds_pdata = { .leds = sel3350_leds, }; -/* Map GPIOs to LEDs */ -static struct gpiod_lookup_table sel3350_leds_table = { - .dev_id = "leds-gpio", - .table = { - GPIO_LOOKUP_IDX(BXT_NW, 49, NULL, 0, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_NW, 50, NULL, 1, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_NW, 51, NULL, 2, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_NW, 52, NULL, 3, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_W, 20, NULL, 4, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_W, 21, NULL, 5, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_SW, 37, NULL, 6, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_SW, 38, NULL, 7, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_SW, 39, NULL, 8, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(BXT_SW, 40, NULL, 9, GPIO_ACTIVE_HIGH), - {}, - } -}; - -/* Map GPIOs to power supplies */ static struct gpiod_lookup_table sel3350_gpios_table = { .dev_id = B2093_GPIO_ACPI_ID ":00", .table = { @@ -76,6 +122,16 @@ static struct gpiod_lookup_table sel3350_gpios_table = { GPIO_LOOKUP(BXT_NW, 45, SEL_PS_A_GOOD, GPIO_ACTIVE_LOW), GPIO_LOOKUP(BXT_NW, 46, SEL_PS_B_DETECT, GPIO_ACTIVE_LOW), GPIO_LOOKUP(BXT_NW, 47, SEL_PS_B_GOOD, GPIO_ACTIVE_LOW), + GPIO_LOOKUP(BXT_NW, 49, AUX_LED_GRN1, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_NW, 50, AUX_LED_GRN2, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_NW, 51, AUX_LED_GRN3, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_NW, 52, AUX_LED_GRN4, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_W, 20, ALARM_STATE_USER, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_W, 21, ENABLE_STATE_USER, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_SW, 37, AUX_LED_RED1, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_SW, 38, AUX_LED_RED2, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_SW, 39, AUX_LED_RED3, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP(BXT_SW, 40, AUX_LED_RED4, GPIO_ACTIVE_HIGH), {}, } }; @@ -149,6 +205,7 @@ struct sel3350_data { static int sel3350_probe(struct platform_device *pdev) { int rs; + int i; struct sel3350_data *sel3350; struct power_supply_config ps_cfg = {}; @@ -158,9 +215,19 @@ static int sel3350_probe(struct platform_device *pdev) platform_set_drvdata(pdev, sel3350); - gpiod_add_lookup_table(&sel3350_leds_table); gpiod_add_lookup_table(&sel3350_gpios_table); + for (i = 0; i < ARRAY_SIZE(sel3350_leds); ++i) { + sel3350_leds[i].gpiod = devm_gpiod_get(&pdev->dev, + sel3350_leds_gpio_names[i], + GPIOD_ASIS); + if (IS_ERR_OR_NULL(sel3350_leds[i].gpiod)) { + rs = -EPROBE_DEFER; + goto err_gpio_loop; + } + gpiod_set_consumer_name(sel3350_leds[i].gpiod, sel3350_leds[i].name); + } + sel3350->leds_pdev = platform_device_register_data( NULL, "leds-gpio", @@ -209,11 +276,15 @@ static int sel3350_probe(struct platform_device *pdev) return 0; +err_gpio_loop: + while (i--) + devm_gpiod_put(&pdev->dev, sel3350_leds[i].gpiod); + goto err_platform; + err_ps: platform_device_unregister(sel3350->leds_pdev); err_platform: gpiod_remove_lookup_table(&sel3350_gpios_table); - gpiod_remove_lookup_table(&sel3350_leds_table); return rs; } @@ -224,7 +295,6 @@ static void sel3350_remove(struct platform_device *pdev) platform_device_unregister(sel3350->leds_pdev); gpiod_remove_lookup_table(&sel3350_gpios_table); - gpiod_remove_lookup_table(&sel3350_leds_table); } static const struct acpi_device_id sel3350_device_ids[] = { diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c index 1a369334f9cb..3e89fcdd45f7 100644 --- a/drivers/platform/x86/serial-multi-instantiate.c +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -307,6 +307,15 @@ static void smi_remove(struct platform_device *pdev) smi_devs_unregister(smi); } +static const struct smi_node aw88399_hda = { + .instances = { + { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, + { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, + {} + }, + .bus_type = SMI_AUTO_DETECT, +}; + static const struct smi_node bsg1160_data = { .instances = { { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, @@ -405,6 +414,7 @@ static const struct smi_node tas2781_hda = { * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). */ static const struct acpi_device_id smi_acpi_ids[] = { + { "AWDZ8399", (unsigned long)&aw88399_hda }, { "BSG1160", (unsigned long)&bsg1160_data }, { "BSG2150", (unsigned long)&bsg2150_data }, { "CSC3551", (unsigned long)&cs35l41_hda }, diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index d3e7a52c22a7..0bf4d3054e37 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -178,8 +178,7 @@ enum sony_nc_rfkill { static int sony_rfkill_handle; static struct rfkill *sony_rfkill_devices[N_SONY_RFKILL]; static int sony_rfkill_address[N_SONY_RFKILL] = {0x300, 0x500, 0x700, 0x900}; -static int sony_nc_rfkill_setup(struct acpi_device *device, - unsigned int handle); +static int sony_nc_rfkill_setup(struct device *dev, unsigned int handle); static void sony_nc_rfkill_cleanup(void); static void sony_nc_rfkill_update(void); @@ -435,7 +434,7 @@ static void sony_laptop_report_input_event(u8 event) dprintk("unknown input event %.2x\n", event); } -static int sony_laptop_setup_input(struct acpi_device *acpi_device) +static int sony_laptop_setup_input(struct device *parent) { struct input_dev *jog_dev; struct input_dev *key_dev; @@ -468,7 +467,7 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device) key_dev->name = "Sony Vaio Keys"; key_dev->id.bustype = BUS_ISA; key_dev->id.vendor = PCI_VENDOR_ID_SONY; - key_dev->dev.parent = &acpi_device->dev; + key_dev->dev.parent = parent; /* Initialize the Input Drivers: special keys */ input_set_capability(key_dev, EV_MSC, MSC_SCAN); @@ -497,7 +496,7 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device) jog_dev->name = "Sony Vaio Jogdial"; jog_dev->id.bustype = BUS_ISA; jog_dev->id.vendor = PCI_VENDOR_ID_SONY; - jog_dev->dev.parent = &acpi_device->dev; + jog_dev->dev.parent = parent; input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE); input_set_capability(jog_dev, EV_REL, REL_WHEEL); @@ -1176,7 +1175,7 @@ enum event_types { KILLSWITCH, GFX_SWITCH }; -static void sony_nc_notify(struct acpi_device *device, u32 event) +static void sony_nc_notify(acpi_handle ah, u32 event, void *data) { u32 real_ev = event; u8 ev_type = 0; @@ -1265,7 +1264,7 @@ static void sony_nc_notify(struct acpi_device *device, u32 event) ev_type = HOTKEY; sony_laptop_report_input_event(real_ev); } - acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class, + acpi_bus_generate_netlink_event("sony/hotkey", dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev); } @@ -1287,7 +1286,7 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level, /* * ACPI device */ -static void sony_nc_function_setup(struct acpi_device *device, +static void sony_nc_function_setup(struct device *dev, struct platform_device *pf_device) { unsigned int i, result, bitmask, arg; @@ -1360,7 +1359,7 @@ static void sony_nc_function_setup(struct acpi_device *device, break; case 0x0124: case 0x0135: - result = sony_nc_rfkill_setup(device, handle); + result = sony_nc_rfkill_setup(dev, handle); if (result) pr_err("couldn't set up rfkill support (%d)\n", result); @@ -1600,8 +1599,7 @@ static const struct rfkill_ops sony_rfkill_ops = { .set_block = sony_nc_rfkill_set, }; -static int sony_nc_setup_rfkill(struct acpi_device *device, - enum sony_nc_rfkill nc_type) +static int sony_nc_setup_rfkill(struct device *parent, enum sony_nc_rfkill nc_type) { int err; struct rfkill *rfk; @@ -1631,8 +1629,7 @@ static int sony_nc_setup_rfkill(struct acpi_device *device, return -EINVAL; } - rfk = rfkill_alloc(name, &device->dev, type, - &sony_rfkill_ops, (void *)nc_type); + rfk = rfkill_alloc(name, parent, type, &sony_rfkill_ops, (void *)nc_type); if (!rfk) return -ENOMEM; @@ -1692,8 +1689,7 @@ static void sony_nc_rfkill_update(void) } } -static int sony_nc_rfkill_setup(struct acpi_device *device, - unsigned int handle) +static int sony_nc_rfkill_setup(struct device *parent, unsigned int handle) { u64 offset; int i; @@ -1734,18 +1730,18 @@ static int sony_nc_rfkill_setup(struct acpi_device *device, dprintk("Radio devices, found 0x%.2x\n", buffer[i]); if (buffer[i] == 0 && !sony_rfkill_devices[SONY_WIFI]) - sony_nc_setup_rfkill(device, SONY_WIFI); + sony_nc_setup_rfkill(parent, SONY_WIFI); if (buffer[i] == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH]) - sony_nc_setup_rfkill(device, SONY_BLUETOOTH); + sony_nc_setup_rfkill(parent, SONY_BLUETOOTH); if (((0xf0 & buffer[i]) == 0x20 || (0xf0 & buffer[i]) == 0x50) && !sony_rfkill_devices[SONY_WWAN]) - sony_nc_setup_rfkill(device, SONY_WWAN); + sony_nc_setup_rfkill(parent, SONY_WWAN); if (buffer[i] == 0x30 && !sony_rfkill_devices[SONY_WIMAX]) - sony_nc_setup_rfkill(device, SONY_WIMAX); + sony_nc_setup_rfkill(parent, SONY_WIMAX); } return 0; } @@ -3149,15 +3145,18 @@ static void sony_nc_backlight_cleanup(void) backlight_device_unregister(sony_bl_props.dev); } -static int sony_nc_add(struct acpi_device *device) +static int sony_nc_probe(struct platform_device *pdev) { + struct acpi_device *device; acpi_status status; int result = 0; struct sony_nc_value *item; - sony_nc_acpi_device = device; - strscpy(acpi_device_class(device), "sony/hotkey"); + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + sony_nc_acpi_device = device; sony_nc_acpi_handle = device->handle; /* read device status */ @@ -3184,7 +3183,7 @@ static int sony_nc_add(struct acpi_device *device) } } - result = sony_laptop_setup_input(device); + result = sony_laptop_setup_input(&pdev->dev); if (result) { pr_err("Unable to create input devices\n"); goto outplatform; @@ -3201,7 +3200,7 @@ static int sony_nc_add(struct acpi_device *device) /* retrieve the available handles */ result = sony_nc_handles_setup(sony_pf_device); if (!result) - sony_nc_function_setup(device, sony_pf_device); + sony_nc_function_setup(&pdev->dev, sony_pf_device); } if (acpi_video_get_backlight_type() == acpi_backlight_vendor) @@ -3244,6 +3243,11 @@ static int sony_nc_add(struct acpi_device *device) } } + result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + sony_nc_notify, NULL); + if (result) + goto out_sysfs; + pr_info("SNC setup done.\n"); return 0; @@ -3266,10 +3270,13 @@ outwalk: return result; } -static void sony_nc_remove(struct acpi_device *device) +static void sony_nc_remove(struct platform_device *pdev) { struct sony_nc_value *item; + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, sony_nc_notify); + sony_nc_backlight_cleanup(); sony_nc_acpi_device = NULL; @@ -3297,16 +3304,14 @@ static const struct acpi_device_id sony_nc_device_ids[] = { {"", 0}, }; -static struct acpi_driver sony_nc_driver = { - .name = SONY_NC_DRIVER_NAME, - .class = SONY_NC_CLASS, - .ids = sony_nc_device_ids, - .ops = { - .add = sony_nc_add, - .remove = sony_nc_remove, - .notify = sony_nc_notify, - }, - .drv.pm = &sony_nc_pm, +static struct platform_driver sony_nc_driver = { + .probe = sony_nc_probe, + .remove = sony_nc_remove, + .driver = { + .name = SONY_NC_DRIVER_NAME, + .acpi_match_table = sony_nc_device_ids, + .pm = &sony_nc_pm, + }, }; /*********** SPIC (SNY6001) Device ***********/ @@ -4276,9 +4281,9 @@ end: /* * Disable the spic device by calling its _DIS method */ -static int sony_pic_disable(struct acpi_device *device) +static int sony_pic_disable(struct device *dev) { - acpi_status ret = acpi_evaluate_object(device->handle, "_DIS", NULL, + acpi_status ret = acpi_evaluate_object(ACPI_HANDLE(dev), "_DIS", NULL, NULL); if (ACPI_FAILURE(ret) && ret != AE_NOT_FOUND) @@ -4294,7 +4299,7 @@ static int sony_pic_disable(struct acpi_device *device) * * Call _SRS to set current resources */ -static int sony_pic_enable(struct acpi_device *device, +static int sony_pic_enable(struct device *dev, struct sony_pic_ioport *ioport, struct sony_pic_irq *irq) { acpi_status status; @@ -4376,7 +4381,7 @@ static int sony_pic_enable(struct acpi_device *device, /* Attempt to set the resource */ dprintk("Evaluating _SRS\n"); - status = acpi_set_current_resources(device->handle, &buffer); + status = acpi_set_current_resources(ACPI_HANDLE(dev), &buffer); /* check for total failure */ if (ACPI_FAILURE(status)) { @@ -4465,12 +4470,12 @@ found: * ACPI driver * *****************/ -static void sony_pic_remove(struct acpi_device *device) +static void sony_pic_remove(struct platform_device *pdev) { struct sony_pic_ioport *io, *tmp_io; struct sony_pic_irq *irq, *tmp_irq; - if (sony_pic_disable(device)) { + if (sony_pic_disable(&pdev->dev)) { pr_err("Couldn't disable device\n"); return; } @@ -4504,14 +4509,18 @@ static void sony_pic_remove(struct acpi_device *device) dprintk(SONY_PIC_DRIVER_NAME " removed.\n"); } -static int sony_pic_add(struct acpi_device *device) +static int sony_pic_probe(struct platform_device *pdev) { - int result; struct sony_pic_ioport *io, *tmp_io; struct sony_pic_irq *irq, *tmp_irq; + struct acpi_device *device; + int result; + + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; spic_dev.acpi_dev = device; - strscpy(acpi_device_class(device), "sony/hotkey"); sony_pic_detect_device_type(&spic_dev); mutex_init(&spic_dev.lock); @@ -4523,7 +4532,7 @@ static int sony_pic_add(struct acpi_device *device) } /* setup input devices and helper fifo */ - result = sony_laptop_setup_input(device); + result = sony_laptop_setup_input(&pdev->dev); if (result) { pr_err("Unable to create input devices\n"); goto err_free_resources; @@ -4593,7 +4602,7 @@ static int sony_pic_add(struct acpi_device *device) } /* set resource status _SRS */ - result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq); + result = sony_pic_enable(&pdev->dev, spic_dev.cur_ioport, spic_dev.cur_irq); if (result) { pr_err("Couldn't enable device\n"); goto err_free_irq; @@ -4616,7 +4625,7 @@ err_remove_pf: sony_pf_remove(); err_disable_device: - sony_pic_disable(device); + sony_pic_disable(&pdev->dev); err_free_irq: free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev); @@ -4652,15 +4661,14 @@ err_free_resources: #ifdef CONFIG_PM_SLEEP static int sony_pic_suspend(struct device *dev) { - if (sony_pic_disable(to_acpi_device(dev))) + if (sony_pic_disable(dev)) return -ENXIO; return 0; } static int sony_pic_resume(struct device *dev) { - sony_pic_enable(to_acpi_device(dev), - spic_dev.cur_ioport, spic_dev.cur_irq); + sony_pic_enable(dev, spic_dev.cur_ioport, spic_dev.cur_irq); return 0; } #endif @@ -4672,15 +4680,14 @@ static const struct acpi_device_id sony_pic_device_ids[] = { {"", 0}, }; -static struct acpi_driver sony_pic_driver = { - .name = SONY_PIC_DRIVER_NAME, - .class = SONY_PIC_CLASS, - .ids = sony_pic_device_ids, - .ops = { - .add = sony_pic_add, - .remove = sony_pic_remove, - }, - .drv.pm = &sony_pic_pm, +static struct platform_driver sony_pic_driver = { + .probe = sony_pic_probe, + .remove = sony_pic_remove, + .driver = { + .name = SONY_PIC_DRIVER_NAME, + .acpi_match_table = sony_pic_device_ids, + .pm = &sony_pic_pm, + }, }; static const struct dmi_system_id sonypi_dmi_table[] __initconst = { @@ -4706,7 +4713,7 @@ static int __init sony_laptop_init(void) int result; if (!no_spic && dmi_check_system(sonypi_dmi_table)) { - result = acpi_bus_register_driver(&sony_pic_driver); + result = platform_driver_register(&sony_pic_driver); if (result) { pr_err("Unable to register SPIC driver\n"); goto out; @@ -4714,7 +4721,7 @@ static int __init sony_laptop_init(void) spic_drv_registered = 1; } - result = acpi_bus_register_driver(&sony_nc_driver); + result = platform_driver_register(&sony_nc_driver); if (result) { pr_err("Unable to register SNC driver\n"); goto out_unregister_pic; @@ -4724,16 +4731,16 @@ static int __init sony_laptop_init(void) out_unregister_pic: if (spic_drv_registered) - acpi_bus_unregister_driver(&sony_pic_driver); + platform_driver_unregister(&sony_pic_driver); out: return result; } static void __exit sony_laptop_exit(void) { - acpi_bus_unregister_driver(&sony_nc_driver); + platform_driver_unregister(&sony_nc_driver); if (spic_drv_registered) - acpi_bus_unregister_driver(&sony_pic_driver); + platform_driver_unregister(&sony_pic_driver); } module_init(sony_laptop_init); diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index 3da753b3d00d..dd7b1b07c316 100644 --- a/drivers/platform/x86/system76_acpi.c +++ b/drivers/platform/x86/system76_acpi.c @@ -18,6 +18,7 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/pci_ids.h> +#include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/sysfs.h> #include <linux/types.h> @@ -644,11 +645,10 @@ static void input_key(struct system76_data *data, unsigned int code) } // Handle ACPI notification -static void system76_notify(struct acpi_device *acpi_dev, u32 event) +static void system76_notify(acpi_handle handle, u32 event, void *context) { - struct system76_data *data; + struct system76_data *data = context; - data = acpi_driver_data(acpi_dev); switch (event) { case 0x80: kb_led_hotkey_hardware(data); @@ -671,16 +671,23 @@ static void system76_notify(struct acpi_device *acpi_dev, u32 event) } } -// Add a System76 ACPI device -static int system76_add(struct acpi_device *acpi_dev) +// Probe a System76 platform device +static int system76_probe(struct platform_device *pdev) { + struct acpi_device *acpi_dev; struct system76_data *data; int err; - data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL); + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - acpi_dev->driver_data = data; + + platform_set_drvdata(pdev, data); + data->acpi_dev = acpi_dev; // Some models do not run open EC firmware. Check for an ACPI method @@ -696,7 +703,7 @@ static int system76_add(struct acpi_device *acpi_dev) data->ap_led.brightness_set_blocking = ap_led_set; data->ap_led.max_brightness = 1; data->ap_led.default_trigger = "rfkill-none"; - err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led); + err = devm_led_classdev_register(&pdev->dev, &data->ap_led); if (err) return err; @@ -740,24 +747,29 @@ static int system76_add(struct acpi_device *acpi_dev) } if (data->kbled_type != KBLED_NONE) { - err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led); + err = devm_led_classdev_register(&pdev->dev, &data->kb_led); if (err) return err; } - data->input = devm_input_allocate_device(&acpi_dev->dev); + data->input = devm_input_allocate_device(&pdev->dev); if (!data->input) return -ENOMEM; data->input->name = "System76 ACPI Hotkeys"; data->input->phys = "system76_acpi/input0"; data->input->id.bustype = BUS_HOST; - data->input->dev.parent = &acpi_dev->dev; + data->input->dev.parent = &pdev->dev; input_set_capability(data->input, EV_KEY, KEY_SCREENLOCK); err = input_register_device(data->input); if (err) - goto error; + return err; + + err = acpi_dev_install_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY, + system76_notify, data); + if (err) + return err; if (data->has_open_ec) { err = system76_get_object(data, "NFAN", &data->nfan); @@ -768,7 +780,7 @@ static int system76_add(struct acpi_device *acpi_dev) if (err) goto error; - data->therm = devm_hwmon_device_register_with_info(&acpi_dev->dev, + data->therm = devm_hwmon_device_register_with_info(&pdev->dev, "system76_acpi", data, &thermal_chip_info, NULL); err = PTR_ERR_OR_ZERO(data->therm); if (err) @@ -784,15 +796,14 @@ error: kfree(data->ntmp); kfree(data->nfan); } + acpi_dev_remove_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY, system76_notify); return err; } -// Remove a System76 ACPI device -static void system76_remove(struct acpi_device *acpi_dev) +// Remove a System76 platform device +static void system76_remove(struct platform_device *pdev) { - struct system76_data *data; - - data = acpi_driver_data(acpi_dev); + struct system76_data *data = platform_get_drvdata(pdev); if (data->has_open_ec) { system76_battery_exit(); @@ -800,23 +811,21 @@ static void system76_remove(struct acpi_device *acpi_dev) kfree(data->ntmp); } - devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led); - devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, system76_notify); system76_get(data, "FINI"); } -static struct acpi_driver system76_driver = { - .name = "System76 ACPI Driver", - .class = "hotkey", - .ids = device_ids, - .ops = { - .add = system76_add, - .remove = system76_remove, - .notify = system76_notify, +static struct platform_driver system76_driver = { + .probe = system76_probe, + .remove = system76_remove, + .driver = { + .name = "System76 ACPI Driver", + .acpi_match_table = device_ids, }, }; -module_acpi_driver(system76_driver); +module_platform_driver(system76_driver); MODULE_DESCRIPTION("System76 ACPI Driver"); MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>"); diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index a7b4b6c8e549..0e842c55dcc5 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -232,9 +232,9 @@ static int topstar_acpi_fncx_switch(struct acpi_device *device, bool state) return 0; } -static void topstar_acpi_notify(struct acpi_device *device, u32 event) +static void topstar_acpi_notify(acpi_handle handle, u32 event, void *data) { - struct topstar_laptop *topstar = acpi_driver_data(device); + struct topstar_laptop *topstar = data; static bool dup_evnt[2]; bool *dup; @@ -285,8 +285,9 @@ static const struct dmi_system_id topstar_dmi_ids[] = { {} }; -static int topstar_acpi_add(struct acpi_device *device) +static int topstar_acpi_probe(struct platform_device *pdev) { + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct topstar_laptop *topstar; int err; @@ -296,9 +297,8 @@ static int topstar_acpi_add(struct acpi_device *device) if (!topstar) return -ENOMEM; - strscpy(acpi_device_name(device), "Topstar TPSACPI"); - strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); - device->driver_data = topstar; + platform_set_drvdata(pdev, topstar); + topstar->device = device; err = topstar_acpi_init(topstar); @@ -313,14 +313,21 @@ static int topstar_acpi_add(struct acpi_device *device) if (err) goto err_platform_exit; + err = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + topstar_acpi_notify, topstar); + if (err) + goto err_input_exit; + if (led_workaround) { err = topstar_led_init(topstar); if (err) - goto err_input_exit; + goto err_notify_handler_exit; } return 0; +err_notify_handler_exit: + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, topstar_acpi_notify); err_input_exit: topstar_input_exit(topstar); err_platform_exit: @@ -332,13 +339,15 @@ err_free: return err; } -static void topstar_acpi_remove(struct acpi_device *device) +static void topstar_acpi_remove(struct platform_device *pdev) { - struct topstar_laptop *topstar = acpi_driver_data(device); + struct topstar_laptop *topstar = platform_get_drvdata(pdev); if (led_workaround) topstar_led_exit(topstar); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, topstar_acpi_notify); topstar_input_exit(topstar); topstar_platform_exit(topstar); topstar_acpi_exit(topstar); @@ -353,14 +362,12 @@ static const struct acpi_device_id topstar_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, topstar_device_ids); -static struct acpi_driver topstar_acpi_driver = { - .name = "Topstar laptop ACPI driver", - .class = TOPSTAR_LAPTOP_CLASS, - .ids = topstar_device_ids, - .ops = { - .add = topstar_acpi_add, - .remove = topstar_acpi_remove, - .notify = topstar_acpi_notify, +static struct platform_driver topstar_acpi_driver = { + .probe = topstar_acpi_probe, + .remove = topstar_acpi_remove, + .driver = { + .name = "Topstar laptop ACPI driver", + .acpi_match_table = topstar_device_ids, }, }; @@ -372,7 +379,7 @@ static int __init topstar_laptop_init(void) if (ret < 0) return ret; - ret = acpi_bus_register_driver(&topstar_acpi_driver); + ret = platform_driver_register(&topstar_acpi_driver); if (ret < 0) goto err_driver_unreg; @@ -386,7 +393,7 @@ err_driver_unreg: static void __exit topstar_laptop_exit(void) { - acpi_bus_unregister_driver(&topstar_acpi_driver); + platform_driver_unregister(&topstar_acpi_driver); platform_driver_unregister(&topstar_platform_driver); } diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 18fb558115aa..a0b8060836d0 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -44,6 +44,7 @@ #include <linux/rfkill.h> #include <linux/hwmon.h> #include <linux/iio/iio.h> +#include <linux/platform_device.h> #include <linux/toshiba.h> #include <acpi/battery.h> #include <acpi/video.h> @@ -223,6 +224,7 @@ struct toshiba_acpi_dev { unsigned int cooling_method_supported:1; unsigned int battery_charge_mode_supported:1; unsigned int sysfs_created:1; + unsigned int notify_handler_installed:1; unsigned int special_functions; bool kbd_event_generated; @@ -2503,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work) LED_FULL : LED_OFF); /* Emulate the keyboard backlight event */ - acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class, - dev_name(&toshiba_acpi->acpi_dev->dev), + acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev), 0x92, 0); } @@ -3193,14 +3194,79 @@ static void print_supported_features(struct toshiba_acpi_dev *dev) pr_cont("\n"); } -static void toshiba_acpi_remove(struct acpi_device *acpi_dev) +static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data) { - struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); + struct toshiba_acpi_dev *dev = data; + struct acpi_device *acpi_dev = dev->acpi_dev; + + switch (event) { + case 0x80: /* Hotkeys and some system events */ + /* + * Machines with this WMI GUID aren't supported due to bugs in + * their AML. + * + * Return silently to avoid triggering a netlink event. + */ + if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) + return; + toshiba_acpi_process_hotkeys(dev); + break; + case 0x81: /* Dock events */ + case 0x82: + case 0x83: + pr_info("Dock event received %x\n", event); + break; + case 0x88: /* Thermal events */ + pr_info("Thermal event received\n"); + break; + case 0x8f: /* LID closed */ + case 0x90: /* LID is closed and Dock has been ejected */ + break; + case 0x8c: /* SATA power events */ + case 0x8b: + pr_info("SATA power event received %x\n", event); + break; + case 0x92: /* Keyboard backlight mode changed */ + 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 0x8e: /* Power button pressed */ + break; + case 0x85: /* Unknown */ + case 0x8d: /* Unknown */ + case 0x94: /* Unknown */ + case 0x95: /* Unknown */ + default: + pr_info("Unknown event received %x\n", event); + break; + } + + acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev), + event, (event == 0x80) ? + dev->last_key_event : 0); +} + +static void toshiba_acpi_remove(struct platform_device *pdev) +{ + struct toshiba_acpi_dev *dev = platform_get_drvdata(pdev); misc_deregister(&dev->miscdev); remove_toshiba_proc_entries(dev); + if (dev->notify_handler_installed) + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, + toshiba_acpi_notify); + #if IS_ENABLED(CONFIG_HWMON) if (dev->hwmon_device) hwmon_device_unregister(dev->hwmon_device); @@ -3240,6 +3306,8 @@ static void toshiba_acpi_remove(struct acpi_device *acpi_dev) if (toshiba_acpi) toshiba_acpi = NULL; + dev_set_drvdata(&dev->acpi_dev->dev, NULL); + kfree(dev); } @@ -3302,8 +3370,9 @@ static const struct dmi_system_id toshiba_dmi_quirks[] __initconst = { { } }; -static int toshiba_acpi_add(struct acpi_device *acpi_dev) +static int toshiba_acpi_probe(struct platform_device *pdev) { + struct acpi_device *acpi_dev; struct toshiba_acpi_dev *dev; const char *hci_method; u32 dummy; @@ -3312,6 +3381,10 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) if (toshiba_acpi) return -EBUSY; + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + pr_info("Toshiba Laptop ACPI Extras version %s\n", TOSHIBA_ACPI_VERSION); @@ -3337,7 +3410,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) return ret; } - acpi_dev->driver_data = dev; + platform_set_drvdata(pdev, dev); dev_set_drvdata(&acpi_dev->dev, dev); /* Query the BIOS for supported features */ @@ -3368,7 +3441,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) dev->led_dev.max_brightness = 1; dev->led_dev.brightness_set = toshiba_illumination_set; dev->led_dev.brightness_get = toshiba_illumination_get; - led_classdev_register(&acpi_dev->dev, &dev->led_dev); + led_classdev_register(&pdev->dev, &dev->led_dev); } toshiba_eco_mode_available(dev); @@ -3377,7 +3450,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) dev->eco_led.max_brightness = 1; dev->eco_led.brightness_set = toshiba_eco_mode_set_status; dev->eco_led.brightness_get = toshiba_eco_mode_get_status; - led_classdev_register(&dev->acpi_dev->dev, &dev->eco_led); + led_classdev_register(&pdev->dev, &dev->eco_led); } toshiba_kbd_illum_available(dev); @@ -3393,7 +3466,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) dev->kbd_led.max_brightness = 1; dev->kbd_led.brightness_set = toshiba_kbd_backlight_set; dev->kbd_led.brightness_get = toshiba_kbd_backlight_get; - led_classdev_register(&dev->acpi_dev->dev, &dev->kbd_led); + led_classdev_register(&pdev->dev, &dev->kbd_led); } ret = toshiba_touchpad_get(dev, &dummy); @@ -3401,7 +3474,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) toshiba_accelerometer_available(dev); if (dev->accelerometer_supported) { - dev->indio_dev = iio_device_alloc(&acpi_dev->dev, sizeof(*dev)); + dev->indio_dev = iio_device_alloc(&pdev->dev, sizeof(*dev)); if (!dev->indio_dev) { pr_err("Unable to allocate iio device\n"); goto iio_error; @@ -3450,7 +3523,7 @@ iio_error: #if IS_ENABLED(CONFIG_HWMON) if (dev->fan_rpm_supported) { dev->hwmon_device = hwmon_device_register_with_info( - &dev->acpi_dev->dev, "toshiba_acpi_sensors", NULL, + &pdev->dev, "toshiba_acpi_sensors", NULL, &toshiba_acpi_hwmon_chip_info, NULL); if (IS_ERR(dev->hwmon_device)) { dev->hwmon_device = NULL; @@ -3477,6 +3550,13 @@ iio_error: } dev->sysfs_created = !ret; + ret = acpi_dev_install_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY, + toshiba_acpi_notify, dev); + if (ret) + goto error; + + dev->notify_handler_installed = 1; + create_toshiba_proc_entries(dev); toshiba_acpi = dev; @@ -3491,74 +3571,14 @@ iio_error: return 0; error: - toshiba_acpi_remove(acpi_dev); + toshiba_acpi_remove(pdev); return ret; } -static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event) -{ - struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev); - - switch (event) { - case 0x80: /* Hotkeys and some system events */ - /* - * Machines with this WMI GUID aren't supported due to bugs in - * their AML. - * - * Return silently to avoid triggering a netlink event. - */ - if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID)) - return; - toshiba_acpi_process_hotkeys(dev); - break; - case 0x81: /* Dock events */ - case 0x82: - case 0x83: - pr_info("Dock event received %x\n", event); - break; - case 0x88: /* Thermal events */ - pr_info("Thermal event received\n"); - break; - case 0x8f: /* LID closed */ - case 0x90: /* LID is closed and Dock has been ejected */ - break; - case 0x8c: /* SATA power events */ - case 0x8b: - pr_info("SATA power event received %x\n", event); - break; - case 0x92: /* Keyboard backlight mode changed */ - 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 0x8e: /* Power button pressed */ - break; - case 0x85: /* Unknown */ - case 0x8d: /* Unknown */ - case 0x94: /* Unknown */ - case 0x95: /* Unknown */ - default: - pr_info("Unknown event received %x\n", event); - break; - } - - acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, - dev_name(&acpi_dev->dev), - event, (event == 0x80) ? - dev->last_key_event : 0); -} - #ifdef CONFIG_PM_SLEEP static int toshiba_acpi_suspend(struct device *device) { - struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device)); + struct toshiba_acpi_dev *dev = dev_get_drvdata(device); if (dev->hotkey_dev) { u32 result; @@ -3573,7 +3593,7 @@ static int toshiba_acpi_suspend(struct device *device) static int toshiba_acpi_resume(struct device *device) { - struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device)); + struct toshiba_acpi_dev *dev = dev_get_drvdata(device); if (dev->hotkey_dev) { if (toshiba_acpi_enable_hotkeys(dev)) @@ -3595,16 +3615,14 @@ static int toshiba_acpi_resume(struct device *device) static SIMPLE_DEV_PM_OPS(toshiba_acpi_pm, toshiba_acpi_suspend, toshiba_acpi_resume); -static struct acpi_driver toshiba_acpi_driver = { - .name = "Toshiba ACPI driver", - .ids = toshiba_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = toshiba_acpi_add, - .remove = toshiba_acpi_remove, - .notify = toshiba_acpi_notify, +static struct platform_driver toshiba_acpi_driver = { + .probe = toshiba_acpi_probe, + .remove = toshiba_acpi_remove, + .driver = { + .name = "Toshiba ACPI driver", + .acpi_match_table = toshiba_device_ids, + .pm = &toshiba_acpi_pm, }, - .drv.pm = &toshiba_acpi_pm, }; static void __init toshiba_dmi_init(void) @@ -3634,7 +3652,7 @@ static int __init toshiba_acpi_init(void) return -ENODEV; } - ret = acpi_bus_register_driver(&toshiba_acpi_driver); + ret = platform_driver_register(&toshiba_acpi_driver); if (ret) { pr_err("Failed to register ACPI driver: %d\n", ret); remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); @@ -3645,7 +3663,7 @@ static int __init toshiba_acpi_init(void) static void __exit toshiba_acpi_exit(void) { - acpi_bus_unregister_driver(&toshiba_acpi_driver); + platform_driver_unregister(&toshiba_acpi_driver); if (toshiba_proc_dir) remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); } diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index e587beef05b9..bf199fba4999 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -17,6 +17,7 @@ #include <linux/types.h> #include <linux/acpi.h> #include <linux/rfkill.h> +#include <linux/platform_device.h> #define BT_KILLSWITCH_MASK 0x01 #define BT_PLUGGED_MASK 0x40 @@ -35,9 +36,9 @@ struct toshiba_bluetooth_dev { bool powered; }; -static int toshiba_bt_rfkill_add(struct acpi_device *device); -static void toshiba_bt_rfkill_remove(struct acpi_device *device); -static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event); +static int toshiba_bt_rfkill_probe(struct platform_device *pdev); +static void toshiba_bt_rfkill_remove(struct platform_device *pdev); +static void toshiba_bt_rfkill_notify(acpi_handle handle, u32 event, void *data); static const struct acpi_device_id bt_device_ids[] = { { "TOS6205", 0}, @@ -50,16 +51,14 @@ static int toshiba_bt_resume(struct device *dev); #endif static SIMPLE_DEV_PM_OPS(toshiba_bt_pm, NULL, toshiba_bt_resume); -static struct acpi_driver toshiba_bt_rfkill_driver = { - .name = "Toshiba BT", - .class = "Toshiba", - .ids = bt_device_ids, - .ops = { - .add = toshiba_bt_rfkill_add, - .remove = toshiba_bt_rfkill_remove, - .notify = toshiba_bt_rfkill_notify, - }, - .drv.pm = &toshiba_bt_pm, +static struct platform_driver toshiba_bt_rfkill_driver = { + .probe = toshiba_bt_rfkill_probe, + .remove = toshiba_bt_rfkill_remove, + .driver = { + .name = "Toshiba BT", + .acpi_match_table = bt_device_ids, + .pm = &toshiba_bt_pm, + }, }; static int toshiba_bluetooth_present(acpi_handle handle) @@ -203,9 +202,9 @@ static const struct rfkill_ops rfk_ops = { }; /* ACPI driver functions */ -static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) +static void toshiba_bt_rfkill_notify(acpi_handle handle, u32 event, void *data) { - struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device); + struct toshiba_bluetooth_dev *bt_dev = data; if (toshiba_bluetooth_sync_status(bt_dev)) return; @@ -216,11 +215,9 @@ static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) #ifdef CONFIG_PM_SLEEP static int toshiba_bt_resume(struct device *dev) { - struct toshiba_bluetooth_dev *bt_dev; + struct toshiba_bluetooth_dev *bt_dev = dev_get_drvdata(dev); int ret; - bt_dev = acpi_driver_data(to_acpi_device(dev)); - ret = toshiba_bluetooth_sync_status(bt_dev); if (ret) return ret; @@ -231,11 +228,16 @@ static int toshiba_bt_resume(struct device *dev) } #endif -static int toshiba_bt_rfkill_add(struct acpi_device *device) +static int toshiba_bt_rfkill_probe(struct platform_device *pdev) { struct toshiba_bluetooth_dev *bt_dev; + struct acpi_device *device; int result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + result = toshiba_bluetooth_present(device->handle); if (result) return result; @@ -246,24 +248,22 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) if (!bt_dev) return -ENOMEM; bt_dev->acpi_dev = device; - device->driver_data = bt_dev; - dev_set_drvdata(&device->dev, bt_dev); + + platform_set_drvdata(pdev, bt_dev); result = toshiba_bluetooth_sync_status(bt_dev); - if (result) { - kfree(bt_dev); - return result; - } + if (result) + goto err_free_bt_dev; bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", - &device->dev, + &pdev->dev, RFKILL_TYPE_BLUETOOTH, &rfk_ops, bt_dev); if (!bt_dev->rfk) { pr_err("Unable to allocate rfkill device\n"); - kfree(bt_dev); - return -ENOMEM; + result = -ENOMEM; + goto err_free_bt_dev; } rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch); @@ -271,18 +271,36 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) result = rfkill_register(bt_dev->rfk); if (result) { pr_err("Unable to register rfkill device\n"); - rfkill_destroy(bt_dev->rfk); - kfree(bt_dev); + goto err_rfkill_destroy; + } + + result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + toshiba_bt_rfkill_notify, bt_dev); + if (result) { + pr_err("Unable to register ACPI notify handler\n"); + goto err_rfkill_unregister; } + return 0; + +err_rfkill_unregister: + rfkill_unregister(bt_dev->rfk); +err_rfkill_destroy: + rfkill_destroy(bt_dev->rfk); +err_free_bt_dev: + kfree(bt_dev); return result; } -static void toshiba_bt_rfkill_remove(struct acpi_device *device) +static void toshiba_bt_rfkill_remove(struct platform_device *pdev) { - struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device); + struct toshiba_bluetooth_dev *bt_dev = platform_get_drvdata(pdev); + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); /* clean up */ + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, + toshiba_bt_rfkill_notify); + if (bt_dev->rfk) { rfkill_unregister(bt_dev->rfk); rfkill_destroy(bt_dev->rfk); @@ -293,4 +311,4 @@ static void toshiba_bt_rfkill_remove(struct acpi_device *device) toshiba_bluetooth_disable(device->handle); } -module_acpi_driver(toshiba_bt_rfkill_driver); +module_platform_driver(toshiba_bt_rfkill_driver); diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index e9324bf16aea..c6633b74029f 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -12,6 +12,7 @@ #include <linux/init.h> #include <linux/types.h> #include <linux/acpi.h> +#include <linux/platform_device.h> MODULE_AUTHOR("Azael Avalos <coproscefalo@gmail.com>"); MODULE_DESCRIPTION("Toshiba HDD Active Protection Sensor"); @@ -129,21 +130,28 @@ static const struct attribute_group haps_attr_group = { /* * ACPI stuff */ -static void toshiba_haps_notify(struct acpi_device *device, u32 event) +static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data) { + struct acpi_device *device = data; + pr_debug("Received event: 0x%x\n", event); - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), - event, 0); + acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0); } -static void toshiba_haps_remove(struct acpi_device *device) +static void toshiba_haps_remove(struct platform_device *pdev) { + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, + toshiba_haps_notify); + sysfs_remove_group(&device->dev.kobj, &haps_attr_group); if (toshiba_haps) toshiba_haps = NULL; + + dev_set_drvdata(&device->dev, NULL); } /* Helper function */ @@ -170,27 +178,33 @@ static int toshiba_haps_available(acpi_handle handle) return 1; } -static int toshiba_haps_add(struct acpi_device *acpi_dev) +static int toshiba_haps_probe(struct platform_device *pdev) { struct toshiba_haps_dev *haps; + struct acpi_device *acpi_dev; int ret; if (toshiba_haps) return -EBUSY; + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + if (!toshiba_haps_available(acpi_dev->handle)) return -ENODEV; pr_info("Toshiba HDD Active Protection Sensor device\n"); - haps = devm_kzalloc(&acpi_dev->dev, sizeof(*haps), GFP_KERNEL); + haps = devm_kzalloc(&pdev->dev, sizeof(*haps), GFP_KERNEL); if (!haps) return -ENOMEM; haps->acpi_dev = acpi_dev; haps->protection_level = 2; - acpi_dev->driver_data = haps; + dev_set_drvdata(&acpi_dev->dev, haps); + platform_set_drvdata(pdev, haps); /* Set the protection level, currently at level 2 (Medium) */ ret = toshiba_haps_protection_level(acpi_dev->handle, 2); @@ -201,19 +215,26 @@ static int toshiba_haps_add(struct acpi_device *acpi_dev) if (ret) return ret; + ret = acpi_dev_install_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY, + toshiba_haps_notify, acpi_dev); + if (ret) + goto err; + toshiba_haps = haps; return 0; + +err: + sysfs_remove_group(&acpi_dev->dev.kobj, &haps_attr_group); + return ret; } #ifdef CONFIG_PM_SLEEP static int toshiba_haps_suspend(struct device *device) { - struct toshiba_haps_dev *haps; + struct toshiba_haps_dev *haps = dev_get_drvdata(device); int ret; - haps = acpi_driver_data(to_acpi_device(device)); - /* Deactivate the protection on suspend */ ret = toshiba_haps_protection_level(haps->acpi_dev->handle, 0); @@ -222,11 +243,9 @@ static int toshiba_haps_suspend(struct device *device) static int toshiba_haps_resume(struct device *device) { - struct toshiba_haps_dev *haps; + struct toshiba_haps_dev *haps = dev_get_drvdata(device); int ret; - haps = acpi_driver_data(to_acpi_device(device)); - /* Set the stored protection level */ ret = toshiba_haps_protection_level(haps->acpi_dev->handle, haps->protection_level); @@ -249,16 +268,14 @@ static const struct acpi_device_id haps_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, haps_device_ids); -static struct acpi_driver toshiba_haps_driver = { - .name = "Toshiba HAPS", - .ids = haps_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = toshiba_haps_add, - .remove = toshiba_haps_remove, - .notify = toshiba_haps_notify, +static struct platform_driver toshiba_haps_driver = { + .probe = toshiba_haps_probe, + .remove = toshiba_haps_remove, + .driver = { + .name = "Toshiba HAPS", + .acpi_match_table = haps_device_ids, + .pm = &toshiba_haps_pm, }, - .drv.pm = &toshiba_haps_pm, }; -module_acpi_driver(toshiba_haps_driver); +module_platform_driver(toshiba_haps_driver); diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 6341dca20b76..7a2eeaec4c96 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -95,6 +95,9 @@ #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 +#define EC_ADDR_SCREEN_STATUS 0x0466 +#define SCREEN_SUSPENDED BIT(6) + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C #define EC_ADDR_SECOND_FAN_RPM_2 0x046D @@ -109,7 +112,35 @@ #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 +#define EC_ADDR_OEM_9 0x0726 +#define AC_AUTO_BOOT_ENABLE BIT(3) + #define EC_ADDR_PROJECT_ID 0x0740 +#define PROJECT_ID_NONE 0x00 +#define PROJECT_ID_GI 0x01 +#define PROJECT_ID_GJ 0x02 +#define PROJECT_ID_GK 0x03 +#define PROJECT_ID_GICN 0x04 +#define PROJECT_ID_GJCN 0x05 +#define PROJECT_ID_GK5CN_X 0x06 +#define PROJECT_ID_GK7CN_S 0x07 +#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 +#define PROJECT_ID_PF 0x09 +#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A +#define PROJECT_ID_IDP 0x0B +#define PROJECT_ID_IDY_6Y 0x0C +#define PROJECT_ID_IDY_7Y 0x0D +#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E +#define PROJECT_ID_CML_GAMING 0x0F +#define PROJECT_ID_GK7NXXR 0x10 +#define PROJECT_ID_GM5MU1Y 0x11 +#define PROJECT_ID_PH4TRX1 0x12 +#define PROJECT_ID_PH4TUX1 0x13 +#define PROJECT_ID_PH4TQX1 0x14 +#define PROJECT_ID_PH6TRX1 0x15 +#define PROJECT_ID_PH6TQXX 0x16 +#define PROJECT_ID_PHXAXXX 0x17 +#define PROJECT_ID_PHXPXXX 0x18 #define EC_ADDR_AP_OEM 0x0741 #define ENABLE_MANUAL_CTRL BIT(0) @@ -212,6 +243,7 @@ #define FAN_TABLE_OFFICE_MODE BIT(2) #define FAN_V3 BIT(3) #define DEFAULT_MODE BIT(4) +#define ENABLE_CHINA_MODE BIT(6) #define EC_ADDR_PL1_SETTING 0x0783 @@ -223,11 +255,12 @@ #define FAN_CURVE_LENGTH 5 #define EC_ADDR_KBD_STATUS 0x078C -#define KBD_WHITE_ONLY BIT(0) // ~single color -#define KBD_SINGLE_COLOR_OFF BIT(1) +/* Unreliable on some models, use the device descriptor instead. */ +#define KBD_WHITE_ONLY BIT(0) +#define KBD_POWER_OFF BIT(1) #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) #define KBD_APPLY BIT(4) -#define KBD_BRIGHTNESS GENMASK(7, 5) +#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) #define EC_ADDR_FAN_CTRL 0x078E #define FAN3P5 BIT(1) @@ -252,6 +285,10 @@ #define EC_ADDR_OEM_4 0x07A6 #define OVERBOOST_DYN_TEMP_OFF BIT(1) +#define CHARGING_PROFILE_MASK GENMASK(5, 4) +#define CHARGING_PROFILE_HIGH_CAPACITY 0x00 +#define CHARGING_PROFILE_BALANCED 0x01 +#define CHARGING_PROFILE_STATIONARY 0x02 #define TOUCHPAD_TOGGLE_OFF BIT(6) #define EC_ADDR_CHARGE_CTRL 0x07B9 @@ -266,8 +303,8 @@ #define BATTERY_CHARGE_FULL_OVER_24H BIT(3) #define BATTERY_ERM_STATUS_REACHED BIT(4) -#define EC_ADDR_CHARGE_PRIO 0x07CC -#define CHARGING_PERFORMANCE BIT(7) +#define EC_ADDR_USB_C_POWER_PRIORITY 0x07CC +#define USB_C_POWER_PRIORITY BIT(7) /* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */ #define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2 @@ -312,34 +349,75 @@ #define FAN_TABLE_LENGTH 16 #define LED_CHANNELS 3 -#define LED_MAX_BRIGHTNESS 200 + +#define KBD_LED_CHANNELS 3 +#define KBD_LED_MAX_INTENSITY 50 #define UNIWILL_FEATURE_FN_LOCK BIT(0) #define UNIWILL_FEATURE_SUPER_KEY BIT(1) #define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2) #define UNIWILL_FEATURE_LIGHTBAR BIT(3) -#define UNIWILL_FEATURE_BATTERY BIT(4) -#define UNIWILL_FEATURE_HWMON BIT(5) -#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(6) +#define UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT BIT(4) +/* Mutually exclusive with the charge limit feature */ +#define UNIWILL_FEATURE_BATTERY_CHARGE_MODES BIT(5) +#define UNIWILL_FEATURE_CPU_TEMP BIT(6) +#define UNIWILL_FEATURE_GPU_TEMP BIT(7) +#define UNIWILL_FEATURE_PRIMARY_FAN BIT(8) +#define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) +#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) +#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) +#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) +#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) +#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) + +enum usb_c_power_priority_options { + USB_C_POWER_PRIORITY_CHARGING = 0, + USB_C_POWER_PRIORITY_PERFORMANCE, +}; struct uniwill_data { struct device *dev; acpi_handle handle; struct regmap *regmap; unsigned int features; + u8 project_id; struct acpi_battery_hook hook; - unsigned int last_charge_ctrl; struct mutex battery_lock; /* Protects the list of currently registered batteries */ - unsigned int last_status; - unsigned int last_switch_status; + union { + struct { + /* Protects writes to last_charge_type */ + struct mutex charge_type_lock; + enum power_supply_charge_type last_charge_type; + }; + unsigned int last_charge_ctrl; + }; + bool last_fn_lock_state; + bool last_super_key_enable_state; + bool last_touchpad_toggle_enable_state; + bool last_usb_powershare_high_state; struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ struct list_head batteries; struct mutex led_lock; /* Protects writes to the lightbar registers */ + u8 lightbar_max_brightness; struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; + bool kbd_led_single_color; + u8 kbd_led_max_brightness; + unsigned int last_kbd_status; + union { + struct { + /* Protects writes to the RGB keyboard backlight registers */ + struct mutex kbd_rgb_led_lock; + struct led_classdev_mc kbd_led_mc_cdev; + struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; + }; + struct led_classdev kbd_led_cdev; + }; struct mutex input_lock; /* Protects input sequence during notify */ struct input_dev *input_device; struct notifier_block nb; + struct mutex usb_c_power_priority_lock; /* Protects dependent bit write and state safe */ + enum usb_c_power_priority_options last_usb_c_power_priority_option; }; struct uniwill_battery_entry { @@ -349,6 +427,9 @@ struct uniwill_battery_entry { struct uniwill_device_descriptor { unsigned int features; + bool kbd_led_single_color; + u8 kbd_led_max_brightness; + u8 lightbar_max_brightness; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -400,6 +481,9 @@ static const struct key_entry uniwill_keymap[] = { { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + /* Reported when the EC changed the keyboard backlight brightness */ + { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the microphone mute status */ { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, @@ -408,11 +492,6 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the brightness of the keyboard */ { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, /* FIXME: find out the exact meaning of those events */ { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, @@ -421,18 +500,27 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the benchmark mode status */ { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + /* Reported when the screen is enabled/disabled during resume/suspend */ + { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the webcam */ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, { KE_END } }; -static inline bool uniwill_device_supports(struct uniwill_data *data, +static inline bool uniwill_device_supports(const struct uniwill_data *data, unsigned int features) { return (data->features & features) == features; } +static inline bool uniwill_device_supports_any(const struct uniwill_data *data, + unsigned int features) +{ + return data->features & features; +} + static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val) { union acpi_object params[2] = { @@ -507,6 +595,7 @@ static const struct regmap_bus uniwill_ec_bus = { static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { + case EC_ADDR_OEM_9: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: case EC_ADDR_LIGHTBAR_AC_RED: @@ -514,6 +603,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) case EC_ADDR_LIGHTBAR_AC_BLUE: case EC_ADDR_BIOS_OEM: case EC_ADDR_TRIGGER: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -524,6 +618,7 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) case EC_ADDR_CTGP_DB_CTGP_OFFSET: case EC_ADDR_CTGP_DB_TPP_OFFSET: case EC_ADDR_CTGP_DB_DB_OFFSET: + case EC_ADDR_USB_C_POWER_PRIORITY: return true; default: return false; @@ -540,6 +635,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_SECOND_FAN_RPM_1: case EC_ADDR_SECOND_FAN_RPM_2: case EC_ADDR_BAT_ALERT: + case EC_ADDR_OEM_9: case EC_ADDR_PROJECT_ID: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: @@ -549,8 +645,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -562,6 +664,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_CTGP_DB_CTGP_OFFSET: case EC_ADDR_CTGP_DB_TPP_OFFSET: case EC_ADDR_CTGP_DB_DB_OFFSET: + case EC_ADDR_USB_C_POWER_PRIORITY: return true; default: return false; @@ -581,9 +684,13 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_USB_C_POWER_PRIORITY: return true; default: return false; @@ -603,11 +710,22 @@ static const struct regmap_config uniwill_ec_config = { .use_single_write = true, }; +static int uniwill_write_fn_lock(struct uniwill_data *data, bool status) +{ + unsigned int value; + + if (status) + value = FN_LOCK_STATUS; + else + value = 0; + + return regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, value); +} + static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; bool enable; int ret; @@ -615,21 +733,15 @@ static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, if (ret < 0) return ret; - if (enable) - value = FN_LOCK_STATUS; - else - value = 0; - - ret = regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, value); + ret = uniwill_write_fn_lock(data, enable); if (ret < 0) return ret; return count; } -static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf) +static int uniwill_read_fn_lock(struct uniwill_data *data, bool *status) { - struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; int ret; @@ -637,23 +749,31 @@ static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, c if (ret < 0) return ret; - return sysfs_emit(buf, "%d\n", !!(value & FN_LOCK_STATUS)); -} + *status = !!(value & FN_LOCK_STATUS); -static DEVICE_ATTR_RW(fn_lock); + return 0; +} -static ssize_t super_key_enable_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; - bool enable; + bool status; int ret; - ret = kstrtobool(buf, &enable); + ret = uniwill_read_fn_lock(data, &status); if (ret < 0) return ret; + return sysfs_emit(buf, "%d\n", status); +} + +static DEVICE_ATTR_RW(fn_lock); + +static int uniwill_write_super_key_enable(struct uniwill_data *data, bool status) +{ + unsigned int value; + int ret; + guard(mutex)(&data->super_key_lock); ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); @@ -664,20 +784,33 @@ static ssize_t super_key_enable_store(struct device *dev, struct device_attribut * We can only toggle the super key lock, so we return early if the setting * is already in the correct state. */ - if (enable == !(value & SUPER_KEY_LOCK_STATUS)) - return count; + if (status == !(value & SUPER_KEY_LOCK_STATUS)) + return 0; + + return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK, + TRIGGER_SUPER_KEY_LOCK); +} + +static ssize_t super_key_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool enable; + int ret; - ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK, - TRIGGER_SUPER_KEY_LOCK); + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + ret = uniwill_write_super_key_enable(data, enable); if (ret < 0) return ret; return count; } -static ssize_t super_key_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +static int uniwill_read_super_key_enable(struct uniwill_data *data, bool *status) { - struct uniwill_data *data = dev_get_drvdata(dev); unsigned int value; int ret; @@ -685,16 +818,42 @@ static ssize_t super_key_enable_show(struct device *dev, struct device_attribute if (ret < 0) return ret; - return sysfs_emit(buf, "%d\n", !(value & SUPER_KEY_LOCK_STATUS)); + *status = !(value & SUPER_KEY_LOCK_STATUS); + + return 0; +} + +static ssize_t super_key_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool status; + int ret; + + ret = uniwill_read_super_key_enable(data, &status); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", status); } static DEVICE_ATTR_RW(super_key_enable); +static int uniwill_write_touchpad_toggle_enable(struct uniwill_data *data, bool status) +{ + unsigned int value; + + if (status) + value = 0; + else + value = TOUCHPAD_TOGGLE_OFF; + + return regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_OFF, value); +} + static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; bool enable; int ret; @@ -702,30 +861,39 @@ static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_at if (ret < 0) return ret; - if (enable) - value = 0; - else - value = TOUCHPAD_TOGGLE_OFF; - - ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_OFF, value); + ret = uniwill_write_touchpad_toggle_enable(data, enable); if (ret < 0) return ret; return count; } +static int uniwill_read_touchpad_toggle_enable(struct uniwill_data *data, bool *status) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value); + if (ret < 0) + return ret; + + *status = !(value & TOUCHPAD_TOGGLE_OFF); + + return 0; +} + static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; + bool status; int ret; - ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value); + ret = uniwill_read_touchpad_toggle_enable(data, &status); if (ret < 0) return ret; - return sysfs_emit(buf, "%d\n", !(value & TOUCHPAD_TOGGLE_OFF)); + return sysfs_emit(buf, "%d\n", status); } static DEVICE_ATTR_RW(touchpad_toggle_enable); @@ -880,6 +1048,208 @@ static int uniwill_nvidia_ctgp_init(struct uniwill_data *data) return 0; } +static const char * const usb_c_power_priority_text[] = { + [USB_C_POWER_PRIORITY_CHARGING] = "charging", + [USB_C_POWER_PRIORITY_PERFORMANCE] = "performance", +}; + +static const u8 usb_c_power_priority_value[] = { + [USB_C_POWER_PRIORITY_CHARGING] = 0, + [USB_C_POWER_PRIORITY_PERFORMANCE] = USB_C_POWER_PRIORITY, +}; + +static ssize_t usb_c_power_priority_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + enum usb_c_power_priority_options option; + unsigned int value; + int ret; + + ret = sysfs_match_string(usb_c_power_priority_text, buf); + if (ret < 0) + return ret; + + option = ret; + value = usb_c_power_priority_value[option]; + + guard(mutex)(&data->usb_c_power_priority_lock); + + ret = regmap_update_bits(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY, + USB_C_POWER_PRIORITY, value); + if (ret < 0) + return ret; + + data->last_usb_c_power_priority_option = option; + + return count; +} + +static ssize_t usb_c_power_priority_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY, &value); + if (ret < 0) + return ret; + + value &= USB_C_POWER_PRIORITY; + + if (usb_c_power_priority_value[USB_C_POWER_PRIORITY_PERFORMANCE] == value) + return sysfs_emit(buf, "%s\n", + usb_c_power_priority_text[USB_C_POWER_PRIORITY_PERFORMANCE]); + + return sysfs_emit(buf, "%s\n", usb_c_power_priority_text[USB_C_POWER_PRIORITY_CHARGING]); +} + +static DEVICE_ATTR_RW(usb_c_power_priority); + +static int usb_c_power_priority_restore(struct uniwill_data *data) +{ + unsigned int value; + + value = usb_c_power_priority_value[data->last_usb_c_power_priority_option]; + + guard(mutex)(&data->usb_c_power_priority_lock); + + return regmap_update_bits(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY, + USB_C_POWER_PRIORITY, value); +} + +static int usb_c_power_priority_init(struct uniwill_data *data) +{ + unsigned int value; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) + return 0; + + ret = devm_mutex_init(data->dev, &data->usb_c_power_priority_lock); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY, &value); + if (ret < 0) + return ret; + + value &= USB_C_POWER_PRIORITY; + + data->last_usb_c_power_priority_option = + usb_c_power_priority_value[USB_C_POWER_PRIORITY_PERFORMANCE] == value ? + USB_C_POWER_PRIORITY_PERFORMANCE : + USB_C_POWER_PRIORITY_CHARGING; + + return 0; +} + +static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + if (enable) + regval = AC_AUTO_BOOT_ENABLE; + else + regval = 0; + + ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); +} + +static DEVICE_ATTR_RW(ac_auto_boot); + +static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) +{ + unsigned int value; + + if (status) + value = TRIGGER_USB_CHARGING; + else + value = 0; + + /* + * Normaly this RMW-sequence could also trigger the super key toggle, + * but the EC seems to take care that those bits are always read as 0. + */ + return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); +} + +static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + ret = uniwill_write_usb_powershare_high(data, enable); + if (ret < 0) + return ret; + + return count; +} + +static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); + if (ret < 0) + return ret; + + *status = !!(value & TRIGGER_USB_CHARGING); + + return 0; +} + +static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool status; + int ret; + + ret = uniwill_read_usb_powershare_high(data, &status); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", status); +} + +static DEVICE_ATTR_RW(usb_powershare_high); + static struct attribute *uniwill_attrs[] = { /* Keyboard-related */ &dev_attr_fn_lock.attr, @@ -890,6 +1260,9 @@ static struct attribute *uniwill_attrs[] = { &dev_attr_breathing_in_suspend.attr, /* Power-management-related */ &dev_attr_ctgp_offset.attr, + &dev_attr_usb_c_power_priority.attr, + &dev_attr_ac_auto_boot.attr, + &dev_attr_usb_powershare_high.attr, NULL }; @@ -924,6 +1297,21 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } + if (attr == &dev_attr_usb_c_power_priority.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) + return attr->mode; + } + + if (attr == &dev_attr_ac_auto_boot.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) + return attr->mode; + } + + if (attr == &dev_attr_usb_powershare_high.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return attr->mode; + } + return 0; } @@ -937,6 +1325,48 @@ static const struct attribute_group *uniwill_groups[] = { NULL }; +static umode_t uniwill_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct uniwill_data *data = drvdata; + unsigned int feature; + + switch (type) { + case hwmon_temp: + switch (channel) { + case 0: + feature = UNIWILL_FEATURE_CPU_TEMP; + break; + case 1: + feature = UNIWILL_FEATURE_GPU_TEMP; + break; + default: + return 0; + } + break; + case hwmon_fan: + case hwmon_pwm: + switch (channel) { + case 0: + feature = UNIWILL_FEATURE_PRIMARY_FAN; + break; + case 1: + feature = UNIWILL_FEATURE_SECONDARY_FAN; + break; + default: + return 0; + } + break; + default: + return 0; + } + + if (uniwill_device_supports(data, feature)) + return 0444; + + return 0; +} + static int uniwill_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -1020,7 +1450,7 @@ static int uniwill_read_string(struct device *dev, enum hwmon_sensor_types type, } static const struct hwmon_ops uniwill_ops = { - .visible = 0444, + .is_visible = uniwill_is_visible, .read = uniwill_read, .read_string = uniwill_read_string, }; @@ -1048,7 +1478,10 @@ static int uniwill_hwmon_init(struct uniwill_data *data) { struct device *hdev; - if (!uniwill_device_supports(data, UNIWILL_FEATURE_HWMON)) + if (!uniwill_device_supports(data, UNIWILL_FEATURE_CPU_TEMP) && + !uniwill_device_supports(data, UNIWILL_FEATURE_GPU_TEMP) && + !uniwill_device_supports(data, UNIWILL_FEATURE_PRIMARY_FAN) && + !uniwill_device_supports(data, UNIWILL_FEATURE_SECONDARY_FAN)) return 0; hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data, @@ -1084,7 +1517,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br for (int i = 0; i < LED_CHANNELS; i++) { /* Prevent the brightness values from overflowing */ - value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); + value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -1153,14 +1586,14 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; - data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; if (value & LIGHTBAR_S0_OFF) data->led_mc_cdev.led_cdev.brightness = 0; else - data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; @@ -1173,7 +1606,7 @@ static int uniwill_led_init(struct uniwill_data *data) * Make sure that the initial intensity value is not greater than * the maximum brightness. */ - value = min(LED_MAX_BRIGHTNESS, value); + value = min(data->lightbar_max_brightness, value); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -1193,6 +1626,280 @@ static int uniwill_led_init(struct uniwill_data *data) &init_data); } +static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) +{ + struct led_classdev *led_cdev; + int ret; + + if (data->kbd_led_single_color) + led_cdev = &data->kbd_led_cdev; + else + led_cdev = &data->kbd_led_mc_cdev.led_cdev; + + guard(mutex)(&led_cdev->led_access); + + /* Sync the LED brightness with the actual hardware state */ + ret = led_update_brightness(led_cdev); + if (ret < 0) + return ret; + + led_classdev_notify_brightness_hw_changed(led_cdev, brightness); + + return 0; +} + +#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) + +static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) +{ + /* KBD_POWER_OFF is always implicitly cleared */ + unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; + + /* We must ensure that the "apply" bit is always written */ + return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); +} + +static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); +} + +static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { + EC_ADDR_RGB_RED, + EC_ADDR_RGB_GREEN, + EC_ADDR_RGB_BLUE, +}; + +static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + unsigned int min_intensity = 0; + unsigned int regval; + int ret; + + guard(mutex)(&data->kbd_rgb_led_lock); + + /* + * The EC interprets a RGB value of 0x000000 as a command to restore + * the device-specfic default RGB value. Work around this by writing + * a RGB value of 0x010101 (faint white) instead. + */ + if (data->kbd_led_mc_subled_info[0].intensity == 0 && + data->kbd_led_mc_subled_info[1].intensity == 0 && + data->kbd_led_mc_subled_info[2].intensity == 0) + min_intensity = 1; + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + } + + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); + if (ret < 0) + return ret; + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static int uniwill_white_kbd_led_init(struct uniwill_data *data) +{ + struct led_init_data init_data = { + .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT, + .devicename = DRIVER_NAME, + .devname_mandatory = true, + }; + + data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; + data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; + data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; + + return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); +} + +static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) +{ + unsigned int color_indices[KBD_LED_CHANNELS] = { + LED_COLOR_ID_RED, + LED_COLOR_ID_GREEN, + LED_COLOR_ID_BLUE, + }; + struct led_init_data init_data = { + .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, + .devicename = DRIVER_NAME, + .devname_mandatory = true, + }; + bool intensity_all_zeros = true; + bool needs_trigger = false; + unsigned int regval; + int ret; + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; + + ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); + if (ret < 0) + return ret; + + /* + * Make sure that the initial intensity value is not greater than + * the maximum intensity. + */ + if (regval > KBD_LED_MAX_INTENSITY) { + regval = KBD_LED_MAX_INTENSITY; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + + needs_trigger = true; + } + + if (regval) + intensity_all_zeros = false; + + data->kbd_led_mc_subled_info[i].intensity = regval; + data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; + data->kbd_led_mc_subled_info[i].channel = i; + } + + /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ + if (intensity_all_zeros) { + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].intensity = 1; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); + if (ret < 0) + return ret; + } + + needs_trigger = true; + } + + if (needs_trigger) { + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, + RGB_APPLY_COLOR); + if (ret < 0) + return ret; + } + + ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); + if (ret < 0) + return ret; + + data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; + data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; + data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; + data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; + data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; + + return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, + &init_data); +} + +static int uniwill_kbd_led_init(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); + if (ret < 0) + return ret; + + if (!(regval & CHINA_MODE)) { + ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); + if (ret < 0) + return ret; + } + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + regval |= KBD_APPLY; + regval &= ~KBD_POWER_OFF; + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); + if (ret < 0) + return ret; + + if (data->kbd_led_single_color) + return uniwill_white_kbd_led_init(data); + + return uniwill_rgb_kbd_led_init(data); +} + +static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) +{ + /* 0 means "charging threshold not active" */ + if (!value) + return 100; + + /* Guard against invalid values */ + return min(value, 100); +} + +static int uniwill_read_charge_type(struct uniwill_data *data, enum power_supply_charge_type *type) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value); + if (ret < 0) + return ret; + + switch (FIELD_GET(CHARGING_PROFILE_MASK, value)) { + case CHARGING_PROFILE_HIGH_CAPACITY: + *type = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + return 0; + case CHARGING_PROFILE_BALANCED: + *type = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + return 0; + case CHARGING_PROFILE_STATIONARY: + *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + return 0; + default: + return -EPROTO; + } +} + static int uniwill_get_property(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp, union power_supply_propval *val) @@ -1203,6 +1910,16 @@ static int uniwill_get_property(struct power_supply *psy, const struct power_sup int ret; switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPES: + /* + * We need to use the cached value here because the charging mode + * reported by the EC might temporarily change when a external power + * source has been connected. + */ + mutex_lock(&data->charge_type_lock); + val->intval = data->last_charge_type; + mutex_unlock(&data->charge_type_lock); + return 0; case POWER_SUPPLY_PROP_HEALTH: ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_PRESENT, &prop); if (ret < 0) @@ -1239,26 +1956,66 @@ static int uniwill_get_property(struct power_supply *psy, const struct power_sup if (ret < 0) return ret; - val->intval = clamp_val(FIELD_GET(CHARGE_CTRL_MASK, regval), 0, 100); + regval = FIELD_GET(CHARGE_CTRL_MASK, regval); + val->intval = uniwill_sanitize_battery_threshold(regval); return 0; default: return -EINVAL; } } +static int uniwill_write_charge_type(struct uniwill_data *data, enum power_supply_charge_type type) +{ + unsigned int value; + + switch (type) { + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + value = FIELD_PREP(CHARGING_PROFILE_MASK, CHARGING_PROFILE_STATIONARY); + break; + case POWER_SUPPLY_CHARGE_TYPE_STANDARD: + value = FIELD_PREP(CHARGING_PROFILE_MASK, CHARGING_PROFILE_HIGH_CAPACITY); + break; + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: + value = FIELD_PREP(CHARGING_PROFILE_MASK, CHARGING_PROFILE_BALANCED); + break; + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, EC_ADDR_OEM_4, CHARGING_PROFILE_MASK, value); +} + +static int uniwill_restore_charge_type(struct uniwill_data *data) +{ + guard(mutex)(&data->charge_type_lock); + + return uniwill_write_charge_type(data, data->last_charge_type); +} + static int uniwill_set_property(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp, const union power_supply_propval *val) { struct uniwill_data *data = drvdata; + int ret; switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPES: + mutex_lock(&data->charge_type_lock); + + ret = uniwill_write_charge_type(data, val->intval); + if (ret >= 0) + data->last_charge_type = val->intval; + + mutex_unlock(&data->charge_type_lock); + + return ret; case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: - if (val->intval < 1 || val->intval > 100) + if (val->intval < 0 || val->intval > 100) return -EINVAL; return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, - val->intval); + max(val->intval, 1)); default: return -EINVAL; } @@ -1268,21 +2025,41 @@ static int uniwill_property_is_writeable(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp) { - if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPES: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: return true; - - return false; + default: + return false; + } } -static const enum power_supply_property uniwill_properties[] = { +static const enum power_supply_property uniwill_charge_limit_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, }; -static const struct power_supply_ext uniwill_extension = { +static const struct power_supply_ext uniwill_charge_limit_extension = { + .name = DRIVER_NAME, + .properties = uniwill_charge_limit_properties, + .num_properties = ARRAY_SIZE(uniwill_charge_limit_properties), + .get_property = uniwill_get_property, + .set_property = uniwill_set_property, + .property_is_writeable = uniwill_property_is_writeable, +}; + +static const enum power_supply_property uniwill_charge_modes_properties[] = { + POWER_SUPPLY_PROP_CHARGE_TYPES, + POWER_SUPPLY_PROP_HEALTH, +}; + +static const struct power_supply_ext uniwill_charge_modes_extension = { .name = DRIVER_NAME, - .properties = uniwill_properties, - .num_properties = ARRAY_SIZE(uniwill_properties), + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE), + .properties = uniwill_charge_modes_properties, + .num_properties = ARRAY_SIZE(uniwill_charge_modes_properties), .get_property = uniwill_get_property, .set_property = uniwill_set_property, .property_is_writeable = uniwill_property_is_writeable, @@ -1298,7 +2075,13 @@ static int uniwill_add_battery(struct power_supply *battery, struct acpi_battery if (!entry) return -ENOMEM; - ret = power_supply_register_extension(battery, &uniwill_extension, data->dev, data); + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) + ret = power_supply_register_extension(battery, &uniwill_charge_limit_extension, + data->dev, data); + else + ret = power_supply_register_extension(battery, &uniwill_charge_modes_extension, + data->dev, data); + if (ret < 0) { kfree(entry); return ret; @@ -1327,17 +2110,51 @@ static int uniwill_remove_battery(struct power_supply *battery, struct acpi_batt } } - power_supply_unregister_extension(battery, &uniwill_extension); + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) + power_supply_unregister_extension(battery, &uniwill_charge_limit_extension); + else + power_supply_unregister_extension(battery, &uniwill_charge_modes_extension); return 0; } static int uniwill_battery_init(struct uniwill_data *data) { + unsigned int value, threshold, sanitized; int ret; - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) { + ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value); + if (ret < 0) + return ret; + + /* + * The charge control threshold might be initialized with 0 by + * the EC to signal that said threshold is uninitialized. We thus + * need to replace this placeholder value with a valid one (100) + * to signal that we want to take control of battery charging. + * For the sake of completeness we also apply this to other + * invalid threshold values. + */ + threshold = FIELD_GET(CHARGE_CTRL_MASK, value); + sanitized = uniwill_sanitize_battery_threshold(threshold); + if (threshold != sanitized) { + FIELD_MODIFY(CHARGE_CTRL_MASK, &value, sanitized); + ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value); + if (ret < 0) + return ret; + } + } else if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) { + ret = devm_mutex_init(data->dev, &data->charge_type_lock); + if (ret < 0) + return ret; + + ret = uniwill_read_charge_type(data, &data->last_charge_type); + if (ret < 0) + return ret; + } else { return 0; + } ret = devm_mutex_init(data->dev, &data->battery_lock); if (ret < 0) @@ -1355,10 +2172,13 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action { struct uniwill_data *data = container_of(nb, struct uniwill_data, nb); struct uniwill_battery_entry *entry; + int ret; switch (action) { case UNIWILL_OSD_BATTERY_ALERT: - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) + if (!uniwill_device_supports_any(data, + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) return NOTIFY_DONE; mutex_lock(&data->battery_lock); @@ -1369,9 +2189,22 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action return NOTIFY_OK; case UNIWILL_OSD_DC_ADAPTER_CHANGED: - /* noop for the time being, will change once charging priority - * gets implemented. - */ + if (!uniwill_device_supports_any(data, + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) + return NOTIFY_DONE; + + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) { + ret = uniwill_restore_charge_type(data); + if (ret < 0) + return notifier_from_errno(ret); + } + + if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) { + ret = usb_c_power_priority_restore(data); + if (ret < 0) + return notifier_from_errno(ret); + } return NOTIFY_OK; case UNIWILL_OSD_FN_LOCK: @@ -1381,6 +2214,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); return NOTIFY_OK; + case UNIWILL_OSD_KB_LED_LEVEL0: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); + case UNIWILL_OSD_KB_LED_LEVEL1: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); + case UNIWILL_OSD_KB_LED_LEVEL2: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); + case UNIWILL_OSD_KB_LED_LEVEL3: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); + case UNIWILL_OSD_KB_LED_LEVEL4: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); default: mutex_lock(&data->input_lock); sparse_keymap_report_event(data->input_device, action, 1, true); @@ -1434,6 +2292,7 @@ static int uniwill_ec_init(struct uniwill_data *data) if (ret < 0) return ret; + data->project_id = value; dev_dbg(data->dev, "Project ID: %u\n", value); ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); @@ -1467,6 +2326,7 @@ static int uniwill_probe(struct platform_device *pdev) return PTR_ERR(regmap); data->regmap = regmap; + ret = devm_mutex_init(&pdev->dev, &data->super_key_lock); if (ret < 0) return ret; @@ -1476,6 +2336,9 @@ static int uniwill_probe(struct platform_device *pdev) return ret; data->features = device_descriptor.features; + data->kbd_led_single_color = device_descriptor.kbd_led_single_color; + data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; + data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; /* * Some devices might need to perform some device-specific initialization steps @@ -1496,6 +2359,10 @@ static int uniwill_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = uniwill_kbd_led_init(data); + if (ret < 0) + return ret; + ret = uniwill_hwmon_init(data); if (ret < 0) return ret; @@ -1504,6 +2371,10 @@ static int uniwill_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = usb_c_power_priority_init(data); + if (ret < 0) + return ret; + return uniwill_input_init(data); } @@ -1520,10 +2391,10 @@ static int uniwill_suspend_fn_lock(struct uniwill_data *data) return 0; /* - * The EC_ADDR_BIOS_OEM is marked as volatile, so we have to restore it + * EC_ADDR_BIOS_OEM is marked as volatile, so we have to restore it * ourselves. */ - return regmap_read(data->regmap, EC_ADDR_BIOS_OEM, &data->last_status); + return uniwill_read_fn_lock(data, &data->last_fn_lock_state); } static int uniwill_suspend_super_key(struct uniwill_data *data) @@ -1532,15 +2403,27 @@ static int uniwill_suspend_super_key(struct uniwill_data *data) return 0; /* - * The EC_ADDR_SWITCH_STATUS is marked as volatile, so we have to restore it + * EC_ADDR_SWITCH_STATUS is marked as volatile, so we have to restore it + * ourselves. + */ + return uniwill_read_super_key_enable(data, &data->last_super_key_enable_state); +} + +static int uniwill_suspend_touchpad_toggle(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE)) + return 0; + + /* + * EC_ADDR_OEM_4 is marked as volatile, so we have to restore it * ourselves. */ - return regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &data->last_switch_status); + return uniwill_read_touchpad_toggle_enable(data, &data->last_touchpad_toggle_enable_state); } static int uniwill_suspend_battery(struct uniwill_data *data) { - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) + if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) return 0; /* @@ -1551,6 +2434,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data) return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); } +static int uniwill_suspend_kbd_led(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + /* + * Save the current keyboard backlight settings in order to restore them + * during resume. We cannot use the regmap code for that since this register + * needs to be declared as volatile because the brightness can be changed + * by the EC. + */ + data->last_kbd_status = regval; + FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); + regval |= KBD_APPLY | KBD_POWER_OFF; + + return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); +} + +static int uniwill_suspend_usb_powershare(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return 0; + + /* + * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it + * ourselves. + */ + return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); +} + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -1573,10 +2493,22 @@ static int uniwill_suspend(struct device *dev) if (ret < 0) return ret; + ret = uniwill_suspend_touchpad_toggle(data); + if (ret < 0) + return ret; + ret = uniwill_suspend_battery(data); if (ret < 0) return ret; + ret = uniwill_suspend_kbd_led(data); + if (ret < 0) + return ret; + + ret = uniwill_suspend_usb_powershare(data); + if (ret < 0) + return ret; + ret = uniwill_suspend_nvidia_ctgp(data); if (ret < 0) return ret; @@ -1592,36 +2524,60 @@ static int uniwill_resume_fn_lock(struct uniwill_data *data) if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK)) return 0; - return regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, - data->last_status); + return uniwill_write_fn_lock(data, data->last_fn_lock_state); } static int uniwill_resume_super_key(struct uniwill_data *data) { - unsigned int value; + if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY)) + return 0; + + return uniwill_write_super_key_enable(data, data->last_super_key_enable_state); +} + +static int uniwill_resume_touchpad_toggle(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE)) + return 0; + + return uniwill_write_touchpad_toggle_enable(data, data->last_touchpad_toggle_enable_state); +} + +static int uniwill_resume_battery(struct uniwill_data *data) +{ + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) + return uniwill_restore_charge_type(data); + + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) + return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, + data->last_charge_ctrl); + + return 0; +} + +static int uniwill_resume_kbd_led(struct uniwill_data *data) +{ int ret; - if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY)) + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) return 0; - ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value); + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); if (ret < 0) return ret; - if ((data->last_switch_status & SUPER_KEY_LOCK_STATUS) == (value & SUPER_KEY_LOCK_STATUS)) + if (data->kbd_led_single_color) return 0; - return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK, - TRIGGER_SUPER_KEY_LOCK); + return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); } -static int uniwill_resume_battery(struct uniwill_data *data) +static int uniwill_resume_usb_powershare(struct uniwill_data *data) { - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) return 0; - return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, - data->last_charge_ctrl); + return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); } static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) @@ -1633,6 +2589,14 @@ static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE); } +static int uniwill_resume_usb_c_power_priority(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) + return 0; + + return usb_c_power_priority_restore(data); +} + static int uniwill_resume(struct device *dev) { struct uniwill_data *data = dev_get_drvdata(dev); @@ -1652,11 +2616,27 @@ static int uniwill_resume(struct device *dev) if (ret < 0) return ret; + ret = uniwill_resume_touchpad_toggle(data); + if (ret < 0) + return ret; + ret = uniwill_resume_battery(data); if (ret < 0) return ret; - return uniwill_resume_nvidia_ctgp(data); + ret = uniwill_resume_kbd_led(data); + if (ret < 0) + return ret; + + ret = uniwill_resume_usb_powershare(data); + if (ret < 0) + return ret; + + ret = uniwill_resume_nvidia_ctgp(data); + if (ret < 0) + return ret; + + return uniwill_resume_usb_c_power_priority(data); } static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume); @@ -1682,12 +2662,41 @@ static struct platform_driver uniwill_driver = { .shutdown = uniwill_shutdown, }; +static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, +}; + +static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { + .features = UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, + .lightbar_max_brightness = 36, +}; + static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_TOUCHPAD_TOGGLE | - UNIWILL_FEATURE_BATTERY | - UNIWILL_FEATURE_HWMON, + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, }; static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { @@ -1695,8 +2704,112 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_TOUCHPAD_TOGGLE | UNIWILL_FEATURE_LIGHTBAR | - UNIWILL_FEATURE_BATTERY | - UNIWILL_FEATURE_HWMON, + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, + .lightbar_max_brightness = 200, +}; + +/* + * The featuresets below reflect somewhat chronological changes: + * 1 -> 2: UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL is added to the EC firmware. + * 2 -> 3: UNIWILL_FEATURE_USB_C_POWER_PRIORITY is removed from the EC firmware. + * Some devices might divert from this timeline. + */ + +static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, +}; + +static struct uniwill_device_descriptor tux_featureset_1_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, +}; + +static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, +}; + +static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, +}; + +static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, +}; + +static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, +}; + +static int phxtxx1_probe(struct uniwill_data *data) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_PROJECT_ID, &value); + if (ret < 0) + return ret; + + if (value == PROJECT_ID_PH4TRX1 || value == PROJECT_ID_PH6TRX1) + data->features |= UNIWILL_FEATURE_SECONDARY_FAN; + + return 0; +}; + +static struct uniwill_device_descriptor phxtxx1_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + .probe = phxtxx1_probe, }; static int phxarx1_phxaqf1_probe(struct uniwill_data *data) @@ -1709,37 +2822,99 @@ static int phxarx1_phxaqf1_probe(struct uniwill_data *data) return ret; if (value & HAS_GPU) - data->features |= UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL; + data->features |= UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL; return 0; }; static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, .probe = phxarx1_phxaqf1_probe, }; -static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = { - .features = UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, +static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN, }; -static struct uniwill_device_descriptor empty_descriptor __initdata = {}; +static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, +}; static const struct dmi_system_id uniwill_dmi_table[] __initconst = { { - .ident = "XMG FUSION 15", + .ident = "AiStone X4SP4NAL", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), + }, + .driver_data = &x4sp4nal_descriptor, + }, + { + .ident = "MACHENIKE L16 Pro", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), + }, + .driver_data = &machenike_l16p_descriptor, + }, + { + .ident = "XMG FUSION 15 (L19)", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"), }, - .driver_data = &empty_descriptor, + .driver_data = &lapqc71a_lapqc71b_descriptor, }, { - .ident = "XMG FUSION 15", + .ident = "XMG FUSION 15 (L19)", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"), }, - .driver_data = &empty_descriptor, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, + { + .ident = "XMG FUSION 15 (L19)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"), + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, + { + .ident = "XMG FUSION 15 (L19)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"), + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, + { + .ident = "Avell A60 MUV", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, }, { .ident = "Intel NUC x15", @@ -1763,7 +2938,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"), }, - .driver_data = &empty_descriptor, + .driver_data = &phxtxx1_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", @@ -1771,7 +2946,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel", @@ -1787,7 +2962,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", @@ -1795,7 +2970,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", @@ -1803,7 +2978,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel", @@ -1811,7 +2986,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD", @@ -1819,7 +2994,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", @@ -1827,7 +3002,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -1835,7 +3010,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -1843,7 +3018,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", @@ -1851,7 +3026,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", @@ -1859,7 +3034,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -1867,7 +3042,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -1875,7 +3050,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", @@ -1883,7 +3058,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", @@ -1891,7 +3066,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", @@ -1899,7 +3074,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", @@ -1907,7 +3082,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", @@ -1915,7 +3090,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", @@ -1923,7 +3098,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", @@ -1931,7 +3106,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", @@ -1939,7 +3114,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", @@ -1947,7 +3122,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", @@ -1955,7 +3130,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Trinity 15 Intel Gen1", @@ -1963,7 +3138,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Trinity 17 Intel Gen1", @@ -1971,7 +3146,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen2 AMD", @@ -1979,7 +3154,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen2 Intel", @@ -1987,7 +3162,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD", @@ -1995,7 +3170,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel", @@ -2003,7 +3178,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD", @@ -2011,7 +3186,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_3_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 15 Gen4 Intel", @@ -2019,7 +3194,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_3_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen5 AMD", @@ -2027,7 +3202,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_2_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen5 AMD", @@ -2035,7 +3210,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_3_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5", @@ -2043,7 +3218,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_3_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD", @@ -2051,7 +3226,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", @@ -2059,7 +3234,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -2067,7 +3242,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -2075,7 +3250,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", @@ -2083,7 +3258,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 AMD", @@ -2091,7 +3266,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -2099,7 +3274,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -2107,7 +3282,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Book BA15 Gen10 AMD", @@ -2115,7 +3290,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"), }, - .driver_data = &empty_descriptor, + .driver_data = &pf5pu1g_descriptor, }, { .ident = "TUXEDO Pulse 14 Gen1 AMD", @@ -2123,7 +3298,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_descriptor, }, { .ident = "TUXEDO Pulse 15 Gen1 AMD", @@ -2131,7 +3306,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_descriptor, }, { .ident = "TUXEDO Pulse 15 Gen2 AMD", @@ -2139,7 +3314,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"), }, - .driver_data = &empty_descriptor, + .driver_data = &tux_featureset_1_descriptor, }, { } }; @@ -2156,8 +3331,6 @@ static int __init uniwill_init(void) if (!force) return -ENODEV; - /* Assume that the device supports all features */ - device_descriptor.features = UINT_MAX; pr_warn("Loading on a potentially unsupported device\n"); } else { /* @@ -2175,6 +3348,18 @@ static int __init uniwill_init(void) device_descriptor = *descriptor; } + if (force) { + /* Assume that the device supports all features except the charge limit */ + device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; + /* Some models only have a (white) single color keyboard backlight */ + device_descriptor.kbd_led_single_color = false; + /* Some models only support 3 brightness levels */ + device_descriptor.kbd_led_max_brightness = 4; + /* Some models only support 36 brightness levels per color component */ + device_descriptor.lightbar_max_brightness = 200; + pr_warn("Enabling potentially unsupported features\n"); + } + ret = platform_driver_register(&uniwill_driver); if (ret < 0) return ret; diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.c b/drivers/platform/x86/uniwill/uniwill-wmi.c index 31d9c39f14ab..afcbfa4f7552 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.c +++ b/drivers/platform/x86/uniwill/uniwill-wmi.c @@ -14,7 +14,6 @@ #include <linux/acpi.h> #include <linux/device.h> #include <linux/init.h> -#include <linux/mod_devicetable.h> #include <linux/notifier.h> #include <linux/printk.h> #include <linux/types.h> @@ -48,6 +47,7 @@ int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj) { u32 value; + int ret; if (obj->type != ACPI_TYPE_INTEGER) return; @@ -56,7 +56,9 @@ static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj) dev_dbg(&wdev->dev, "Received WMI event %u\n", value); - blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL); + ret = blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL); + if (notifier_to_errno(ret) < 0) + dev_err(&wdev->dev, "Failed to handle event %u\n", value); } /* @@ -77,6 +79,7 @@ static struct wmi_driver uniwill_wmi_driver = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .id_table = uniwill_wmi_id_table, + .min_event_size = sizeof(u32), .notify = uniwill_wmi_notify, .no_singleton = true, }; diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h index fb1910c0f741..b25b2f31211c 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.h +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h @@ -113,6 +113,8 @@ #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 +#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 diff --git a/drivers/platform/x86/wireless-hotkey.c b/drivers/platform/x86/wireless-hotkey.c index e5083c0e1515..3151844d1699 100644 --- a/drivers/platform/x86/wireless-hotkey.c +++ b/drivers/platform/x86/wireless-hotkey.c @@ -35,16 +35,17 @@ static const struct acpi_device_id wl_ids[] = { {"", 0}, }; -static int wireless_input_setup(struct acpi_device *device) +static int wireless_input_setup(struct device *dev) { - struct wl_button *button = acpi_driver_data(device); + struct wl_button *button = dev_get_drvdata(dev); int err; button->input_dev = input_allocate_device(); if (!button->input_dev) return -ENOMEM; - snprintf(button->phys, sizeof(button->phys), "%s/input0", acpi_device_hid(device)); + snprintf(button->phys, sizeof(button->phys), "%s/input0", + acpi_device_hid(ACPI_COMPANION(dev))); button->input_dev->name = "Wireless hotkeys"; button->input_dev->phys = button->phys; @@ -63,17 +64,17 @@ err_free_dev: return err; } -static void wireless_input_destroy(struct acpi_device *device) +static void wireless_input_destroy(struct device *dev) { - struct wl_button *button = acpi_driver_data(device); + struct wl_button *button = dev_get_drvdata(dev); input_unregister_device(button->input_dev); kfree(button); } -static void wl_notify(struct acpi_device *acpi_dev, u32 event) +static void wl_notify(acpi_handle handle, u32 event, void *data) { - struct wl_button *button = acpi_driver_data(acpi_dev); + struct wl_button *button = data; if (event != 0x80) { pr_info("Received unknown event (0x%x)\n", event); @@ -86,39 +87,52 @@ static void wl_notify(struct acpi_device *acpi_dev, u32 event) input_sync(button->input_dev); } -static int wl_add(struct acpi_device *device) +static int wl_probe(struct platform_device *pdev) { + struct acpi_device *adev; struct wl_button *button; int err; + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return -ENODEV; + button = kzalloc_obj(struct wl_button); if (!button) return -ENOMEM; - device->driver_data = button; + platform_set_drvdata(pdev, button); - err = wireless_input_setup(device); + err = wireless_input_setup(&pdev->dev); if (err) { pr_err("Failed to setup wireless hotkeys\n"); kfree(button); + return err; + } + err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, + wl_notify, button); + if (err) { + pr_err("Failed to install ACPI notify handler\n"); + wireless_input_destroy(&pdev->dev); } return err; } -static void wl_remove(struct acpi_device *device) +static void wl_remove(struct platform_device *pdev) { - wireless_input_destroy(device); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, wl_notify); + wireless_input_destroy(&pdev->dev); } -static struct acpi_driver wl_driver = { - .name = "wireless-hotkey", - .ids = wl_ids, - .ops = { - .add = wl_add, - .remove = wl_remove, - .notify = wl_notify, +static struct platform_driver wl_driver = { + .probe = wl_probe, + .remove = wl_remove, + .driver = { + .name = "wireless-hotkey", + .acpi_match_table = wl_ids, }, }; -module_acpi_driver(wl_driver); +module_platform_driver(wl_driver); diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c index e3a126de421b..6623cf60e4b4 100644 --- a/drivers/platform/x86/wmi-bmof.c +++ b/drivers/platform/x86/wmi-bmof.c @@ -62,7 +62,7 @@ static int wmi_bmof_probe(struct wmi_device *wdev, const void *context) if (!buffer) return -ENOMEM; - ret = wmidev_query_block(wdev, 0, buffer); + ret = wmidev_query_block(wdev, 0, buffer, 0); if (ret < 0) return ret; diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 021009e9085b..cfff7f5eac5d 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -11,8 +11,10 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/bug.h> #include <linux/device.h> #include <linux/dmi.h> +#include <linux/fwnode.h> #include <linux/gpio/consumer.h> #include <linux/gpio/machine.h> #include <linux/irq.h> @@ -154,7 +156,6 @@ static struct platform_device **pdevs; static struct serdev_device **serdevs; static const struct software_node **gpio_button_swnodes; static const struct software_node **swnode_group; -static const struct software_node **gpiochip_node_group; static void (*exit_handler)(void); static __init struct i2c_adapter * @@ -360,6 +361,79 @@ static const struct software_node *cherryview_gpiochip_node_group[] = { NULL }; +const struct software_node crystalcove_gpiochip_node = { + .name = "INT33FD:00", +}; + +static const struct software_node *crystalcove_gpiochip_node_group[] = { + &crystalcove_gpiochip_node, + NULL +}; + +static void gpio_secondary_unset(void *data) +{ + struct device *dev = data; + + set_secondary_fwnode(dev, NULL); + put_device(dev); +} + +static void gpio_secondary_unregister_node_group(void *data) +{ + const struct software_node **nodes = data; + + software_node_unregister_node_group(nodes); +} + +static int gpio_secondary_fwnode_init(struct device *parent, + const struct software_node * const *node_group) +{ + const struct software_node *const *swnode; + struct fwnode_handle *fwnode; + struct device *phys_dev; + int ret; + + if (!node_group) + return 0; + + ret = software_node_register_node_group(node_group); + if (ret) + return ret; + + ret = devm_add_action_or_reset(parent, + gpio_secondary_unregister_node_group, + (void *)node_group); + if (ret) + return ret; + + for (swnode = node_group; *swnode; swnode++) { + struct device *dev __free(put_device) = + acpi_bus_find_device_by_name((*swnode)->name); + if (!dev) + return dev_err_probe(parent, + -ENODEV, "Failed to find the required GPIO controller: %s\n", + (*swnode)->name); + + fwnode = software_node_fwnode(*swnode); + if (WARN_ON(!fwnode)) + return -ENOENT; + + phys_dev = acpi_get_first_physical_node(to_acpi_device(dev)); + if (!phys_dev) + return dev_err_probe(parent, -ENODEV, + "No physical device for ACPI GPIO dev: %pfwP\n", + fwnode); + + set_secondary_fwnode(phys_dev, fwnode); + + ret = devm_add_action_or_reset(parent, gpio_secondary_unset, get_device(phys_dev)); + if (ret) + return ret; + } + + return 0; +} + static void x86_android_tablet_remove(struct platform_device *pdev) { int i; @@ -391,11 +465,11 @@ static void x86_android_tablet_remove(struct platform_device *pdev) software_node_unregister_node_group(gpio_button_swnodes); software_node_unregister_node_group(swnode_group); - software_node_unregister_node_group(gpiochip_node_group); } static __init int x86_android_tablet_probe(struct platform_device *pdev) { + const struct software_node * const *gpiochip_node_group; const struct x86_dev_info *dev_info; const struct dmi_system_id *id; int i, ret = 0; @@ -427,9 +501,19 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) break; } - ret = software_node_register_node_group(gpiochip_node_group); - if (ret) + ret = gpio_secondary_fwnode_init(&pdev->dev, gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); return ret; + } + + if (dev_info->has_crystalcove) { + ret = gpio_secondary_fwnode_init(&pdev->dev, crystalcove_gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + } ret = software_node_register_node_group(dev_info->swnode_group); if (ret) { diff --git a/drivers/platform/x86/x86-android-tablets/dmi.c b/drivers/platform/x86/x86-android-tablets/dmi.c index 4a5720d6fc1d..2ab53030e5a4 100644 --- a/drivers/platform/x86/x86-android-tablets/dmi.c +++ b/drivers/platform/x86/x86-android-tablets/dmi.c @@ -10,7 +10,6 @@ #include <linux/dmi.h> #include <linux/init.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include "x86-android-tablets.h" diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index 8d825e0b4661..52d96ae89078 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -61,13 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { .initial_brightness = 128, }; -static const struct software_node arizona_gpiochip_node = { - .name = "arizona", -}; - -static const struct software_node crystalcove_gpiochip_node = { - .name = "gpio_crystalcove", -}; /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ @@ -416,15 +409,17 @@ static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __ini #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" +static const struct software_node lenovo_yoga_tab2_830_1050_wm5102; + static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { PROPERTY_ENTRY_GPIO("reset-gpios", &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", - &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + &lenovo_yoga_tab2_830_1050_wm5102, 2, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", - &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), + &lenovo_yoga_tab2_830_1050_wm5102, 4, GPIO_ACTIVE_LOW), { } }; @@ -432,14 +427,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { .properties = lenovo_yoga_tab2_830_1050_wm1502_props, }; -static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { - &crystalcove_gpiochip_node, - &arizona_gpiochip_node, - &lenovo_yoga_tab2_830_1050_wm5102, - &generic_lipo_hv_4v35_battery_node, - NULL -}; - static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); static void lenovo_yoga_tab2_830_1050_exit(void); @@ -455,8 +442,9 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, - .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .swnode_group = generic_lipo_hv_4v35_battery_swnodes, .modules = lenovo_yoga_tab2_modules, + .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_830_1050_init, .exit = lenovo_yoga_tab2_830_1050_exit, @@ -800,8 +788,9 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { .pdev_info = lenovo_yoga_tab2_1380_pdevs, .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, - .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .swnode_group = generic_lipo_hv_4v35_battery_swnodes, .modules = lenovo_yoga_tab2_modules, + .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_1380_init, .exit = lenovo_yoga_tab2_830_1050_exit, @@ -985,13 +974,15 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { }, }; +static const struct software_node lenovo_yt3_wm5102; + static const struct property_entry lenovo_yt3_wm1502_props[] = { PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), - PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &lenovo_yt3_wm5102, 2, GPIO_ACTIVE_HIGH), { } }; @@ -1000,11 +991,6 @@ static const struct software_node lenovo_yt3_wm5102 = { .name = "wm5102", }; -static const struct software_node *lenovo_yt3_swnodes[] = { - &arizona_gpiochip_node, - &lenovo_yt3_wm5102, - NULL -}; static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { { @@ -1072,7 +1058,6 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), .spi_dev_info = lenovo_yt3_spi_devs, .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), - .swnode_group = lenovo_yt3_swnodes, .modules = lenovo_yt3_modules, .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, .init = lenovo_yt3_init, diff --git a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c index ebbedfe5f4e8..67dba9aa51f6 100644 --- a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c +++ b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c @@ -242,7 +242,7 @@ static int atla10_ec_probe(struct i2c_client *client) } static const struct i2c_device_id atla10_ec_id_table[] = { - { "vexia_atla10_ec" }, + { .name = "vexia_atla10_ec" }, { } }; MODULE_DEVICE_TABLE(i2c, atla10_ec_id_table); diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h index 2498390958ad..6e6534f8fa6c 100644 --- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h @@ -13,6 +13,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/irqdomain_defs.h> +#include <linux/device-id/dmi.h> #include <linux/spi/spi.h> struct gpio_desc; @@ -95,6 +96,7 @@ struct x86_dev_info { int (*init)(struct device *dev); void (*exit)(void); bool use_pci; + bool has_crystalcove; enum x86_gpiochip_type gpiochip_type; }; @@ -106,6 +108,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); /* Software nodes representing GPIO chips used by various tablets */ extern const struct software_node baytrail_gpiochip_nodes[]; extern const struct software_node cherryview_gpiochip_nodes[]; +extern const struct software_node crystalcove_gpiochip_node; /* * Extern declarations of x86_dev_info structs so there can be a single diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c index badf9e42e015..3874f3336a0d 100644 --- a/drivers/platform/x86/xiaomi-wmi.c +++ b/drivers/platform/x86/xiaomi-wmi.c @@ -83,6 +83,7 @@ static struct wmi_driver xiaomi_wmi_driver = { .name = "xiaomi-wmi", }, .id_table = xiaomi_wmi_id_table, + .min_event_size = 0, .probe = xiaomi_wmi_probe, .notify_new = xiaomi_wmi_notify, .no_singleton = true, diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c index 4d1b1b310cc5..4c3185e2ceec 100644 --- a/drivers/platform/x86/xo15-ebook.c +++ b/drivers/platform/x86/xo15-ebook.c @@ -15,16 +15,14 @@ #include <linux/types.h> #include <linux/input.h> #include <linux/acpi.h> +#include <linux/platform_device.h> #define MODULE_NAME "xo15-ebook" -#define XO15_EBOOK_CLASS MODULE_NAME #define XO15_EBOOK_TYPE_UNKNOWN 0x00 #define XO15_EBOOK_NOTIFY_STATUS 0x80 -#define XO15_EBOOK_SUBCLASS "ebook" #define XO15_EBOOK_HID "XO15EBK" -#define XO15_EBOOK_DEVICE_NAME "EBook Switch" MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); MODULE_LICENSE("GPL"); @@ -38,15 +36,16 @@ MODULE_DEVICE_TABLE(acpi, ebook_device_ids); struct ebook_switch { struct input_dev *input; char phys[32]; /* for input device */ + bool gpe_enabled; }; -static int ebook_send_state(struct acpi_device *device) +static int ebook_send_state(struct device *dev) { - struct ebook_switch *button = acpi_driver_data(device); + struct ebook_switch *button = dev_get_drvdata(dev); unsigned long long state; acpi_status status; - status = acpi_evaluate_integer(device->handle, "EBK", NULL, &state); + status = acpi_evaluate_integer(ACPI_HANDLE(dev), "EBK", NULL, &state); if (ACPI_FAILURE(status)) return -EIO; @@ -56,16 +55,15 @@ static int ebook_send_state(struct acpi_device *device) return 0; } -static void ebook_switch_notify(struct acpi_device *device, u32 event) +static void ebook_switch_notify(acpi_handle handle, u32 event, void *data) { switch (event) { case ACPI_FIXED_HARDWARE_EVENT: case XO15_EBOOK_NOTIFY_STATUS: - ebook_send_state(device); + ebook_send_state(data); break; default: - acpi_handle_debug(device->handle, - "Unsupported event [0x%x]\n", event); + acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event); break; } } @@ -73,90 +71,87 @@ static void ebook_switch_notify(struct acpi_device *device, u32 event) #ifdef CONFIG_PM_SLEEP static int ebook_switch_resume(struct device *dev) { - return ebook_send_state(to_acpi_device(dev)); + return ebook_send_state(dev); } #endif static SIMPLE_DEV_PM_OPS(ebook_switch_pm, NULL, ebook_switch_resume); -static int ebook_switch_add(struct acpi_device *device) +static int ebook_switch_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct acpi_device *device = ACPI_COMPANION(dev); const struct acpi_device_id *id; struct ebook_switch *button; struct input_dev *input; int error; - button = kzalloc_obj(struct ebook_switch); + button = devm_kzalloc(dev, sizeof(*button), GFP_KERNEL); if (!button) return -ENOMEM; - device->driver_data = button; + platform_set_drvdata(pdev, button); - button->input = input = input_allocate_device(); - if (!input) { - error = -ENOMEM; - goto err_free_button; - } + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; - id = acpi_match_acpi_device(ebook_device_ids, device); - if (!id) { - dev_err(&device->dev, "Unsupported hid\n"); - error = -ENODEV; - goto err_free_input; - } + button->input = input; - strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME); - strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS); + id = acpi_match_acpi_device(ebook_device_ids, device); + if (!id) + return dev_err_probe(dev, -ENODEV, "Unsupported hid\n"); snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id); - input->name = acpi_device_name(device); + input->name = "EBook Switch"; input->phys = button->phys; input->id.bustype = BUS_HOST; - input->dev.parent = &device->dev; input->evbit[0] = BIT_MASK(EV_SW); set_bit(SW_TABLET_MODE, input->swbit); error = input_register_device(input); if (error) - goto err_free_input; + return error; - ebook_send_state(device); + error = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + ebook_switch_notify, dev); + if (error) + return error; + + ebook_send_state(dev); if (device->wakeup.flags.valid) { /* Button's GPE is run-wake GPE */ acpi_enable_gpe(device->wakeup.gpe_device, device->wakeup.gpe_number); - device_set_wakeup_enable(&device->dev, true); + button->gpe_enabled = true; } return 0; - - err_free_input: - input_free_device(input); - err_free_button: - kfree(button); - return error; } -static void ebook_switch_remove(struct acpi_device *device) +static void ebook_switch_remove(struct platform_device *pdev) { - struct ebook_switch *button = acpi_driver_data(device); + struct ebook_switch *button = platform_get_drvdata(pdev); + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + + if (button->gpe_enabled) + acpi_disable_gpe(device->wakeup.gpe_device, + device->wakeup.gpe_number); - input_unregister_device(button->input); - kfree(button); + acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, + ebook_switch_notify); } -static struct acpi_driver xo15_ebook_driver = { - .name = MODULE_NAME, - .class = XO15_EBOOK_CLASS, - .ids = ebook_device_ids, - .ops = { - .add = ebook_switch_add, - .remove = ebook_switch_remove, - .notify = ebook_switch_notify, +static struct platform_driver xo15_ebook_driver = { + .probe = ebook_switch_probe, + .remove = ebook_switch_remove, + .driver = { + .name = MODULE_NAME, + .acpi_match_table = ebook_device_ids, + .pm = &ebook_switch_pm, }, - .drv.pm = &ebook_switch_pm, }; -module_acpi_driver(xo15_ebook_driver); +module_platform_driver(xo15_ebook_driver); |
