From e184b1e589a7fbb80bfdd0364c11422999a17a26 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Tue, 27 Jul 2021 09:49:28 -0700 Subject: platform/x86/intel: Move Intel PMT drivers to new subfolder Move all Intel Platform Monitoring Technology drivers to drivers/platform/x86/intel/pmt. Signed-off-by: David E. Box Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210727164928.3171521-1-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index a61f4f3b78a9..14dd0045bc78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9494,7 +9494,7 @@ INTEL PMT DRIVER M: "David E. Box" S: Maintained F: drivers/mfd/intel_pmt.c -F: drivers/platform/x86/intel_pmt_* +F: drivers/platform/x86/intel/pmt/ INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT M: Stanislav Yakovlev -- cgit v1.2.3-59-g8ed1b From 2010319b3c438ba428e9a5c54998c5b05631cf78 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 15 Aug 2021 20:53:56 -0700 Subject: thermal/drivers/intel: Move intel_menlow to thermal drivers Moved drivers/platform/x86/intel_menlow.c to drivers/thermal/intel. Signed-off-by: Srinivas Pandruvada Acked-by: Daniel Lezcano Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20210816035356.1955982-1-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 4 +- drivers/platform/x86/Kconfig | 10 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel_menlow.c | 523 ----------------------------------- drivers/thermal/intel/Kconfig | 9 + drivers/thermal/intel/Makefile | 1 + drivers/thermal/intel/intel_menlow.c | 523 +++++++++++++++++++++++++++++++++++ 7 files changed, 535 insertions(+), 536 deletions(-) delete mode 100644 drivers/platform/x86/intel_menlow.c create mode 100644 drivers/thermal/intel/intel_menlow.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 14dd0045bc78..279af7e9a281 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9458,10 +9458,10 @@ F: include/linux/mfd/intel-m10-bmc.h INTEL MENLOW THERMAL DRIVER M: Sujith Thomas -L: platform-driver-x86@vger.kernel.org +L: linux-pm@vger.kernel.org S: Supported W: https://01.org/linux-acpi -F: drivers/platform/x86/intel_menlow.c +F: drivers/thermal/intel/intel_menlow.c INTEL P-Unit IPC DRIVER M: Zha Qipeng diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 432d72170b00..a87a4960256d 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -733,16 +733,6 @@ config INTEL_INT0002_VGPIO To compile this driver as a module, choose M here: the module will be called intel_int0002_vgpio. -config INTEL_MENLOW - tristate "Thermal Management driver for Intel menlow platform" - depends on ACPI_THERMAL - select THERMAL - help - ACPI thermal management enhancement driver on - Intel Menlow platform. - - If unsure, say N. - config INTEL_OAKTRAIL tristate "Intel Oaktrail Platform Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 9bb3c3f77386..03a1fc20bba5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -75,7 +75,6 @@ obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o -obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c deleted file mode 100644 index 101d7e791a13..000000000000 --- a/drivers/platform/x86/intel_menlow.c +++ /dev/null @@ -1,523 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel menlow Driver for thermal management extension - * - * Copyright (C) 2008 Intel Corp - * Copyright (C) 2008 Sujith Thomas - * Copyright (C) 2008 Zhang Rui - * - * This driver creates the sys I/F for programming the sensors. - * It also implements the driver for intel menlow memory controller (hardware - * id is INT0002) which makes use of the platform specific ACPI methods - * to get/set bandwidth. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_AUTHOR("Thomas Sujith"); -MODULE_AUTHOR("Zhang Rui"); -MODULE_DESCRIPTION("Intel Menlow platform specific driver"); -MODULE_LICENSE("GPL v2"); - -/* - * Memory controller device control - */ - -#define MEMORY_GET_BANDWIDTH "GTHS" -#define MEMORY_SET_BANDWIDTH "STHS" -#define MEMORY_ARG_CUR_BANDWIDTH 1 -#define MEMORY_ARG_MAX_BANDWIDTH 0 - -static void intel_menlow_unregister_sensor(void); - -/* - * GTHS returning 'n' would mean that [0,n-1] states are supported - * In that case max_cstate would be n-1 - * GTHS returning '0' would mean that no bandwidth control states are supported - */ -static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev, - unsigned long *max_state) -{ - struct acpi_device *device = cdev->devdata; - acpi_handle handle = device->handle; - unsigned long long value; - struct acpi_object_list arg_list; - union acpi_object arg; - acpi_status status = AE_OK; - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH; - status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, - &arg_list, &value); - if (ACPI_FAILURE(status)) - return -EFAULT; - - if (!value) - return -EINVAL; - - *max_state = value - 1; - return 0; -} - -static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev, - unsigned long *value) -{ - struct acpi_device *device = cdev->devdata; - acpi_handle handle = device->handle; - unsigned long long result; - struct acpi_object_list arg_list; - union acpi_object arg; - acpi_status status = AE_OK; - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH; - status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, - &arg_list, &result); - if (ACPI_FAILURE(status)) - return -EFAULT; - - *value = result; - return 0; -} - -static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, - unsigned long state) -{ - struct acpi_device *device = cdev->devdata; - acpi_handle handle = device->handle; - struct acpi_object_list arg_list; - union acpi_object arg; - acpi_status status; - unsigned long long temp; - unsigned long max_state; - - if (memory_get_max_bandwidth(cdev, &max_state)) - return -EFAULT; - - if (state > max_state) - return -EINVAL; - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = state; - - status = - acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list, - &temp); - - pr_info("Bandwidth value was %ld: status is %d\n", state, status); - if (ACPI_FAILURE(status)) - return -EFAULT; - - return 0; -} - -static const struct thermal_cooling_device_ops memory_cooling_ops = { - .get_max_state = memory_get_max_bandwidth, - .get_cur_state = memory_get_cur_bandwidth, - .set_cur_state = memory_set_cur_bandwidth, -}; - -/* - * Memory Device Management - */ -static int intel_menlow_memory_add(struct acpi_device *device) -{ - int result = -ENODEV; - struct thermal_cooling_device *cdev; - - if (!device) - return -EINVAL; - - if (!acpi_has_method(device->handle, MEMORY_GET_BANDWIDTH)) - goto end; - - if (!acpi_has_method(device->handle, MEMORY_SET_BANDWIDTH)) - goto end; - - cdev = thermal_cooling_device_register("Memory controller", device, - &memory_cooling_ops); - if (IS_ERR(cdev)) { - result = PTR_ERR(cdev); - goto end; - } - - device->driver_data = cdev; - result = sysfs_create_link(&device->dev.kobj, - &cdev->device.kobj, "thermal_cooling"); - if (result) - goto unregister; - - result = sysfs_create_link(&cdev->device.kobj, - &device->dev.kobj, "device"); - if (result) { - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); - goto unregister; - } - - end: - return result; - - unregister: - thermal_cooling_device_unregister(cdev); - return result; - -} - -static int intel_menlow_memory_remove(struct acpi_device *device) -{ - struct thermal_cooling_device *cdev; - - if (!device) - return -EINVAL; - - cdev = acpi_driver_data(device); - if (!cdev) - return -EINVAL; - - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&cdev->device.kobj, "device"); - thermal_cooling_device_unregister(cdev); - - return 0; -} - -static const struct acpi_device_id intel_menlow_memory_ids[] = { - {"INT0002", 0}, - {"", 0}, -}; - -static struct acpi_driver intel_menlow_memory_driver = { - .name = "intel_menlow_thermal_control", - .ids = intel_menlow_memory_ids, - .ops = { - .add = intel_menlow_memory_add, - .remove = intel_menlow_memory_remove, - }, -}; - -/* - * Sensor control on menlow platform - */ - -#define THERMAL_AUX0 0 -#define THERMAL_AUX1 1 -#define GET_AUX0 "GAX0" -#define GET_AUX1 "GAX1" -#define SET_AUX0 "SAX0" -#define SET_AUX1 "SAX1" - -struct intel_menlow_attribute { - struct device_attribute attr; - struct device *device; - acpi_handle handle; - struct list_head node; -}; - -static LIST_HEAD(intel_menlow_attr_list); -static DEFINE_MUTEX(intel_menlow_attr_lock); - -/* - * sensor_get_auxtrip - get the current auxtrip value from sensor - * @name: Thermalzone name - * @auxtype : AUX0/AUX1 - * @buf: syfs buffer - */ -static int sensor_get_auxtrip(acpi_handle handle, int index, - unsigned long long *value) -{ - acpi_status status; - - if ((index != 0 && index != 1) || !value) - return -EINVAL; - - status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0, - NULL, value); - if (ACPI_FAILURE(status)) - return -EIO; - - return 0; -} - -/* - * sensor_set_auxtrip - set the new auxtrip value to sensor - * @name: Thermalzone name - * @auxtype : AUX0/AUX1 - * @buf: syfs buffer - */ -static int sensor_set_auxtrip(acpi_handle handle, int index, int value) -{ - acpi_status status; - union acpi_object arg = { - ACPI_TYPE_INTEGER - }; - struct acpi_object_list args = { - 1, &arg - }; - unsigned long long temp; - - if (index != 0 && index != 1) - return -EINVAL; - - status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1, - NULL, &temp); - if (ACPI_FAILURE(status)) - return -EIO; - if ((index && value < temp) || (!index && value > temp)) - return -EINVAL; - - arg.integer.value = value; - status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0, - &args, &temp); - if (ACPI_FAILURE(status)) - return -EIO; - - /* do we need to check the return value of SAX0/SAX1 ? */ - - return 0; -} - -#define to_intel_menlow_attr(_attr) \ - container_of(_attr, struct intel_menlow_attribute, attr) - -static ssize_t aux_show(struct device *dev, struct device_attribute *dev_attr, - char *buf, int idx) -{ - struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); - unsigned long long value; - int result; - - result = sensor_get_auxtrip(attr->handle, idx, &value); - if (result) - return result; - - return sprintf(buf, "%lu", deci_kelvin_to_celsius(value)); -} - -static ssize_t aux0_show(struct device *dev, - struct device_attribute *dev_attr, char *buf) -{ - return aux_show(dev, dev_attr, buf, 0); -} - -static ssize_t aux1_show(struct device *dev, - struct device_attribute *dev_attr, char *buf) -{ - return aux_show(dev, dev_attr, buf, 1); -} - -static ssize_t aux_store(struct device *dev, struct device_attribute *dev_attr, - const char *buf, size_t count, int idx) -{ - struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); - int value; - int result; - - /*Sanity check; should be a positive integer */ - if (!sscanf(buf, "%d", &value)) - return -EINVAL; - - if (value < 0) - return -EINVAL; - - result = sensor_set_auxtrip(attr->handle, idx, - celsius_to_deci_kelvin(value)); - return result ? result : count; -} - -static ssize_t aux0_store(struct device *dev, - struct device_attribute *dev_attr, - const char *buf, size_t count) -{ - return aux_store(dev, dev_attr, buf, count, 0); -} - -static ssize_t aux1_store(struct device *dev, - struct device_attribute *dev_attr, - const char *buf, size_t count) -{ - return aux_store(dev, dev_attr, buf, count, 1); -} - -/* BIOS can enable/disable the thermal user application in dabney platform */ -#define BIOS_ENABLED "\\_TZ.GSTS" -static ssize_t bios_enabled_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - unsigned long long bios_enabled; - - status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled); - if (ACPI_FAILURE(status)) - return -ENODEV; - - return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled"); -} - -static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show, - void *store, struct device *dev, - acpi_handle handle) -{ - struct intel_menlow_attribute *attr; - int result; - - attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL); - if (!attr) - return -ENOMEM; - - sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */ - attr->attr.attr.name = name; - attr->attr.attr.mode = mode; - attr->attr.show = show; - attr->attr.store = store; - attr->device = dev; - attr->handle = handle; - - result = device_create_file(dev, &attr->attr); - if (result) { - kfree(attr); - return result; - } - - mutex_lock(&intel_menlow_attr_lock); - list_add_tail(&attr->node, &intel_menlow_attr_list); - mutex_unlock(&intel_menlow_attr_lock); - - return 0; -} - -static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, - void *context, void **rv) -{ - acpi_status status; - acpi_handle dummy; - struct thermal_zone_device *thermal; - int result; - - result = acpi_bus_get_private_data(handle, (void **)&thermal); - if (result) - return 0; - - /* _TZ must have the AUX0/1 methods */ - status = acpi_get_handle(handle, GET_AUX0, &dummy); - if (ACPI_FAILURE(status)) - return (status == AE_NOT_FOUND) ? AE_OK : status; - - status = acpi_get_handle(handle, SET_AUX0, &dummy); - if (ACPI_FAILURE(status)) - return (status == AE_NOT_FOUND) ? AE_OK : status; - - result = intel_menlow_add_one_attribute("aux0", 0644, - aux0_show, aux0_store, - &thermal->device, handle); - if (result) - return AE_ERROR; - - status = acpi_get_handle(handle, GET_AUX1, &dummy); - if (ACPI_FAILURE(status)) - goto aux1_not_found; - - status = acpi_get_handle(handle, SET_AUX1, &dummy); - if (ACPI_FAILURE(status)) - goto aux1_not_found; - - result = intel_menlow_add_one_attribute("aux1", 0644, - aux1_show, aux1_store, - &thermal->device, handle); - if (result) { - intel_menlow_unregister_sensor(); - return AE_ERROR; - } - - /* - * create the "dabney_enabled" attribute which means the user app - * should be loaded or not - */ - - result = intel_menlow_add_one_attribute("bios_enabled", 0444, - bios_enabled_show, NULL, - &thermal->device, handle); - if (result) { - intel_menlow_unregister_sensor(); - return AE_ERROR; - } - - return AE_OK; - - aux1_not_found: - if (status == AE_NOT_FOUND) - return AE_OK; - - intel_menlow_unregister_sensor(); - return status; -} - -static void intel_menlow_unregister_sensor(void) -{ - struct intel_menlow_attribute *pos, *next; - - mutex_lock(&intel_menlow_attr_lock); - list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) { - list_del(&pos->node); - device_remove_file(pos->device, &pos->attr); - kfree(pos); - } - mutex_unlock(&intel_menlow_attr_lock); - - return; -} - -static int __init intel_menlow_module_init(void) -{ - int result = -ENODEV; - acpi_status status; - unsigned long long enable; - - if (acpi_disabled) - return result; - - /* Looking for the \_TZ.GSTS method */ - status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable); - if (ACPI_FAILURE(status) || !enable) - return -ENODEV; - - /* Looking for ACPI device MEM0 with hardware id INT0002 */ - result = acpi_bus_register_driver(&intel_menlow_memory_driver); - if (result) - return result; - - /* Looking for sensors in each ACPI thermal zone */ - status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, - intel_menlow_register_sensor, NULL, NULL, NULL); - if (ACPI_FAILURE(status)) { - acpi_bus_unregister_driver(&intel_menlow_memory_driver); - return -ENODEV; - } - - return 0; -} - -static void __exit intel_menlow_module_exit(void) -{ - acpi_bus_unregister_driver(&intel_menlow_memory_driver); - intel_menlow_unregister_sensor(); -} - -module_init(intel_menlow_module_init); -module_exit(intel_menlow_module_exit); diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig index e4299ca3423c..c83ea5d04a1d 100644 --- a/drivers/thermal/intel/Kconfig +++ b/drivers/thermal/intel/Kconfig @@ -90,3 +90,12 @@ config INTEL_TCC_COOLING Note that, on different platforms, the behavior might be different on how fast the setting takes effect, and how much the CPU frequency is reduced. + +config INTEL_MENLOW + tristate "Thermal Management driver for Intel menlow platform" + depends on ACPI_THERMAL + help + ACPI thermal management enhancement driver on + Intel Menlow platform. + + If unsure, say N. diff --git a/drivers/thermal/intel/Makefile b/drivers/thermal/intel/Makefile index 5ff2afa388f7..960b56268b4a 100644 --- a/drivers/thermal/intel/Makefile +++ b/drivers/thermal/intel/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o obj-$(CONFIG_INTEL_TCC_COOLING) += intel_tcc_cooling.o obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o +obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o diff --git a/drivers/thermal/intel/intel_menlow.c b/drivers/thermal/intel/intel_menlow.c new file mode 100644 index 000000000000..101d7e791a13 --- /dev/null +++ b/drivers/thermal/intel/intel_menlow.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel menlow Driver for thermal management extension + * + * Copyright (C) 2008 Intel Corp + * Copyright (C) 2008 Sujith Thomas + * Copyright (C) 2008 Zhang Rui + * + * This driver creates the sys I/F for programming the sensors. + * It also implements the driver for intel menlow memory controller (hardware + * id is INT0002) which makes use of the platform specific ACPI methods + * to get/set bandwidth. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Thomas Sujith"); +MODULE_AUTHOR("Zhang Rui"); +MODULE_DESCRIPTION("Intel Menlow platform specific driver"); +MODULE_LICENSE("GPL v2"); + +/* + * Memory controller device control + */ + +#define MEMORY_GET_BANDWIDTH "GTHS" +#define MEMORY_SET_BANDWIDTH "STHS" +#define MEMORY_ARG_CUR_BANDWIDTH 1 +#define MEMORY_ARG_MAX_BANDWIDTH 0 + +static void intel_menlow_unregister_sensor(void); + +/* + * GTHS returning 'n' would mean that [0,n-1] states are supported + * In that case max_cstate would be n-1 + * GTHS returning '0' would mean that no bandwidth control states are supported + */ +static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev, + unsigned long *max_state) +{ + struct acpi_device *device = cdev->devdata; + acpi_handle handle = device->handle; + unsigned long long value; + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status = AE_OK; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH; + status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, + &arg_list, &value); + if (ACPI_FAILURE(status)) + return -EFAULT; + + if (!value) + return -EINVAL; + + *max_state = value - 1; + return 0; +} + +static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev, + unsigned long *value) +{ + struct acpi_device *device = cdev->devdata; + acpi_handle handle = device->handle; + unsigned long long result; + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status = AE_OK; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH; + status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, + &arg_list, &result); + if (ACPI_FAILURE(status)) + return -EFAULT; + + *value = result; + return 0; +} + +static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct acpi_device *device = cdev->devdata; + acpi_handle handle = device->handle; + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status; + unsigned long long temp; + unsigned long max_state; + + if (memory_get_max_bandwidth(cdev, &max_state)) + return -EFAULT; + + if (state > max_state) + return -EINVAL; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = state; + + status = + acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list, + &temp); + + pr_info("Bandwidth value was %ld: status is %d\n", state, status); + if (ACPI_FAILURE(status)) + return -EFAULT; + + return 0; +} + +static const struct thermal_cooling_device_ops memory_cooling_ops = { + .get_max_state = memory_get_max_bandwidth, + .get_cur_state = memory_get_cur_bandwidth, + .set_cur_state = memory_set_cur_bandwidth, +}; + +/* + * Memory Device Management + */ +static int intel_menlow_memory_add(struct acpi_device *device) +{ + int result = -ENODEV; + struct thermal_cooling_device *cdev; + + if (!device) + return -EINVAL; + + if (!acpi_has_method(device->handle, MEMORY_GET_BANDWIDTH)) + goto end; + + if (!acpi_has_method(device->handle, MEMORY_SET_BANDWIDTH)) + goto end; + + cdev = thermal_cooling_device_register("Memory controller", device, + &memory_cooling_ops); + if (IS_ERR(cdev)) { + result = PTR_ERR(cdev); + goto end; + } + + device->driver_data = cdev; + result = sysfs_create_link(&device->dev.kobj, + &cdev->device.kobj, "thermal_cooling"); + if (result) + goto unregister; + + result = sysfs_create_link(&cdev->device.kobj, + &device->dev.kobj, "device"); + if (result) { + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); + goto unregister; + } + + end: + return result; + + unregister: + thermal_cooling_device_unregister(cdev); + return result; + +} + +static int intel_menlow_memory_remove(struct acpi_device *device) +{ + struct thermal_cooling_device *cdev; + + if (!device) + return -EINVAL; + + cdev = acpi_driver_data(device); + if (!cdev) + return -EINVAL; + + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); + sysfs_remove_link(&cdev->device.kobj, "device"); + thermal_cooling_device_unregister(cdev); + + return 0; +} + +static const struct acpi_device_id intel_menlow_memory_ids[] = { + {"INT0002", 0}, + {"", 0}, +}; + +static struct acpi_driver intel_menlow_memory_driver = { + .name = "intel_menlow_thermal_control", + .ids = intel_menlow_memory_ids, + .ops = { + .add = intel_menlow_memory_add, + .remove = intel_menlow_memory_remove, + }, +}; + +/* + * Sensor control on menlow platform + */ + +#define THERMAL_AUX0 0 +#define THERMAL_AUX1 1 +#define GET_AUX0 "GAX0" +#define GET_AUX1 "GAX1" +#define SET_AUX0 "SAX0" +#define SET_AUX1 "SAX1" + +struct intel_menlow_attribute { + struct device_attribute attr; + struct device *device; + acpi_handle handle; + struct list_head node; +}; + +static LIST_HEAD(intel_menlow_attr_list); +static DEFINE_MUTEX(intel_menlow_attr_lock); + +/* + * sensor_get_auxtrip - get the current auxtrip value from sensor + * @name: Thermalzone name + * @auxtype : AUX0/AUX1 + * @buf: syfs buffer + */ +static int sensor_get_auxtrip(acpi_handle handle, int index, + unsigned long long *value) +{ + acpi_status status; + + if ((index != 0 && index != 1) || !value) + return -EINVAL; + + status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0, + NULL, value); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +/* + * sensor_set_auxtrip - set the new auxtrip value to sensor + * @name: Thermalzone name + * @auxtype : AUX0/AUX1 + * @buf: syfs buffer + */ +static int sensor_set_auxtrip(acpi_handle handle, int index, int value) +{ + acpi_status status; + union acpi_object arg = { + ACPI_TYPE_INTEGER + }; + struct acpi_object_list args = { + 1, &arg + }; + unsigned long long temp; + + if (index != 0 && index != 1) + return -EINVAL; + + status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1, + NULL, &temp); + if (ACPI_FAILURE(status)) + return -EIO; + if ((index && value < temp) || (!index && value > temp)) + return -EINVAL; + + arg.integer.value = value; + status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0, + &args, &temp); + if (ACPI_FAILURE(status)) + return -EIO; + + /* do we need to check the return value of SAX0/SAX1 ? */ + + return 0; +} + +#define to_intel_menlow_attr(_attr) \ + container_of(_attr, struct intel_menlow_attribute, attr) + +static ssize_t aux_show(struct device *dev, struct device_attribute *dev_attr, + char *buf, int idx) +{ + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); + unsigned long long value; + int result; + + result = sensor_get_auxtrip(attr->handle, idx, &value); + if (result) + return result; + + return sprintf(buf, "%lu", deci_kelvin_to_celsius(value)); +} + +static ssize_t aux0_show(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + return aux_show(dev, dev_attr, buf, 0); +} + +static ssize_t aux1_show(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + return aux_show(dev, dev_attr, buf, 1); +} + +static ssize_t aux_store(struct device *dev, struct device_attribute *dev_attr, + const char *buf, size_t count, int idx) +{ + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); + int value; + int result; + + /*Sanity check; should be a positive integer */ + if (!sscanf(buf, "%d", &value)) + return -EINVAL; + + if (value < 0) + return -EINVAL; + + result = sensor_set_auxtrip(attr->handle, idx, + celsius_to_deci_kelvin(value)); + return result ? result : count; +} + +static ssize_t aux0_store(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + return aux_store(dev, dev_attr, buf, count, 0); +} + +static ssize_t aux1_store(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + return aux_store(dev, dev_attr, buf, count, 1); +} + +/* BIOS can enable/disable the thermal user application in dabney platform */ +#define BIOS_ENABLED "\\_TZ.GSTS" +static ssize_t bios_enabled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + unsigned long long bios_enabled; + + status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled"); +} + +static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show, + void *store, struct device *dev, + acpi_handle handle) +{ + struct intel_menlow_attribute *attr; + int result; + + attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL); + if (!attr) + return -ENOMEM; + + sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */ + attr->attr.attr.name = name; + attr->attr.attr.mode = mode; + attr->attr.show = show; + attr->attr.store = store; + attr->device = dev; + attr->handle = handle; + + result = device_create_file(dev, &attr->attr); + if (result) { + kfree(attr); + return result; + } + + mutex_lock(&intel_menlow_attr_lock); + list_add_tail(&attr->node, &intel_menlow_attr_list); + mutex_unlock(&intel_menlow_attr_lock); + + return 0; +} + +static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + acpi_status status; + acpi_handle dummy; + struct thermal_zone_device *thermal; + int result; + + result = acpi_bus_get_private_data(handle, (void **)&thermal); + if (result) + return 0; + + /* _TZ must have the AUX0/1 methods */ + status = acpi_get_handle(handle, GET_AUX0, &dummy); + if (ACPI_FAILURE(status)) + return (status == AE_NOT_FOUND) ? AE_OK : status; + + status = acpi_get_handle(handle, SET_AUX0, &dummy); + if (ACPI_FAILURE(status)) + return (status == AE_NOT_FOUND) ? AE_OK : status; + + result = intel_menlow_add_one_attribute("aux0", 0644, + aux0_show, aux0_store, + &thermal->device, handle); + if (result) + return AE_ERROR; + + status = acpi_get_handle(handle, GET_AUX1, &dummy); + if (ACPI_FAILURE(status)) + goto aux1_not_found; + + status = acpi_get_handle(handle, SET_AUX1, &dummy); + if (ACPI_FAILURE(status)) + goto aux1_not_found; + + result = intel_menlow_add_one_attribute("aux1", 0644, + aux1_show, aux1_store, + &thermal->device, handle); + if (result) { + intel_menlow_unregister_sensor(); + return AE_ERROR; + } + + /* + * create the "dabney_enabled" attribute which means the user app + * should be loaded or not + */ + + result = intel_menlow_add_one_attribute("bios_enabled", 0444, + bios_enabled_show, NULL, + &thermal->device, handle); + if (result) { + intel_menlow_unregister_sensor(); + return AE_ERROR; + } + + return AE_OK; + + aux1_not_found: + if (status == AE_NOT_FOUND) + return AE_OK; + + intel_menlow_unregister_sensor(); + return status; +} + +static void intel_menlow_unregister_sensor(void) +{ + struct intel_menlow_attribute *pos, *next; + + mutex_lock(&intel_menlow_attr_lock); + list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) { + list_del(&pos->node); + device_remove_file(pos->device, &pos->attr); + kfree(pos); + } + mutex_unlock(&intel_menlow_attr_lock); + + return; +} + +static int __init intel_menlow_module_init(void) +{ + int result = -ENODEV; + acpi_status status; + unsigned long long enable; + + if (acpi_disabled) + return result; + + /* Looking for the \_TZ.GSTS method */ + status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable); + if (ACPI_FAILURE(status) || !enable) + return -ENODEV; + + /* Looking for ACPI device MEM0 with hardware id INT0002 */ + result = acpi_bus_register_driver(&intel_menlow_memory_driver); + if (result) + return result; + + /* Looking for sensors in each ACPI thermal zone */ + status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, + intel_menlow_register_sensor, NULL, NULL, NULL); + if (ACPI_FAILURE(status)) { + acpi_bus_unregister_driver(&intel_menlow_memory_driver); + return -ENODEV; + } + + return 0; +} + +static void __exit intel_menlow_module_exit(void) +{ + acpi_bus_unregister_driver(&intel_menlow_memory_driver); + intel_menlow_unregister_sensor(); +} + +module_init(intel_menlow_module_init); +module_exit(intel_menlow_module_exit); -- cgit v1.2.3-59-g8ed1b From dcfbd31ef4bcf6ceb373911faa4a5299e0547702 Mon Sep 17 00:00:00 2001 From: Shravan S Date: Sat, 24 Jul 2021 02:44:52 +0530 Subject: platform/x86: BIOS SAR driver for Intel M.2 Modem Dynamic BIOS SAR driver exposing dynamic SAR information from BIOS The Dynamic SAR (Specific Absorption Rate) driver uses ACPI DSM (Device Specific Method) to communicate with BIOS and retrieve dynamic SAR information and change notifications. The driver uses sysfs to expose this data to userspace via read and notify. Sysfs interface is documented in detail under: Documentation/ABI/testing/sysfs-driver-intc_sar Signed-off-by: Shravan S Link: https://lore.kernel.org/r/20210723211452.27995-2-s.shravan@intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- Documentation/ABI/testing/sysfs-driver-intc_sar | 54 ++++ MAINTAINERS | 7 + drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/int1092/Kconfig | 14 ++ drivers/platform/x86/intel/int1092/Makefile | 1 + drivers/platform/x86/intel/int1092/intel_sar.c | 316 ++++++++++++++++++++++++ drivers/platform/x86/intel/int1092/intel_sar.h | 86 +++++++ 8 files changed, 480 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-intc_sar create mode 100644 drivers/platform/x86/intel/int1092/Kconfig create mode 100644 drivers/platform/x86/intel/int1092/Makefile create mode 100644 drivers/platform/x86/intel/int1092/intel_sar.c create mode 100644 drivers/platform/x86/intel/int1092/intel_sar.h (limited to 'MAINTAINERS') diff --git a/Documentation/ABI/testing/sysfs-driver-intc_sar b/Documentation/ABI/testing/sysfs-driver-intc_sar new file mode 100644 index 000000000000..ec334b0e5ed9 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-intc_sar @@ -0,0 +1,54 @@ +What: /sys/bus/platform/devices/INTC1092:00/intc_reg +Date: August 2021 +KernelVersion: 5.15 +Contact: Shravan S , + An Sudhakar +Description: + Specific Absorption Rate (SAR) regulatory mode is typically + derived based on information like mcc (Mobile Country Code) and + mnc (Mobile Network Code) that is available for the currently + attached LTE network. A userspace application is required to set + the current SAR regulatory mode on the Dynamic SAR driver using + this sysfs node. Such an application can also read back using + this sysfs node, the currently configured regulatory mode value + from the Dynamic SAR driver. + + Acceptable regulatory modes are: + == ==== + 0 FCC + 1 CE + 2 ISED + == ==== + + - The regulatory mode value has one of the above values. + - The default regulatory mode used in the driver is 0. + +What: /sys/bus/platform/devices/INTC1092:00/intc_data +Date: August 2021 +KernelVersion: 5.15 +Contact: Shravan S , + An Sudhakar +Description: + This sysfs entry is used to retrieve Dynamic SAR information + emitted/maintained by a BIOS that supports Dynamic SAR. + + The retrieved information is in the order given below: + - device_mode + - bandtable_index + - antennatable_index + - sartable_index + + The above information is sent as integer values separated + by a single space. This information can then be pushed to a + WWAN modem that uses this to control the transmit signal + level using the Band/Antenna/SAR table index information. + These parameters are derived/decided by aggregating + device-mode like laptop/tablet/clamshell etc. and the + proximity-sensor data available to the embedded controller on + given host. The regulatory mode configured on Dynamic SAR + driver also influences these values. + + The userspace applications can poll for changes to this file + using POLLPRI event on file-descriptor (fd) obtained by opening + this sysfs entry. Application can then read this information from + the sysfs node and consume the given information. diff --git a/MAINTAINERS b/MAINTAINERS index 279af7e9a281..b731988a4452 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9220,6 +9220,13 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/intel_atomisp2_led.c +INTEL BIOS SAR INT1092 DRIVER +M: Shravan S +M: Intel Corporation +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/intel/int1092/ + INTEL BROXTON PMC DRIVER M: Mika Westerberg M: Zha Qipeng diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 6eec084d9bf9..4dd1fd4450ad 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -16,6 +16,7 @@ menuconfig X86_PLATFORM_DRIVERS_INTEL if X86_PLATFORM_DRIVERS_INTEL +source "drivers/platform/x86/intel/int1092/Kconfig" source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index ca0ec2c85b05..dc6baf420808 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -4,6 +4,7 @@ # Intel x86 Platform-Specific Drivers # +obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ diff --git a/drivers/platform/x86/intel/int1092/Kconfig b/drivers/platform/x86/intel/int1092/Kconfig new file mode 100644 index 000000000000..2e9a177241aa --- /dev/null +++ b/drivers/platform/x86/intel/int1092/Kconfig @@ -0,0 +1,14 @@ +config INTEL_SAR_INT1092 + tristate "Intel Specific Absorption Rate Driver" + depends on ACPI + help + This driver helps to limit the exposure of human body to RF frequency by + providing information to userspace application that will inform the Intel + M.2 modem to regulate the RF power based on SAR data obtained from the + sensors captured in the BIOS. ACPI interface exposes this data from the BIOS + to SAR driver. The front end application in userspace will interact with SAR + driver to obtain information like the device mode, Antenna index, baseband index, + SAR table index and use available communication like MBIM interface to enable + data communication to modem for RF power regulation. Enable this config when + given platform needs to support "Dynamic SAR" configuration for a modem available + on the platform. diff --git a/drivers/platform/x86/intel/int1092/Makefile b/drivers/platform/x86/intel/int1092/Makefile new file mode 100644 index 000000000000..4ab94e541de3 --- /dev/null +++ b/drivers/platform/x86/intel/int1092/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_INTEL_SAR_INT1092) += intel_sar.o diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c new file mode 100644 index 000000000000..379560fe5df9 --- /dev/null +++ b/drivers/platform/x86/intel/int1092/intel_sar.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, Intel Corporation. + */ + +#include +#include +#include +#include +#include "intel_sar.h" + +/** + * get_int_value: Retrieve integer values from ACPI Object + * @obj: acpi_object pointer which has the integer value + * @out: output pointer will get integer value + * + * Function is used to retrieve integer value from acpi object. + * + * Return: + * * 0 on success + * * -EIO if there is an issue in acpi_object passed. + */ +static int get_int_value(union acpi_object *obj, int *out) +{ + if (!obj || obj->type != ACPI_TYPE_INTEGER) + return -EIO; + *out = (int)obj->integer.value; + return 0; +} + +/** + * update_sar_data: sar data is updated based on regulatory mode + * @context: pointer to driver context structure + * + * sar_data is updated based on regulatory value + * context->reg_value will never exceed MAX_REGULATORY + */ +static void update_sar_data(struct wwan_sar_context *context) +{ + struct wwan_device_mode_configuration *config = + &context->config_data[context->reg_value]; + + if (config->device_mode_info && + context->sar_data.device_mode < config->total_dev_mode) { + struct wwan_device_mode_info *dev_mode = + &config->device_mode_info[context->sar_data.device_mode]; + + context->sar_data.antennatable_index = dev_mode->antennatable_index; + context->sar_data.bandtable_index = dev_mode->bandtable_index; + context->sar_data.sartable_index = dev_mode->sartable_index; + } +} + +/** + * parse_package: parse acpi package for retrieving SAR information + * @context: pointer to driver context structure + * @item : acpi_object pointer + * + * Given acpi_object is iterated to retrieve information for each device mode. + * If a given package corresponding to a specific device mode is faulty, it is + * skipped and the specific entry in context structure will have the default value + * of zero. Decoding of subsequent device modes is realized by having "continue" + * statements in the for loop on encountering error in parsing given device mode. + * + * Return: + * AE_OK if success + * AE_ERROR on error + */ +static acpi_status parse_package(struct wwan_sar_context *context, union acpi_object *item) +{ + struct wwan_device_mode_configuration *data; + int value, itr, reg; + union acpi_object *num; + + num = &item->package.elements[0]; + if (get_int_value(num, &value) || value < 0 || value >= MAX_REGULATORY) + return AE_ERROR; + + reg = value; + + data = &context->config_data[reg]; + if (data->total_dev_mode > MAX_DEV_MODES || data->total_dev_mode == 0 || + item->package.count <= data->total_dev_mode) + return AE_ERROR; + + data->device_mode_info = kmalloc_array(data->total_dev_mode, + sizeof(struct wwan_device_mode_info), GFP_KERNEL); + if (!data->device_mode_info) + return AE_ERROR; + + for (itr = 0; itr < data->total_dev_mode; itr++) { + struct wwan_device_mode_info temp = { 0 }; + + num = &item->package.elements[itr + 1]; + if (num->type != ACPI_TYPE_PACKAGE || num->package.count < TOTAL_DATA) + continue; + if (get_int_value(&num->package.elements[0], &temp.device_mode)) + continue; + if (get_int_value(&num->package.elements[1], &temp.bandtable_index)) + continue; + if (get_int_value(&num->package.elements[2], &temp.antennatable_index)) + continue; + if (get_int_value(&num->package.elements[3], &temp.sartable_index)) + continue; + data->device_mode_info[itr] = temp; + } + return AE_OK; +} + +/** + * sar_get_device_mode: Extraction of information from BIOS via DSM calls + * @device: ACPI device for which to retrieve the data + * + * Retrieve the current device mode information from the BIOS. + * + * Return: + * AE_OK on success + * AE_ERROR on error + */ +static acpi_status sar_get_device_mode(struct platform_device *device) +{ + struct wwan_sar_context *context = dev_get_drvdata(&device->dev); + acpi_status status = AE_OK; + union acpi_object *out; + u32 rev = 0; + int value; + + out = acpi_evaluate_dsm(context->handle, &context->guid, rev, + COMMAND_ID_DEV_MODE, NULL); + if (get_int_value(out, &value)) { + dev_err(&device->dev, "DSM cmd:%d Failed to retrieve value\n", COMMAND_ID_DEV_MODE); + status = AE_ERROR; + goto dev_mode_error; + } + context->sar_data.device_mode = value; + update_sar_data(context); + sysfs_notify(&device->dev.kobj, NULL, SYSFS_DATANAME); + +dev_mode_error: + ACPI_FREE(out); + return status; +} + +static const struct acpi_device_id sar_device_ids[] = { + { "INTC1092", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, sar_device_ids); + +static ssize_t intc_data_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct wwan_sar_context *context = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d %d %d %d\n", context->sar_data.device_mode, + context->sar_data.bandtable_index, + context->sar_data.antennatable_index, + context->sar_data.sartable_index); +} +static DEVICE_ATTR_RO(intc_data); + +static ssize_t intc_reg_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct wwan_sar_context *context = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", context->reg_value); +} + +static ssize_t intc_reg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wwan_sar_context *context = dev_get_drvdata(dev); + unsigned int value; + int read; + + if (!count) + return -EINVAL; + read = kstrtouint(buf, 10, &value); + if (read < 0) + return read; + if (value >= MAX_REGULATORY) + return -EOVERFLOW; + context->reg_value = value; + update_sar_data(context); + sysfs_notify(&dev->kobj, NULL, SYSFS_DATANAME); + return count; +} +static DEVICE_ATTR_RW(intc_reg); + +static struct attribute *intcsar_attrs[] = { + &dev_attr_intc_data.attr, + &dev_attr_intc_reg.attr, + NULL +}; + +static struct attribute_group intcsar_group = { + .attrs = intcsar_attrs, +}; + +static void sar_notify(acpi_handle handle, u32 event, void *data) +{ + struct platform_device *device = data; + + if (event == SAR_EVENT) { + if (sar_get_device_mode(device) != AE_OK) + dev_err(&device->dev, "sar_get_device_mode error"); + } +} + +static void sar_get_data(int reg, struct wwan_sar_context *context) +{ + union acpi_object *out, req; + u32 rev = 0; + + req.type = ACPI_TYPE_INTEGER; + req.integer.value = reg; + out = acpi_evaluate_dsm(context->handle, &context->guid, rev, + COMMAND_ID_CONFIG_TABLE, &req); + if (!out) + return; + if (out->type == ACPI_TYPE_PACKAGE && out->package.count >= 3 && + out->package.elements[0].type == ACPI_TYPE_INTEGER && + out->package.elements[1].type == ACPI_TYPE_INTEGER && + out->package.elements[2].type == ACPI_TYPE_PACKAGE && + out->package.elements[2].package.count > 0) { + context->config_data[reg].version = out->package.elements[0].integer.value; + context->config_data[reg].total_dev_mode = + out->package.elements[1].integer.value; + if (context->config_data[reg].total_dev_mode <= 0 || + context->config_data[reg].total_dev_mode > MAX_DEV_MODES) { + ACPI_FREE(out); + return; + } + parse_package(context, &out->package.elements[2]); + } + ACPI_FREE(out); +} + +static int sar_probe(struct platform_device *device) +{ + struct wwan_sar_context *context; + int reg; + int result; + + context = kzalloc(sizeof(*context), GFP_KERNEL); + if (!context) + return -ENOMEM; + + context->sar_device = device; + context->handle = ACPI_HANDLE(&device->dev); + 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; + } + + for (reg = 0; reg < MAX_REGULATORY; reg++) + sar_get_data(reg, context); + + if (sar_get_device_mode(device) != AE_OK) { + dev_err(&device->dev, "Failed to get device mode\n"); + result = -EIO; + goto r_free; + } + + result = sysfs_create_group(&device->dev.kobj, &intcsar_group); + if (result) { + dev_err(&device->dev, "sysfs creation failed\n"); + goto r_free; + } + + 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; + } + return 0; + +r_sys: + sysfs_remove_group(&device->dev.kobj, &intcsar_group); +r_free: + kfree(context); + return result; +} + +static int 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); + return 0; +} + +static struct platform_driver sar_driver = { + .probe = sar_probe, + .remove = sar_remove, + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(sar_device_ids) + } +}; +module_platform_driver(sar_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Platform device driver for INTEL MODEM BIOS SAR"); +MODULE_AUTHOR("Shravan S "); diff --git a/drivers/platform/x86/intel/int1092/intel_sar.h b/drivers/platform/x86/intel/int1092/intel_sar.h new file mode 100644 index 000000000000..b5310510b84c --- /dev/null +++ b/drivers/platform/x86/intel/int1092/intel_sar.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, Intel Corporation. + */ +#ifndef INTEL_SAR_H +#define INTEL_SAR_H + +#define COMMAND_ID_DEV_MODE 1 +#define COMMAND_ID_CONFIG_TABLE 2 +#define DRVNAME "intc_sar" +#define MAX_DEV_MODES 50 +#define MAX_REGULATORY 3 +#define SAR_DSM_UUID "82737E72-3A33-4C45-A9C7-57C0411A5F13" +#define SAR_EVENT 0x80 +#define SYSFS_DATANAME "intc_data" +#define TOTAL_DATA 4 + +/** + * Structure wwan_device_mode_info - device mode information + * Holds the data that needs to be passed to userspace. + * The data is updated from the BIOS sensor information. + * @device_mode: Specific mode of the device + * @bandtable_index: Index of RF band + * @antennatable_index: Index of antenna + * @sartable_index: Index of SAR + */ +struct wwan_device_mode_info { + int device_mode; + int bandtable_index; + int antennatable_index; + int sartable_index; +}; + +/** + * Structure wwan_device_mode_configuration - device configuration + * Holds the data that is configured and obtained on probe event. + * The data is updated from the BIOS sensor information. + * @version: Mode configuration version + * @total_dev_mode: Total number of device modes + * @device_mode_info: pointer to structure wwan_device_mode_info + */ +struct wwan_device_mode_configuration { + int version; + int total_dev_mode; + struct wwan_device_mode_info *device_mode_info; +}; + +/** + * Structure wwan_supported_info - userspace datastore + * Holds the data that is obtained from userspace + * The data is updated from the userspace and send value back in the + * structure format that is mentioned here. + * @reg_mode_needed: regulatory mode set by user for tests + * @bios_table_revision: Version of SAR table + * @num_supported_modes: Total supported modes based on reg_mode + */ +struct wwan_supported_info { + int reg_mode_needed; + int bios_table_revision; + int num_supported_modes; +}; + +/** + * Structure wwan_sar_context - context of SAR + * Holds the complete context as long as the driver is in existence + * The context holds instance of the data used for different cases. + * @guid: Group id + * @handle: store acpi handle + * @reg_value: regulatory value + * Regulatory 0: FCC, 1: CE, 2: ISED + * @sar_device: platform_device type + * @sar_kobject: kobject for sysfs + * @supported_data: wwan_supported_info struct + * @sar_data: wwan_device_mode_info struct + * @config_data: wwan_device_mode_configuration array struct + */ +struct wwan_sar_context { + guid_t guid; + acpi_handle handle; + int reg_value; + struct platform_device *sar_device; + struct wwan_supported_info supported_data; + struct wwan_device_mode_info sar_data; + struct wwan_device_mode_configuration config_data[MAX_REGULATORY]; +}; +#endif /* INTEL_SAR_H */ -- cgit v1.2.3-59-g8ed1b From 386d17b22e422da3285e33fe8dcc282f02d98178 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:43 +0300 Subject: platform/x86: intel_punit_ipc: Move to intel sub-directory Move Intel P-Unit IPC driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-6-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 6 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 6 + drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/punit_ipc.c | 342 +++++++++++++++++++++++++++++++++ drivers/platform/x86/intel_punit_ipc.c | 342 --------------------------------- 7 files changed, 351 insertions(+), 350 deletions(-) create mode 100644 drivers/platform/x86/intel/punit_ipc.c delete mode 100644 drivers/platform/x86/intel_punit_ipc.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index b731988a4452..21813a0e00eb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9475,7 +9475,7 @@ M: Zha Qipeng L: platform-driver-x86@vger.kernel.org S: Maintained F: arch/x86/include/asm/intel_punit_ipc.h -F: drivers/platform/x86/intel_punit_ipc.c +F: drivers/platform/x86/intel/punit_ipc.c INTEL PMC CORE DRIVER M: Rajneesh Bhardwaj diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 2d6723bb6459..9cadce7908ea 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1180,12 +1180,6 @@ config INTEL_PMC_CORE - Low Power Mode registers (Tigerlake and beyond) - PMC quirks as needed to enable SLPS0/S0ix -config INTEL_PUNIT_IPC - tristate "Intel P-Unit IPC Driver" - help - This driver provides support for Intel P-Unit Mailbox IPC mechanism, - which is used to bridge the communications between kernel and P-Unit. - config INTEL_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index f70c0aa30cdd..776370ce8fbb 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -128,7 +128,6 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o -obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 9e719db8450c..c4ceb5ee83f3 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -53,4 +53,10 @@ config INTEL_MRFLD_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_mrfld_pwrbtn. +config INTEL_PUNIT_IPC + tristate "Intel P-Unit IPC Driver" + help + This driver provides support for Intel P-Unit Mailbox IPC mechanism, + which is used to bridge the communications between kernel and P-Unit. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 4ff755a11770..58fc8e7a3b62 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -16,3 +16,5 @@ intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o +intel_punit_ipc-y := punit_ipc.o +obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o diff --git a/drivers/platform/x86/intel/punit_ipc.c b/drivers/platform/x86/intel/punit_ipc.c new file mode 100644 index 000000000000..f58b8543f6ac --- /dev/null +++ b/drivers/platform/x86/intel/punit_ipc.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Intel P-Unit Mailbox IPC mechanism + * + * (C) Copyright 2015 Intel Corporation + * + * The heart of the P-Unit is the Foxton microcontroller and its firmware, + * which provide mailbox interface for power management usage. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* IPC Mailbox registers */ +#define OFFSET_DATA_LOW 0x0 +#define OFFSET_DATA_HIGH 0x4 +/* bit field of interface register */ +#define CMD_RUN BIT(31) +#define CMD_ERRCODE_MASK GENMASK(7, 0) +#define CMD_PARA1_SHIFT 8 +#define CMD_PARA2_SHIFT 16 + +#define CMD_TIMEOUT_SECONDS 1 + +enum { + BASE_DATA = 0, + BASE_IFACE, + BASE_MAX, +}; + +typedef struct { + struct device *dev; + struct mutex lock; + int irq; + struct completion cmd_complete; + /* base of interface and data registers */ + void __iomem *base[RESERVED_IPC][BASE_MAX]; + IPC_TYPE type; +} IPC_DEV; + +static IPC_DEV *punit_ipcdev; + +static inline u32 ipc_read_status(IPC_DEV *ipcdev, IPC_TYPE type) +{ + return readl(ipcdev->base[type][BASE_IFACE]); +} + +static inline void ipc_write_cmd(IPC_DEV *ipcdev, IPC_TYPE type, u32 cmd) +{ + writel(cmd, ipcdev->base[type][BASE_IFACE]); +} + +static inline u32 ipc_read_data_low(IPC_DEV *ipcdev, IPC_TYPE type) +{ + return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW); +} + +static inline u32 ipc_read_data_high(IPC_DEV *ipcdev, IPC_TYPE type) +{ + return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH); +} + +static inline void ipc_write_data_low(IPC_DEV *ipcdev, IPC_TYPE type, u32 data) +{ + writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW); +} + +static inline void ipc_write_data_high(IPC_DEV *ipcdev, IPC_TYPE type, u32 data) +{ + writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH); +} + +static const char *ipc_err_string(int error) +{ + if (error == IPC_PUNIT_ERR_SUCCESS) + return "no error"; + else if (error == IPC_PUNIT_ERR_INVALID_CMD) + return "invalid command"; + else if (error == IPC_PUNIT_ERR_INVALID_PARAMETER) + return "invalid parameter"; + else if (error == IPC_PUNIT_ERR_CMD_TIMEOUT) + return "command timeout"; + else if (error == IPC_PUNIT_ERR_CMD_LOCKED) + return "command locked"; + else if (error == IPC_PUNIT_ERR_INVALID_VR_ID) + return "invalid vr id"; + else if (error == IPC_PUNIT_ERR_VR_ERR) + return "vr error"; + else + return "unknown error"; +} + +static int intel_punit_ipc_check_status(IPC_DEV *ipcdev, IPC_TYPE type) +{ + int loops = CMD_TIMEOUT_SECONDS * USEC_PER_SEC; + int errcode; + int status; + + if (ipcdev->irq) { + if (!wait_for_completion_timeout(&ipcdev->cmd_complete, + CMD_TIMEOUT_SECONDS * HZ)) { + dev_err(ipcdev->dev, "IPC timed out\n"); + return -ETIMEDOUT; + } + } else { + while ((ipc_read_status(ipcdev, type) & CMD_RUN) && --loops) + udelay(1); + if (!loops) { + dev_err(ipcdev->dev, "IPC timed out\n"); + return -ETIMEDOUT; + } + } + + status = ipc_read_status(ipcdev, type); + errcode = status & CMD_ERRCODE_MASK; + if (errcode) { + dev_err(ipcdev->dev, "IPC failed: %s, IPC_STS=0x%x\n", + ipc_err_string(errcode), status); + return -EIO; + } + + return 0; +} + +/** + * intel_punit_ipc_simple_command() - Simple IPC command + * @cmd: IPC command code. + * @para1: First 8bit parameter, set 0 if not used. + * @para2: Second 8bit parameter, set 0 if not used. + * + * Send a IPC command to P-Unit when there is no data transaction + * + * Return: IPC error code or 0 on success. + */ +int intel_punit_ipc_simple_command(int cmd, int para1, int para2) +{ + IPC_DEV *ipcdev = punit_ipcdev; + IPC_TYPE type; + u32 val; + int ret; + + mutex_lock(&ipcdev->lock); + + reinit_completion(&ipcdev->cmd_complete); + type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET; + + val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK; + val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT; + ipc_write_cmd(ipcdev, type, val); + ret = intel_punit_ipc_check_status(ipcdev, type); + + mutex_unlock(&ipcdev->lock); + + return ret; +} +EXPORT_SYMBOL(intel_punit_ipc_simple_command); + +/** + * intel_punit_ipc_command() - IPC command with data and pointers + * @cmd: IPC command code. + * @para1: First 8bit parameter, set 0 if not used. + * @para2: Second 8bit parameter, set 0 if not used. + * @in: Input data, 32bit for BIOS cmd, two 32bit for GTD and ISPD. + * @out: Output data. + * + * Send a IPC command to P-Unit with data transaction + * + * Return: IPC error code or 0 on success. + */ +int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2, u32 *in, u32 *out) +{ + IPC_DEV *ipcdev = punit_ipcdev; + IPC_TYPE type; + u32 val; + int ret; + + mutex_lock(&ipcdev->lock); + + reinit_completion(&ipcdev->cmd_complete); + type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET; + + if (in) { + ipc_write_data_low(ipcdev, type, *in); + if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC) + ipc_write_data_high(ipcdev, type, *++in); + } + + val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK; + val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT; + ipc_write_cmd(ipcdev, type, val); + + ret = intel_punit_ipc_check_status(ipcdev, type); + if (ret) + goto out; + + if (out) { + *out = ipc_read_data_low(ipcdev, type); + if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC) + *++out = ipc_read_data_high(ipcdev, type); + } + +out: + mutex_unlock(&ipcdev->lock); + return ret; +} +EXPORT_SYMBOL_GPL(intel_punit_ipc_command); + +static irqreturn_t intel_punit_ioc(int irq, void *dev_id) +{ + IPC_DEV *ipcdev = dev_id; + + complete(&ipcdev->cmd_complete); + return IRQ_HANDLED; +} + +static int intel_punit_get_bars(struct platform_device *pdev) +{ + void __iomem *addr; + + /* + * The following resources are required + * - BIOS_IPC BASE_DATA + * - BIOS_IPC BASE_IFACE + */ + addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(addr)) + return PTR_ERR(addr); + punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr; + + addr = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(addr)) + return PTR_ERR(addr); + punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr; + + /* + * The following resources are optional + * - ISPDRIVER_IPC BASE_DATA + * - ISPDRIVER_IPC BASE_IFACE + * - GTDRIVER_IPC BASE_DATA + * - GTDRIVER_IPC BASE_IFACE + */ + addr = devm_platform_ioremap_resource(pdev, 2); + if (!IS_ERR(addr)) + punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr; + + addr = devm_platform_ioremap_resource(pdev, 3); + if (!IS_ERR(addr)) + punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr; + + addr = devm_platform_ioremap_resource(pdev, 4); + if (!IS_ERR(addr)) + punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr; + + addr = devm_platform_ioremap_resource(pdev, 5); + if (!IS_ERR(addr)) + punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr; + + return 0; +} + +static int intel_punit_ipc_probe(struct platform_device *pdev) +{ + int irq, ret; + + punit_ipcdev = devm_kzalloc(&pdev->dev, + sizeof(*punit_ipcdev), GFP_KERNEL); + if (!punit_ipcdev) + return -ENOMEM; + + platform_set_drvdata(pdev, punit_ipcdev); + + irq = platform_get_irq_optional(pdev, 0); + if (irq < 0) { + dev_warn(&pdev->dev, "Invalid IRQ, using polling mode\n"); + } else { + ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc, + IRQF_NO_SUSPEND, "intel_punit_ipc", + &punit_ipcdev); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", irq); + return ret; + } + punit_ipcdev->irq = irq; + } + + ret = intel_punit_get_bars(pdev); + if (ret) + return ret; + + punit_ipcdev->dev = &pdev->dev; + mutex_init(&punit_ipcdev->lock); + init_completion(&punit_ipcdev->cmd_complete); + + return 0; +} + +static int intel_punit_ipc_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct acpi_device_id punit_ipc_acpi_ids[] = { + { "INT34D4", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, punit_ipc_acpi_ids); + +static struct platform_driver intel_punit_ipc_driver = { + .probe = intel_punit_ipc_probe, + .remove = intel_punit_ipc_remove, + .driver = { + .name = "intel_punit_ipc", + .acpi_match_table = ACPI_PTR(punit_ipc_acpi_ids), + }, +}; + +static int __init intel_punit_ipc_init(void) +{ + return platform_driver_register(&intel_punit_ipc_driver); +} + +static void __exit intel_punit_ipc_exit(void) +{ + platform_driver_unregister(&intel_punit_ipc_driver); +} + +MODULE_AUTHOR("Zha Qipeng "); +MODULE_DESCRIPTION("Intel P-Unit IPC driver"); +MODULE_LICENSE("GPL v2"); + +/* Some modules are dependent on this, so init earlier */ +fs_initcall(intel_punit_ipc_init); +module_exit(intel_punit_ipc_exit); diff --git a/drivers/platform/x86/intel_punit_ipc.c b/drivers/platform/x86/intel_punit_ipc.c deleted file mode 100644 index f58b8543f6ac..000000000000 --- a/drivers/platform/x86/intel_punit_ipc.c +++ /dev/null @@ -1,342 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Driver for the Intel P-Unit Mailbox IPC mechanism - * - * (C) Copyright 2015 Intel Corporation - * - * The heart of the P-Unit is the Foxton microcontroller and its firmware, - * which provide mailbox interface for power management usage. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* IPC Mailbox registers */ -#define OFFSET_DATA_LOW 0x0 -#define OFFSET_DATA_HIGH 0x4 -/* bit field of interface register */ -#define CMD_RUN BIT(31) -#define CMD_ERRCODE_MASK GENMASK(7, 0) -#define CMD_PARA1_SHIFT 8 -#define CMD_PARA2_SHIFT 16 - -#define CMD_TIMEOUT_SECONDS 1 - -enum { - BASE_DATA = 0, - BASE_IFACE, - BASE_MAX, -}; - -typedef struct { - struct device *dev; - struct mutex lock; - int irq; - struct completion cmd_complete; - /* base of interface and data registers */ - void __iomem *base[RESERVED_IPC][BASE_MAX]; - IPC_TYPE type; -} IPC_DEV; - -static IPC_DEV *punit_ipcdev; - -static inline u32 ipc_read_status(IPC_DEV *ipcdev, IPC_TYPE type) -{ - return readl(ipcdev->base[type][BASE_IFACE]); -} - -static inline void ipc_write_cmd(IPC_DEV *ipcdev, IPC_TYPE type, u32 cmd) -{ - writel(cmd, ipcdev->base[type][BASE_IFACE]); -} - -static inline u32 ipc_read_data_low(IPC_DEV *ipcdev, IPC_TYPE type) -{ - return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW); -} - -static inline u32 ipc_read_data_high(IPC_DEV *ipcdev, IPC_TYPE type) -{ - return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH); -} - -static inline void ipc_write_data_low(IPC_DEV *ipcdev, IPC_TYPE type, u32 data) -{ - writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW); -} - -static inline void ipc_write_data_high(IPC_DEV *ipcdev, IPC_TYPE type, u32 data) -{ - writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH); -} - -static const char *ipc_err_string(int error) -{ - if (error == IPC_PUNIT_ERR_SUCCESS) - return "no error"; - else if (error == IPC_PUNIT_ERR_INVALID_CMD) - return "invalid command"; - else if (error == IPC_PUNIT_ERR_INVALID_PARAMETER) - return "invalid parameter"; - else if (error == IPC_PUNIT_ERR_CMD_TIMEOUT) - return "command timeout"; - else if (error == IPC_PUNIT_ERR_CMD_LOCKED) - return "command locked"; - else if (error == IPC_PUNIT_ERR_INVALID_VR_ID) - return "invalid vr id"; - else if (error == IPC_PUNIT_ERR_VR_ERR) - return "vr error"; - else - return "unknown error"; -} - -static int intel_punit_ipc_check_status(IPC_DEV *ipcdev, IPC_TYPE type) -{ - int loops = CMD_TIMEOUT_SECONDS * USEC_PER_SEC; - int errcode; - int status; - - if (ipcdev->irq) { - if (!wait_for_completion_timeout(&ipcdev->cmd_complete, - CMD_TIMEOUT_SECONDS * HZ)) { - dev_err(ipcdev->dev, "IPC timed out\n"); - return -ETIMEDOUT; - } - } else { - while ((ipc_read_status(ipcdev, type) & CMD_RUN) && --loops) - udelay(1); - if (!loops) { - dev_err(ipcdev->dev, "IPC timed out\n"); - return -ETIMEDOUT; - } - } - - status = ipc_read_status(ipcdev, type); - errcode = status & CMD_ERRCODE_MASK; - if (errcode) { - dev_err(ipcdev->dev, "IPC failed: %s, IPC_STS=0x%x\n", - ipc_err_string(errcode), status); - return -EIO; - } - - return 0; -} - -/** - * intel_punit_ipc_simple_command() - Simple IPC command - * @cmd: IPC command code. - * @para1: First 8bit parameter, set 0 if not used. - * @para2: Second 8bit parameter, set 0 if not used. - * - * Send a IPC command to P-Unit when there is no data transaction - * - * Return: IPC error code or 0 on success. - */ -int intel_punit_ipc_simple_command(int cmd, int para1, int para2) -{ - IPC_DEV *ipcdev = punit_ipcdev; - IPC_TYPE type; - u32 val; - int ret; - - mutex_lock(&ipcdev->lock); - - reinit_completion(&ipcdev->cmd_complete); - type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET; - - val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK; - val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT; - ipc_write_cmd(ipcdev, type, val); - ret = intel_punit_ipc_check_status(ipcdev, type); - - mutex_unlock(&ipcdev->lock); - - return ret; -} -EXPORT_SYMBOL(intel_punit_ipc_simple_command); - -/** - * intel_punit_ipc_command() - IPC command with data and pointers - * @cmd: IPC command code. - * @para1: First 8bit parameter, set 0 if not used. - * @para2: Second 8bit parameter, set 0 if not used. - * @in: Input data, 32bit for BIOS cmd, two 32bit for GTD and ISPD. - * @out: Output data. - * - * Send a IPC command to P-Unit with data transaction - * - * Return: IPC error code or 0 on success. - */ -int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2, u32 *in, u32 *out) -{ - IPC_DEV *ipcdev = punit_ipcdev; - IPC_TYPE type; - u32 val; - int ret; - - mutex_lock(&ipcdev->lock); - - reinit_completion(&ipcdev->cmd_complete); - type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET; - - if (in) { - ipc_write_data_low(ipcdev, type, *in); - if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC) - ipc_write_data_high(ipcdev, type, *++in); - } - - val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK; - val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT; - ipc_write_cmd(ipcdev, type, val); - - ret = intel_punit_ipc_check_status(ipcdev, type); - if (ret) - goto out; - - if (out) { - *out = ipc_read_data_low(ipcdev, type); - if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC) - *++out = ipc_read_data_high(ipcdev, type); - } - -out: - mutex_unlock(&ipcdev->lock); - return ret; -} -EXPORT_SYMBOL_GPL(intel_punit_ipc_command); - -static irqreturn_t intel_punit_ioc(int irq, void *dev_id) -{ - IPC_DEV *ipcdev = dev_id; - - complete(&ipcdev->cmd_complete); - return IRQ_HANDLED; -} - -static int intel_punit_get_bars(struct platform_device *pdev) -{ - void __iomem *addr; - - /* - * The following resources are required - * - BIOS_IPC BASE_DATA - * - BIOS_IPC BASE_IFACE - */ - addr = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(addr)) - return PTR_ERR(addr); - punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr; - - addr = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(addr)) - return PTR_ERR(addr); - punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr; - - /* - * The following resources are optional - * - ISPDRIVER_IPC BASE_DATA - * - ISPDRIVER_IPC BASE_IFACE - * - GTDRIVER_IPC BASE_DATA - * - GTDRIVER_IPC BASE_IFACE - */ - addr = devm_platform_ioremap_resource(pdev, 2); - if (!IS_ERR(addr)) - punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr; - - addr = devm_platform_ioremap_resource(pdev, 3); - if (!IS_ERR(addr)) - punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr; - - addr = devm_platform_ioremap_resource(pdev, 4); - if (!IS_ERR(addr)) - punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr; - - addr = devm_platform_ioremap_resource(pdev, 5); - if (!IS_ERR(addr)) - punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr; - - return 0; -} - -static int intel_punit_ipc_probe(struct platform_device *pdev) -{ - int irq, ret; - - punit_ipcdev = devm_kzalloc(&pdev->dev, - sizeof(*punit_ipcdev), GFP_KERNEL); - if (!punit_ipcdev) - return -ENOMEM; - - platform_set_drvdata(pdev, punit_ipcdev); - - irq = platform_get_irq_optional(pdev, 0); - if (irq < 0) { - dev_warn(&pdev->dev, "Invalid IRQ, using polling mode\n"); - } else { - ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc, - IRQF_NO_SUSPEND, "intel_punit_ipc", - &punit_ipcdev); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq: %d\n", irq); - return ret; - } - punit_ipcdev->irq = irq; - } - - ret = intel_punit_get_bars(pdev); - if (ret) - return ret; - - punit_ipcdev->dev = &pdev->dev; - mutex_init(&punit_ipcdev->lock); - init_completion(&punit_ipcdev->cmd_complete); - - return 0; -} - -static int intel_punit_ipc_remove(struct platform_device *pdev) -{ - return 0; -} - -static const struct acpi_device_id punit_ipc_acpi_ids[] = { - { "INT34D4", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, punit_ipc_acpi_ids); - -static struct platform_driver intel_punit_ipc_driver = { - .probe = intel_punit_ipc_probe, - .remove = intel_punit_ipc_remove, - .driver = { - .name = "intel_punit_ipc", - .acpi_match_table = ACPI_PTR(punit_ipc_acpi_ids), - }, -}; - -static int __init intel_punit_ipc_init(void) -{ - return platform_driver_register(&intel_punit_ipc_driver); -} - -static void __exit intel_punit_ipc_exit(void) -{ - platform_driver_unregister(&intel_punit_ipc_driver); -} - -MODULE_AUTHOR("Zha Qipeng "); -MODULE_DESCRIPTION("Intel P-Unit IPC driver"); -MODULE_LICENSE("GPL v2"); - -/* Some modules are dependent on this, so init earlier */ -fs_initcall(intel_punit_ipc_init); -module_exit(intel_punit_ipc_exit); -- cgit v1.2.3-59-g8ed1b From fa082a7cf5a66a42a06da048afd972e2cc1b67ea Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:44 +0300 Subject: platform/x86: intel_pmc_core: Move to intel sub-directory Move Intel PMC core driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Reviewed-by: Rajneesh Bhardwaj Link: https://lore.kernel.org/r/20210820110458.73018-7-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 21 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/pmc/Kconfig | 25 + drivers/platform/x86/intel/pmc/Makefile | 9 + drivers/platform/x86/intel/pmc/core.c | 1859 ++++++++++++++++++++++++++ drivers/platform/x86/intel/pmc/core.h | 346 +++++ drivers/platform/x86/intel/pmc/pltdrv.c | 80 ++ drivers/platform/x86/intel_pmc_core.c | 1859 -------------------------- drivers/platform/x86/intel_pmc_core.h | 346 ----- drivers/platform/x86/intel_pmc_core_pltdrv.c | 80 -- 13 files changed, 2322 insertions(+), 2308 deletions(-) create mode 100644 drivers/platform/x86/intel/pmc/Kconfig create mode 100644 drivers/platform/x86/intel/pmc/Makefile create mode 100644 drivers/platform/x86/intel/pmc/core.c create mode 100644 drivers/platform/x86/intel/pmc/core.h create mode 100644 drivers/platform/x86/intel/pmc/pltdrv.c delete mode 100644 drivers/platform/x86/intel_pmc_core.c delete mode 100644 drivers/platform/x86/intel_pmc_core.h delete mode 100644 drivers/platform/x86/intel_pmc_core_pltdrv.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 21813a0e00eb..eefe4edb1b3d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9483,7 +9483,7 @@ M: David E Box L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-platform-intel-pmc -F: drivers/platform/x86/intel_pmc_core* +F: drivers/platform/x86/intel/pmc/ INTEL PMIC GPIO DRIVERS M: Andy Shevchenko diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 9cadce7908ea..aed7570388ef 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1159,27 +1159,6 @@ config INTEL_UNCORE_FREQ_CONTROL To compile this driver as a module, choose M here: the module will be called intel-uncore-frequency. -config INTEL_PMC_CORE - tristate "Intel PMC Core driver" - depends on PCI - depends on ACPI - help - The Intel Platform Controller Hub for Intel Core SoCs provides access - to Power Management Controller registers via various interfaces. This - driver can utilize debugging capabilities and supported features as - exposed by the Power Management Controller. It also may perform some - tasks in the PMC in order to enable transition into the SLPS0 state. - It should be selected on all Intel platforms supported by the driver. - - Supported features: - - SLP_S0_RESIDENCY counter - - PCH IP Power Gating status - - LTR Ignore / LTR Show - - MPHY/PLL gating status (Sunrisepoint PCH only) - - SLPS0 Debug registers (Cannonlake/Icelake PCH) - - Low Power Mode registers (Tigerlake and beyond) - - PMC quirks as needed to enable SLPS0/S0ix - config INTEL_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 776370ce8fbb..1668f7360833 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -127,7 +127,6 @@ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices -obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index c4ceb5ee83f3..7de11636904d 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -19,6 +19,7 @@ if X86_PLATFORM_DRIVERS_INTEL source "drivers/platform/x86/intel/int1092/Kconfig" source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" +source "drivers/platform/x86/intel/pmc/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" config INTEL_BXTWC_PMIC_TMU diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 58fc8e7a3b62..a1555a1e421d 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ +obj-$(CONFIG_INTEL_PMC_CORE) += pmc/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ # Intel PMIC / PMC / P-Unit drivers diff --git a/drivers/platform/x86/intel/pmc/Kconfig b/drivers/platform/x86/intel/pmc/Kconfig new file mode 100644 index 000000000000..b526597e4deb --- /dev/null +++ b/drivers/platform/x86/intel/pmc/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Intel x86 Platform-Specific Drivers +# + +config INTEL_PMC_CORE + tristate "Intel PMC Core driver" + depends on PCI + depends on ACPI + help + The Intel Platform Controller Hub for Intel Core SoCs provides access + to Power Management Controller registers via various interfaces. This + driver can utilize debugging capabilities and supported features as + exposed by the Power Management Controller. It also may perform some + tasks in the PMC in order to enable transition into the SLPS0 state. + It should be selected on all Intel platforms supported by the driver. + + Supported features: + - SLP_S0_RESIDENCY counter + - PCH IP Power Gating status + - LTR Ignore / LTR Show + - MPHY/PLL gating status (Sunrisepoint PCH only) + - SLPS0 Debug registers (Cannonlake/Icelake PCH) + - Low Power Mode registers (Tigerlake and beyond) + - PMC quirks as needed to enable SLPS0/S0ix diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile new file mode 100644 index 000000000000..8966fcdc0e1d --- /dev/null +++ b/drivers/platform/x86/intel/pmc/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Intel x86 Platform-Specific Drivers +# + +intel_pmc_core-y := core.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 diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c new file mode 100644 index 000000000000..9963bc3d470c --- /dev/null +++ b/drivers/platform/x86/intel/pmc/core.c @@ -0,0 +1,1859 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Core SoC Power Management Controller Driver + * + * Copyright (c) 2016, Intel Corporation. + * All Rights Reserved. + * + * Authors: Rajneesh Bhardwaj + * Vishwanath Somayaji + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "core.h" + +#define ACPI_S0IX_DSM_UUID "57a6512e-3979-4e9d-9708-ff13b2508972" +#define ACPI_GET_LOW_MODE_REGISTERS 1 + +/* PKGC MSRs are common across Intel Core SoCs */ +static const struct pmc_bit_map msr_map[] = { + {"Package C2", MSR_PKG_C2_RESIDENCY}, + {"Package C3", MSR_PKG_C3_RESIDENCY}, + {"Package C6", MSR_PKG_C6_RESIDENCY}, + {"Package C7", MSR_PKG_C7_RESIDENCY}, + {"Package C8", MSR_PKG_C8_RESIDENCY}, + {"Package C9", MSR_PKG_C9_RESIDENCY}, + {"Package C10", MSR_PKG_C10_RESIDENCY}, + {} +}; + +static const struct pmc_bit_map spt_pll_map[] = { + {"MIPI PLL", SPT_PMC_BIT_MPHY_CMN_LANE0}, + {"GEN2 USB2PCIE2 PLL", SPT_PMC_BIT_MPHY_CMN_LANE1}, + {"DMIPCIE3 PLL", SPT_PMC_BIT_MPHY_CMN_LANE2}, + {"SATA PLL", SPT_PMC_BIT_MPHY_CMN_LANE3}, + {} +}; + +static const struct pmc_bit_map spt_mphy_map[] = { + {"MPHY CORE LANE 0", SPT_PMC_BIT_MPHY_LANE0}, + {"MPHY CORE LANE 1", SPT_PMC_BIT_MPHY_LANE1}, + {"MPHY CORE LANE 2", SPT_PMC_BIT_MPHY_LANE2}, + {"MPHY CORE LANE 3", SPT_PMC_BIT_MPHY_LANE3}, + {"MPHY CORE LANE 4", SPT_PMC_BIT_MPHY_LANE4}, + {"MPHY CORE LANE 5", SPT_PMC_BIT_MPHY_LANE5}, + {"MPHY CORE LANE 6", SPT_PMC_BIT_MPHY_LANE6}, + {"MPHY CORE LANE 7", SPT_PMC_BIT_MPHY_LANE7}, + {"MPHY CORE LANE 8", SPT_PMC_BIT_MPHY_LANE8}, + {"MPHY CORE LANE 9", SPT_PMC_BIT_MPHY_LANE9}, + {"MPHY CORE LANE 10", SPT_PMC_BIT_MPHY_LANE10}, + {"MPHY CORE LANE 11", SPT_PMC_BIT_MPHY_LANE11}, + {"MPHY CORE LANE 12", SPT_PMC_BIT_MPHY_LANE12}, + {"MPHY CORE LANE 13", SPT_PMC_BIT_MPHY_LANE13}, + {"MPHY CORE LANE 14", SPT_PMC_BIT_MPHY_LANE14}, + {"MPHY CORE LANE 15", SPT_PMC_BIT_MPHY_LANE15}, + {} +}; + +static const struct pmc_bit_map spt_pfear_map[] = { + {"PMC", SPT_PMC_BIT_PMC}, + {"OPI-DMI", SPT_PMC_BIT_OPI}, + {"SPI / eSPI", SPT_PMC_BIT_SPI}, + {"XHCI", SPT_PMC_BIT_XHCI}, + {"SPA", SPT_PMC_BIT_SPA}, + {"SPB", SPT_PMC_BIT_SPB}, + {"SPC", SPT_PMC_BIT_SPC}, + {"GBE", SPT_PMC_BIT_GBE}, + {"SATA", SPT_PMC_BIT_SATA}, + {"HDA-PGD0", SPT_PMC_BIT_HDA_PGD0}, + {"HDA-PGD1", SPT_PMC_BIT_HDA_PGD1}, + {"HDA-PGD2", SPT_PMC_BIT_HDA_PGD2}, + {"HDA-PGD3", SPT_PMC_BIT_HDA_PGD3}, + {"RSVD", SPT_PMC_BIT_RSVD_0B}, + {"LPSS", SPT_PMC_BIT_LPSS}, + {"LPC", SPT_PMC_BIT_LPC}, + {"SMB", SPT_PMC_BIT_SMB}, + {"ISH", SPT_PMC_BIT_ISH}, + {"P2SB", SPT_PMC_BIT_P2SB}, + {"DFX", SPT_PMC_BIT_DFX}, + {"SCC", SPT_PMC_BIT_SCC}, + {"RSVD", SPT_PMC_BIT_RSVD_0C}, + {"FUSE", SPT_PMC_BIT_FUSE}, + {"CAMERA", SPT_PMC_BIT_CAMREA}, + {"RSVD", SPT_PMC_BIT_RSVD_0D}, + {"USB3-OTG", SPT_PMC_BIT_USB3_OTG}, + {"EXI", SPT_PMC_BIT_EXI}, + {"CSE", SPT_PMC_BIT_CSE}, + {"CSME_KVM", SPT_PMC_BIT_CSME_KVM}, + {"CSME_PMT", SPT_PMC_BIT_CSME_PMT}, + {"CSME_CLINK", SPT_PMC_BIT_CSME_CLINK}, + {"CSME_PTIO", SPT_PMC_BIT_CSME_PTIO}, + {"CSME_USBR", SPT_PMC_BIT_CSME_USBR}, + {"CSME_SUSRAM", SPT_PMC_BIT_CSME_SUSRAM}, + {"CSME_SMT", SPT_PMC_BIT_CSME_SMT}, + {"RSVD", SPT_PMC_BIT_RSVD_1A}, + {"CSME_SMS2", SPT_PMC_BIT_CSME_SMS2}, + {"CSME_SMS1", SPT_PMC_BIT_CSME_SMS1}, + {"CSME_RTC", SPT_PMC_BIT_CSME_RTC}, + {"CSME_PSF", SPT_PMC_BIT_CSME_PSF}, + {} +}; + +static const struct pmc_bit_map *ext_spt_pfear_map[] = { + /* + * Check intel_pmc_core_ids[] users of spt_reg_map for + * a list of core SoCs using this. + */ + spt_pfear_map, + NULL +}; + +static const struct pmc_bit_map spt_ltr_show_map[] = { + {"SOUTHPORT_A", SPT_PMC_LTR_SPA}, + {"SOUTHPORT_B", SPT_PMC_LTR_SPB}, + {"SATA", SPT_PMC_LTR_SATA}, + {"GIGABIT_ETHERNET", SPT_PMC_LTR_GBE}, + {"XHCI", SPT_PMC_LTR_XHCI}, + {"Reserved", SPT_PMC_LTR_RESERVED}, + {"ME", SPT_PMC_LTR_ME}, + /* EVA is Enterprise Value Add, doesn't really exist on PCH */ + {"EVA", SPT_PMC_LTR_EVA}, + {"SOUTHPORT_C", SPT_PMC_LTR_SPC}, + {"HD_AUDIO", SPT_PMC_LTR_AZ}, + {"LPSS", SPT_PMC_LTR_LPSS}, + {"SOUTHPORT_D", SPT_PMC_LTR_SPD}, + {"SOUTHPORT_E", SPT_PMC_LTR_SPE}, + {"CAMERA", SPT_PMC_LTR_CAM}, + {"ESPI", SPT_PMC_LTR_ESPI}, + {"SCC", SPT_PMC_LTR_SCC}, + {"ISH", SPT_PMC_LTR_ISH}, + /* Below two cannot be used for LTR_IGNORE */ + {"CURRENT_PLATFORM", SPT_PMC_LTR_CUR_PLT}, + {"AGGREGATED_SYSTEM", SPT_PMC_LTR_CUR_ASLT}, + {} +}; + +static const struct pmc_reg_map spt_reg_map = { + .pfear_sts = ext_spt_pfear_map, + .mphy_sts = spt_mphy_map, + .pll_sts = spt_pll_map, + .ltr_show_sts = spt_ltr_show_map, + .msr_sts = msr_map, + .slp_s0_offset = SPT_PMC_SLP_S0_RES_COUNTER_OFFSET, + .slp_s0_res_counter_step = SPT_PMC_SLP_S0_RES_COUNTER_STEP, + .ltr_ignore_offset = SPT_PMC_LTR_IGNORE_OFFSET, + .regmap_length = SPT_PMC_MMIO_REG_LEN, + .ppfear0_offset = SPT_PMC_XRAM_PPFEAR0A, + .ppfear_buckets = SPT_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = SPT_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = SPT_PMC_READ_DISABLE_BIT, + .ltr_ignore_max = SPT_NUM_IP_IGN_ALLOWED, + .pm_vric1_offset = SPT_PMC_VRIC1_OFFSET, +}; + +/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */ +static const struct pmc_bit_map cnp_pfear_map[] = { + {"PMC", BIT(0)}, + {"OPI-DMI", BIT(1)}, + {"SPI/eSPI", BIT(2)}, + {"XHCI", BIT(3)}, + {"SPA", BIT(4)}, + {"SPB", BIT(5)}, + {"SPC", BIT(6)}, + {"GBE", BIT(7)}, + + {"SATA", BIT(0)}, + {"HDA_PGD0", BIT(1)}, + {"HDA_PGD1", BIT(2)}, + {"HDA_PGD2", BIT(3)}, + {"HDA_PGD3", BIT(4)}, + {"SPD", BIT(5)}, + {"LPSS", BIT(6)}, + {"LPC", BIT(7)}, + + {"SMB", BIT(0)}, + {"ISH", BIT(1)}, + {"P2SB", BIT(2)}, + {"NPK_VNN", BIT(3)}, + {"SDX", BIT(4)}, + {"SPE", BIT(5)}, + {"Fuse", BIT(6)}, + {"SBR8", BIT(7)}, + + {"CSME_FSC", BIT(0)}, + {"USB3_OTG", BIT(1)}, + {"EXI", BIT(2)}, + {"CSE", BIT(3)}, + {"CSME_KVM", BIT(4)}, + {"CSME_PMT", BIT(5)}, + {"CSME_CLINK", BIT(6)}, + {"CSME_PTIO", BIT(7)}, + + {"CSME_USBR", BIT(0)}, + {"CSME_SUSRAM", BIT(1)}, + {"CSME_SMT1", BIT(2)}, + {"CSME_SMT4", BIT(3)}, + {"CSME_SMS2", BIT(4)}, + {"CSME_SMS1", BIT(5)}, + {"CSME_RTC", BIT(6)}, + {"CSME_PSF", BIT(7)}, + + {"SBR0", BIT(0)}, + {"SBR1", BIT(1)}, + {"SBR2", BIT(2)}, + {"SBR3", BIT(3)}, + {"SBR4", BIT(4)}, + {"SBR5", BIT(5)}, + {"CSME_PECI", BIT(6)}, + {"PSF1", BIT(7)}, + + {"PSF2", BIT(0)}, + {"PSF3", BIT(1)}, + {"PSF4", BIT(2)}, + {"CNVI", BIT(3)}, + {"UFS0", BIT(4)}, + {"EMMC", BIT(5)}, + {"SPF", BIT(6)}, + {"SBR6", BIT(7)}, + + {"SBR7", BIT(0)}, + {"NPK_AON", BIT(1)}, + {"HDA_PGD4", BIT(2)}, + {"HDA_PGD5", BIT(3)}, + {"HDA_PGD6", BIT(4)}, + {"PSF6", BIT(5)}, + {"PSF7", BIT(6)}, + {"PSF8", BIT(7)}, + {} +}; + +static const struct pmc_bit_map *ext_cnp_pfear_map[] = { + /* + * Check intel_pmc_core_ids[] users of cnp_reg_map for + * a list of core SoCs using this. + */ + cnp_pfear_map, + NULL +}; + +static const struct pmc_bit_map icl_pfear_map[] = { + {"RES_65", BIT(0)}, + {"RES_66", BIT(1)}, + {"RES_67", BIT(2)}, + {"TAM", BIT(3)}, + {"GBETSN", BIT(4)}, + {"TBTLSX", BIT(5)}, + {"RES_71", BIT(6)}, + {"RES_72", BIT(7)}, + {} +}; + +static const struct pmc_bit_map *ext_icl_pfear_map[] = { + /* + * Check intel_pmc_core_ids[] users of icl_reg_map for + * a list of core SoCs using this. + */ + cnp_pfear_map, + icl_pfear_map, + NULL +}; + +static const struct pmc_bit_map tgl_pfear_map[] = { + {"PSF9", BIT(0)}, + {"RES_66", BIT(1)}, + {"RES_67", BIT(2)}, + {"RES_68", BIT(3)}, + {"RES_69", BIT(4)}, + {"RES_70", BIT(5)}, + {"TBTLSX", BIT(6)}, + {} +}; + +static const struct pmc_bit_map *ext_tgl_pfear_map[] = { + /* + * Check intel_pmc_core_ids[] users of tgl_reg_map for + * a list of core SoCs using this. + */ + cnp_pfear_map, + tgl_pfear_map, + NULL +}; + +static const struct pmc_bit_map cnp_slps0_dbg0_map[] = { + {"AUDIO_D3", BIT(0)}, + {"OTG_D3", BIT(1)}, + {"XHCI_D3", BIT(2)}, + {"LPIO_D3", BIT(3)}, + {"SDX_D3", BIT(4)}, + {"SATA_D3", BIT(5)}, + {"UFS0_D3", BIT(6)}, + {"UFS1_D3", BIT(7)}, + {"EMMC_D3", BIT(8)}, + {} +}; + +static const struct pmc_bit_map cnp_slps0_dbg1_map[] = { + {"SDIO_PLL_OFF", BIT(0)}, + {"USB2_PLL_OFF", BIT(1)}, + {"AUDIO_PLL_OFF", BIT(2)}, + {"OC_PLL_OFF", BIT(3)}, + {"MAIN_PLL_OFF", BIT(4)}, + {"XOSC_OFF", BIT(5)}, + {"LPC_CLKS_GATED", BIT(6)}, + {"PCIE_CLKREQS_IDLE", BIT(7)}, + {"AUDIO_ROSC_OFF", BIT(8)}, + {"HPET_XOSC_CLK_REQ", BIT(9)}, + {"PMC_ROSC_SLOW_CLK", BIT(10)}, + {"AON2_ROSC_GATED", BIT(11)}, + {"CLKACKS_DEASSERTED", BIT(12)}, + {} +}; + +static const struct pmc_bit_map cnp_slps0_dbg2_map[] = { + {"MPHY_CORE_GATED", BIT(0)}, + {"CSME_GATED", BIT(1)}, + {"USB2_SUS_GATED", BIT(2)}, + {"DYN_FLEX_IO_IDLE", BIT(3)}, + {"GBE_NO_LINK", BIT(4)}, + {"THERM_SEN_DISABLED", BIT(5)}, + {"PCIE_LOW_POWER", BIT(6)}, + {"ISH_VNNAON_REQ_ACT", BIT(7)}, + {"ISH_VNN_REQ_ACT", BIT(8)}, + {"CNV_VNNAON_REQ_ACT", BIT(9)}, + {"CNV_VNN_REQ_ACT", BIT(10)}, + {"NPK_VNNON_REQ_ACT", BIT(11)}, + {"PMSYNC_STATE_IDLE", BIT(12)}, + {"ALST_GT_THRES", BIT(13)}, + {"PMC_ARC_PG_READY", BIT(14)}, + {} +}; + +static const struct pmc_bit_map *cnp_slps0_dbg_maps[] = { + cnp_slps0_dbg0_map, + cnp_slps0_dbg1_map, + cnp_slps0_dbg2_map, + NULL +}; + +static const struct pmc_bit_map cnp_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}, + {"Reserved", CNP_PMC_LTR_RESERVED}, + {"ME", CNP_PMC_LTR_ME}, + /* EVA is Enterprise Value Add, doesn't really exist on PCH */ + {"EVA", 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}, + {"CAMERA", CNP_PMC_LTR_CAM}, + {"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}, + /* + * Check intel_pmc_core_ids[] users of cnp_reg_map for + * a list of core SoCs using this. + */ + {"WIGIG", ICL_PMC_LTR_WIGIG}, + {"THC0", TGL_PMC_LTR_THC0}, + {"THC1", TGL_PMC_LTR_THC1}, + /* Below two cannot be used for LTR_IGNORE */ + {"CURRENT_PLATFORM", CNP_PMC_LTR_CUR_PLT}, + {"AGGREGATED_SYSTEM", CNP_PMC_LTR_CUR_ASLT}, + {} +}; + +static const struct pmc_reg_map cnp_reg_map = { + .pfear_sts = ext_cnp_pfear_map, + .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, + .slp_s0_res_counter_step = SPT_PMC_SLP_S0_RES_COUNTER_STEP, + .slps0_dbg_maps = cnp_slps0_dbg_maps, + .ltr_show_sts = cnp_ltr_show_map, + .msr_sts = msr_map, + .slps0_dbg_offset = CNP_PMC_SLPS0_DBG_OFFSET, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = CNP_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = CNP_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, + .ltr_ignore_max = CNP_NUM_IP_IGN_ALLOWED, + .etr3_offset = ETR3_OFFSET, +}; + +static const struct pmc_reg_map icl_reg_map = { + .pfear_sts = ext_icl_pfear_map, + .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, + .slp_s0_res_counter_step = ICL_PMC_SLP_S0_RES_COUNTER_STEP, + .slps0_dbg_maps = cnp_slps0_dbg_maps, + .ltr_show_sts = cnp_ltr_show_map, + .msr_sts = msr_map, + .slps0_dbg_offset = CNP_PMC_SLPS0_DBG_OFFSET, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = CNP_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = ICL_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, + .ltr_ignore_max = ICL_NUM_IP_IGN_ALLOWED, + .etr3_offset = ETR3_OFFSET, +}; + +static const struct pmc_bit_map tgl_clocksource_status_map[] = { + {"USB2PLL_OFF_STS", BIT(18)}, + {"PCIe/USB3.1_Gen2PLL_OFF_STS", BIT(19)}, + {"PCIe_Gen3PLL_OFF_STS", BIT(20)}, + {"OPIOPLL_OFF_STS", BIT(21)}, + {"OCPLL_OFF_STS", BIT(22)}, + {"MainPLL_OFF_STS", BIT(23)}, + {"MIPIPLL_OFF_STS", BIT(24)}, + {"Fast_XTAL_Osc_OFF_STS", BIT(25)}, + {"AC_Ring_Osc_OFF_STS", BIT(26)}, + {"MC_Ring_Osc_OFF_STS", BIT(27)}, + {"SATAPLL_OFF_STS", BIT(29)}, + {"XTAL_USB2PLL_OFF_STS", BIT(31)}, + {} +}; + +static const struct pmc_bit_map tgl_power_gating_status_map[] = { + {"CSME_PG_STS", BIT(0)}, + {"SATA_PG_STS", BIT(1)}, + {"xHCI_PG_STS", BIT(2)}, + {"UFSX2_PG_STS", BIT(3)}, + {"OTG_PG_STS", BIT(5)}, + {"SPA_PG_STS", BIT(6)}, + {"SPB_PG_STS", BIT(7)}, + {"SPC_PG_STS", BIT(8)}, + {"SPD_PG_STS", BIT(9)}, + {"SPE_PG_STS", BIT(10)}, + {"SPF_PG_STS", BIT(11)}, + {"LSX_PG_STS", BIT(13)}, + {"P2SB_PG_STS", BIT(14)}, + {"PSF_PG_STS", BIT(15)}, + {"SBR_PG_STS", BIT(16)}, + {"OPIDMI_PG_STS", BIT(17)}, + {"THC0_PG_STS", BIT(18)}, + {"THC1_PG_STS", BIT(19)}, + {"GBETSN_PG_STS", BIT(20)}, + {"GBE_PG_STS", BIT(21)}, + {"LPSS_PG_STS", BIT(22)}, + {"MMP_UFSX2_PG_STS", BIT(23)}, + {"MMP_UFSX2B_PG_STS", BIT(24)}, + {"FIA_PG_STS", BIT(25)}, + {} +}; + +static const struct pmc_bit_map tgl_d3_status_map[] = { + {"ADSP_D3_STS", BIT(0)}, + {"SATA_D3_STS", BIT(1)}, + {"xHCI0_D3_STS", BIT(2)}, + {"xDCI1_D3_STS", BIT(5)}, + {"SDX_D3_STS", BIT(6)}, + {"EMMC_D3_STS", BIT(7)}, + {"IS_D3_STS", BIT(8)}, + {"THC0_D3_STS", BIT(9)}, + {"THC1_D3_STS", BIT(10)}, + {"GBE_D3_STS", BIT(11)}, + {"GBE_TSN_D3_STS", BIT(12)}, + {} +}; + +static const struct pmc_bit_map tgl_vnn_req_status_map[] = { + {"GPIO_COM0_VNN_REQ_STS", BIT(1)}, + {"GPIO_COM1_VNN_REQ_STS", BIT(2)}, + {"GPIO_COM2_VNN_REQ_STS", BIT(3)}, + {"GPIO_COM3_VNN_REQ_STS", BIT(4)}, + {"GPIO_COM4_VNN_REQ_STS", BIT(5)}, + {"GPIO_COM5_VNN_REQ_STS", BIT(6)}, + {"Audio_VNN_REQ_STS", BIT(7)}, + {"ISH_VNN_REQ_STS", BIT(8)}, + {"CNVI_VNN_REQ_STS", BIT(9)}, + {"eSPI_VNN_REQ_STS", BIT(10)}, + {"Display_VNN_REQ_STS", BIT(11)}, + {"DTS_VNN_REQ_STS", BIT(12)}, + {"SMBUS_VNN_REQ_STS", BIT(14)}, + {"CSME_VNN_REQ_STS", BIT(15)}, + {"SMLINK0_VNN_REQ_STS", BIT(16)}, + {"SMLINK1_VNN_REQ_STS", BIT(17)}, + {"CLINK_VNN_REQ_STS", BIT(20)}, + {"DCI_VNN_REQ_STS", BIT(21)}, + {"ITH_VNN_REQ_STS", BIT(22)}, + {"CSME_VNN_REQ_STS", BIT(24)}, + {"GBE_VNN_REQ_STS", BIT(25)}, + {} +}; + +static const struct pmc_bit_map tgl_vnn_misc_status_map[] = { + {"CPU_C10_REQ_STS_0", BIT(0)}, + {"PCIe_LPM_En_REQ_STS_3", BIT(3)}, + {"ITH_REQ_STS_5", BIT(5)}, + {"CNVI_REQ_STS_6", BIT(6)}, + {"ISH_REQ_STS_7", BIT(7)}, + {"USB2_SUS_PG_Sys_REQ_STS_10", BIT(10)}, + {"PCIe_Clk_REQ_STS_12", BIT(12)}, + {"MPHY_Core_DL_REQ_STS_16", BIT(16)}, + {"Break-even_En_REQ_STS_17", BIT(17)}, + {"Auto-demo_En_REQ_STS_18", BIT(18)}, + {"MPHY_SUS_REQ_STS_22", BIT(22)}, + {"xDCI_attached_REQ_STS_24", BIT(24)}, + {} +}; + +static const struct pmc_bit_map tgl_signal_status_map[] = { + {"LSX_Wake0_En_STS", BIT(0)}, + {"LSX_Wake0_Pol_STS", BIT(1)}, + {"LSX_Wake1_En_STS", BIT(2)}, + {"LSX_Wake1_Pol_STS", BIT(3)}, + {"LSX_Wake2_En_STS", BIT(4)}, + {"LSX_Wake2_Pol_STS", BIT(5)}, + {"LSX_Wake3_En_STS", BIT(6)}, + {"LSX_Wake3_Pol_STS", BIT(7)}, + {"LSX_Wake4_En_STS", BIT(8)}, + {"LSX_Wake4_Pol_STS", BIT(9)}, + {"LSX_Wake5_En_STS", BIT(10)}, + {"LSX_Wake5_Pol_STS", BIT(11)}, + {"LSX_Wake6_En_STS", BIT(12)}, + {"LSX_Wake6_Pol_STS", BIT(13)}, + {"LSX_Wake7_En_STS", BIT(14)}, + {"LSX_Wake7_Pol_STS", BIT(15)}, + {"Intel_Se_IO_Wake0_En_STS", BIT(16)}, + {"Intel_Se_IO_Wake0_Pol_STS", BIT(17)}, + {"Intel_Se_IO_Wake1_En_STS", BIT(18)}, + {"Intel_Se_IO_Wake1_Pol_STS", BIT(19)}, + {"Int_Timer_SS_Wake0_En_STS", BIT(20)}, + {"Int_Timer_SS_Wake0_Pol_STS", BIT(21)}, + {"Int_Timer_SS_Wake1_En_STS", BIT(22)}, + {"Int_Timer_SS_Wake1_Pol_STS", BIT(23)}, + {"Int_Timer_SS_Wake2_En_STS", BIT(24)}, + {"Int_Timer_SS_Wake2_Pol_STS", BIT(25)}, + {"Int_Timer_SS_Wake3_En_STS", BIT(26)}, + {"Int_Timer_SS_Wake3_Pol_STS", BIT(27)}, + {"Int_Timer_SS_Wake4_En_STS", BIT(28)}, + {"Int_Timer_SS_Wake4_Pol_STS", BIT(29)}, + {"Int_Timer_SS_Wake5_En_STS", BIT(30)}, + {"Int_Timer_SS_Wake5_Pol_STS", BIT(31)}, + {} +}; + +static const struct pmc_bit_map *tgl_lpm_maps[] = { + tgl_clocksource_status_map, + tgl_power_gating_status_map, + tgl_d3_status_map, + tgl_vnn_req_status_map, + tgl_vnn_misc_status_map, + tgl_signal_status_map, + NULL +}; + +static const struct pmc_reg_map tgl_reg_map = { + .pfear_sts = ext_tgl_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 = cnp_ltr_show_map, + .msr_sts = msr_map, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = CNP_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = ICL_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, + .ltr_ignore_max = TGL_NUM_IP_IGN_ALLOWED, + .lpm_num_maps = TGL_LPM_NUM_MAPS, + .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, + .lpm_sts_latch_en_offset = TGL_LPM_STS_LATCH_EN_OFFSET, + .lpm_en_offset = TGL_LPM_EN_OFFSET, + .lpm_priority_offset = TGL_LPM_PRI_OFFSET, + .lpm_residency_offset = TGL_LPM_RESIDENCY_OFFSET, + .lpm_sts = tgl_lpm_maps, + .lpm_status_offset = TGL_LPM_STATUS_OFFSET, + .lpm_live_status_offset = TGL_LPM_LIVE_STATUS_OFFSET, + .etr3_offset = ETR3_OFFSET, +}; + +static void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev) +{ + struct pmc_dev *pmcdev = platform_get_drvdata(pdev); + const int num_maps = pmcdev->map->lpm_num_maps; + u32 lpm_size = LPM_MAX_NUM_MODES * num_maps * 4; + union acpi_object *out_obj; + struct acpi_device *adev; + guid_t s0ix_dsm_guid; + u32 *lpm_req_regs, *addr; + + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return; + + guid_parse(ACPI_S0IX_DSM_UUID, &s0ix_dsm_guid); + + out_obj = acpi_evaluate_dsm(adev->handle, &s0ix_dsm_guid, 0, + ACPI_GET_LOW_MODE_REGISTERS, NULL); + if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) { + u32 size = out_obj->buffer.length; + + if (size != lpm_size) { + acpi_handle_debug(adev->handle, + "_DSM returned unexpected buffer size, have %u, expect %u\n", + size, lpm_size); + goto free_acpi_obj; + } + } else { + acpi_handle_debug(adev->handle, + "_DSM function 0 evaluation failed\n"); + goto free_acpi_obj; + } + + addr = (u32 *)out_obj->buffer.pointer; + + lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32), + GFP_KERNEL); + if (!lpm_req_regs) + goto free_acpi_obj; + + memcpy(lpm_req_regs, addr, lpm_size); + pmcdev->lpm_req_regs = lpm_req_regs; + +free_acpi_obj: + ACPI_FREE(out_obj); +} + +static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset) +{ + return readl(pmcdev->regbase + reg_offset); +} + +static inline void pmc_core_reg_write(struct pmc_dev *pmcdev, int reg_offset, + u32 val) +{ + writel(val, pmcdev->regbase + reg_offset); +} + +static inline u64 pmc_core_adjust_slp_s0_step(struct pmc_dev *pmcdev, u32 value) +{ + return (u64)value * pmcdev->map->slp_s0_res_counter_step; +} + +static int set_etr3(struct pmc_dev *pmcdev) +{ + const struct pmc_reg_map *map = pmcdev->map; + u32 reg; + int err; + + if (!map->etr3_offset) + return -EOPNOTSUPP; + + mutex_lock(&pmcdev->lock); + + /* check if CF9 is locked */ + reg = pmc_core_reg_read(pmcdev, map->etr3_offset); + if (reg & ETR3_CF9LOCK) { + err = -EACCES; + goto out_unlock; + } + + /* write CF9 global reset bit */ + reg |= ETR3_CF9GR; + pmc_core_reg_write(pmcdev, map->etr3_offset, reg); + + reg = pmc_core_reg_read(pmcdev, map->etr3_offset); + if (!(reg & ETR3_CF9GR)) { + err = -EIO; + goto out_unlock; + } + + err = 0; + +out_unlock: + mutex_unlock(&pmcdev->lock); + return err; +} +static umode_t etr3_is_visible(struct kobject *kobj, + struct attribute *attr, + int idx) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pmc_dev *pmcdev = dev_get_drvdata(dev); + const struct pmc_reg_map *map = pmcdev->map; + u32 reg; + + mutex_lock(&pmcdev->lock); + reg = pmc_core_reg_read(pmcdev, map->etr3_offset); + mutex_unlock(&pmcdev->lock); + + return reg & ETR3_CF9LOCK ? attr->mode & (SYSFS_PREALLOC | 0444) : attr->mode; +} + +static ssize_t etr3_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pmc_dev *pmcdev = dev_get_drvdata(dev); + const struct pmc_reg_map *map = pmcdev->map; + u32 reg; + + if (!map->etr3_offset) + return -EOPNOTSUPP; + + mutex_lock(&pmcdev->lock); + + reg = pmc_core_reg_read(pmcdev, map->etr3_offset); + reg &= ETR3_CF9GR | ETR3_CF9LOCK; + + mutex_unlock(&pmcdev->lock); + + return sysfs_emit(buf, "0x%08x", reg); +} + +static ssize_t etr3_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct pmc_dev *pmcdev = dev_get_drvdata(dev); + int err; + u32 reg; + + err = kstrtouint(buf, 16, ®); + if (err) + return err; + + /* allow only CF9 writes */ + if (reg != ETR3_CF9GR) + return -EINVAL; + + err = set_etr3(pmcdev); + if (err) + return err; + + return len; +} +static DEVICE_ATTR_RW(etr3); + +static struct attribute *pmc_attrs[] = { + &dev_attr_etr3.attr, + NULL +}; + +static const struct attribute_group pmc_attr_group = { + .attrs = pmc_attrs, + .is_visible = etr3_is_visible, +}; + +static const struct attribute_group *pmc_dev_groups[] = { + &pmc_attr_group, + NULL +}; + +static int pmc_core_dev_state_get(void *data, u64 *val) +{ + struct pmc_dev *pmcdev = data; + const struct pmc_reg_map *map = pmcdev->map; + u32 value; + + value = pmc_core_reg_read(pmcdev, map->slp_s0_offset); + *val = pmc_core_adjust_slp_s0_step(pmcdev, value); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n"); + +static int pmc_core_check_read_lock_bit(struct pmc_dev *pmcdev) +{ + u32 value; + + value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_cfg_offset); + return value & BIT(pmcdev->map->pm_read_disable_bit); +} + +static void pmc_core_slps0_display(struct pmc_dev *pmcdev, struct device *dev, + struct seq_file *s) +{ + const struct pmc_bit_map **maps = pmcdev->map->slps0_dbg_maps; + const struct pmc_bit_map *map; + int offset = pmcdev->map->slps0_dbg_offset; + u32 data; + + while (*maps) { + map = *maps; + data = pmc_core_reg_read(pmcdev, offset); + offset += 4; + while (map->name) { + if (dev) + dev_info(dev, "SLP_S0_DBG: %-32s\tState: %s\n", + map->name, + data & map->bit_mask ? "Yes" : "No"); + if (s) + seq_printf(s, "SLP_S0_DBG: %-32s\tState: %s\n", + map->name, + data & map->bit_mask ? "Yes" : "No"); + ++map; + } + ++maps; + } +} + +static int pmc_core_lpm_get_arr_size(const struct pmc_bit_map **maps) +{ + int idx; + + for (idx = 0; maps[idx]; idx++) + ;/* Nothing */ + + return idx; +} + +static void pmc_core_lpm_display(struct pmc_dev *pmcdev, struct device *dev, + struct seq_file *s, u32 offset, + const char *str, + const struct pmc_bit_map **maps) +{ + int index, idx, len = 32, bit_mask, arr_size; + u32 *lpm_regs; + + arr_size = pmc_core_lpm_get_arr_size(maps); + lpm_regs = kmalloc_array(arr_size, sizeof(*lpm_regs), GFP_KERNEL); + if (!lpm_regs) + return; + + for (index = 0; index < arr_size; index++) { + lpm_regs[index] = pmc_core_reg_read(pmcdev, offset); + offset += 4; + } + + for (idx = 0; idx < arr_size; idx++) { + if (dev) + dev_info(dev, "\nLPM_%s_%d:\t0x%x\n", str, idx, + lpm_regs[idx]); + if (s) + seq_printf(s, "\nLPM_%s_%d:\t0x%x\n", str, idx, + lpm_regs[idx]); + for (index = 0; maps[idx][index].name && index < len; index++) { + bit_mask = maps[idx][index].bit_mask; + if (dev) + dev_info(dev, "%-30s %-30d\n", + maps[idx][index].name, + lpm_regs[idx] & bit_mask ? 1 : 0); + if (s) + seq_printf(s, "%-30s %-30d\n", + maps[idx][index].name, + lpm_regs[idx] & bit_mask ? 1 : 0); + } + } + + kfree(lpm_regs); +} + +static bool slps0_dbg_latch; + +static inline u8 pmc_core_reg_read_byte(struct pmc_dev *pmcdev, int offset) +{ + return readb(pmcdev->regbase + offset); +} + +static void pmc_core_display_map(struct seq_file *s, int index, int idx, int ip, + u8 pf_reg, const struct pmc_bit_map **pf_map) +{ + seq_printf(s, "PCH IP: %-2d - %-32s\tState: %s\n", + ip, pf_map[idx][index].name, + pf_map[idx][index].bit_mask & pf_reg ? "Off" : "On"); +} + +static int pmc_core_ppfear_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map **maps = pmcdev->map->pfear_sts; + u8 pf_regs[PPFEAR_MAX_NUM_ENTRIES]; + int index, iter, idx, ip = 0; + + iter = pmcdev->map->ppfear0_offset; + + for (index = 0; index < pmcdev->map->ppfear_buckets && + index < PPFEAR_MAX_NUM_ENTRIES; index++, iter++) + pf_regs[index] = pmc_core_reg_read_byte(pmcdev, iter); + + for (idx = 0; maps[idx]; idx++) { + for (index = 0; maps[idx][index].name && + index < pmcdev->map->ppfear_buckets * 8; ip++, index++) + pmc_core_display_map(s, index, idx, ip, + pf_regs[index / 8], maps); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_ppfear); + +/* This function should return link status, 0 means ready */ +static int pmc_core_mtpmc_link_status(struct pmc_dev *pmcdev) +{ + u32 value; + + value = pmc_core_reg_read(pmcdev, SPT_PMC_PM_STS_OFFSET); + return value & BIT(SPT_PMC_MSG_FULL_STS_BIT); +} + +static int pmc_core_send_msg(struct pmc_dev *pmcdev, u32 *addr_xram) +{ + u32 dest; + int timeout; + + for (timeout = NUM_RETRIES; timeout > 0; timeout--) { + if (pmc_core_mtpmc_link_status(pmcdev) == 0) + break; + msleep(5); + } + + if (timeout <= 0 && pmc_core_mtpmc_link_status(pmcdev)) + return -EBUSY; + + dest = (*addr_xram & MTPMC_MASK) | (1U << 1); + pmc_core_reg_write(pmcdev, SPT_PMC_MTPMC_OFFSET, dest); + return 0; +} + +static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map *map = pmcdev->map->mphy_sts; + u32 mphy_core_reg_low, mphy_core_reg_high; + u32 val_low, val_high; + int index, err = 0; + + if (pmcdev->pmc_xram_read_bit) { + seq_puts(s, "Access denied: please disable PMC_READ_DISABLE setting in BIOS."); + return 0; + } + + mphy_core_reg_low = (SPT_PMC_MPHY_CORE_STS_0 << 16); + mphy_core_reg_high = (SPT_PMC_MPHY_CORE_STS_1 << 16); + + mutex_lock(&pmcdev->lock); + + if (pmc_core_send_msg(pmcdev, &mphy_core_reg_low) != 0) { + err = -EBUSY; + goto out_unlock; + } + + msleep(10); + val_low = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET); + + if (pmc_core_send_msg(pmcdev, &mphy_core_reg_high) != 0) { + err = -EBUSY; + goto out_unlock; + } + + msleep(10); + val_high = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET); + + for (index = 0; index < 8 && map[index].name; index++) { + seq_printf(s, "%-32s\tState: %s\n", + map[index].name, + map[index].bit_mask & val_low ? "Not power gated" : + "Power gated"); + } + + for (index = 8; map[index].name; index++) { + seq_printf(s, "%-32s\tState: %s\n", + map[index].name, + map[index].bit_mask & val_high ? "Not power gated" : + "Power gated"); + } + +out_unlock: + mutex_unlock(&pmcdev->lock); + return err; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_mphy_pg); + +static int pmc_core_pll_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map *map = pmcdev->map->pll_sts; + u32 mphy_common_reg, val; + int index, err = 0; + + if (pmcdev->pmc_xram_read_bit) { + seq_puts(s, "Access denied: please disable PMC_READ_DISABLE setting in BIOS."); + return 0; + } + + mphy_common_reg = (SPT_PMC_MPHY_COM_STS_0 << 16); + mutex_lock(&pmcdev->lock); + + if (pmc_core_send_msg(pmcdev, &mphy_common_reg) != 0) { + err = -EBUSY; + goto out_unlock; + } + + /* Observed PMC HW response latency for MTPMC-MFPMC is ~10 ms */ + msleep(10); + val = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET); + + for (index = 0; map[index].name ; index++) { + seq_printf(s, "%-32s\tState: %s\n", + map[index].name, + map[index].bit_mask & val ? "Active" : "Idle"); + } + +out_unlock: + mutex_unlock(&pmcdev->lock); + return err; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_pll); + +static int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value) +{ + const struct pmc_reg_map *map = pmcdev->map; + u32 reg; + int err = 0; + + mutex_lock(&pmcdev->lock); + + if (value > map->ltr_ignore_max) { + err = -EINVAL; + goto out_unlock; + } + + reg = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset); + reg |= BIT(value); + pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, reg); + +out_unlock: + mutex_unlock(&pmcdev->lock); + + return err; +} + +static ssize_t pmc_core_ltr_ignore_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct pmc_dev *pmcdev = s->private; + u32 buf_size, value; + int err; + + buf_size = min_t(u32, count, 64); + + err = kstrtou32_from_user(userbuf, buf_size, 10, &value); + if (err) + return err; + + err = pmc_core_send_ltr_ignore(pmcdev, value); + + return err == 0 ? count : err; +} + +static int pmc_core_ltr_ignore_show(struct seq_file *s, void *unused) +{ + return 0; +} + +static int pmc_core_ltr_ignore_open(struct inode *inode, struct file *file) +{ + return single_open(file, pmc_core_ltr_ignore_show, inode->i_private); +} + +static const struct file_operations pmc_core_ltr_ignore_ops = { + .open = pmc_core_ltr_ignore_open, + .read = seq_read, + .write = pmc_core_ltr_ignore_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset) +{ + const struct pmc_reg_map *map = pmcdev->map; + u32 fd; + + mutex_lock(&pmcdev->lock); + + if (!reset && !slps0_dbg_latch) + goto out_unlock; + + fd = pmc_core_reg_read(pmcdev, map->slps0_dbg_offset); + if (reset) + fd &= ~CNP_PMC_LATCH_SLPS0_EVENTS; + else + fd |= CNP_PMC_LATCH_SLPS0_EVENTS; + pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd); + + slps0_dbg_latch = false; + +out_unlock: + mutex_unlock(&pmcdev->lock); +} + +static int pmc_core_slps0_dbg_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + + pmc_core_slps0_dbg_latch(pmcdev, false); + pmc_core_slps0_display(pmcdev, NULL, s); + pmc_core_slps0_dbg_latch(pmcdev, true); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_slps0_dbg); + +static u32 convert_ltr_scale(u32 val) +{ + /* + * As per PCIE specification supporting document + * ECN_LatencyTolnReporting_14Aug08.pdf the Latency + * Tolerance Reporting data payload is encoded in a + * 3 bit scale and 10 bit value fields. Values are + * multiplied by the indicated scale to yield an absolute time + * value, expressible in a range from 1 nanosecond to + * 2^25*(2^10-1) = 34,326,183,936 nanoseconds. + * + * scale encoding is as follows: + * + * ---------------------------------------------- + * |scale factor | Multiplier (ns) | + * ---------------------------------------------- + * | 0 | 1 | + * | 1 | 32 | + * | 2 | 1024 | + * | 3 | 32768 | + * | 4 | 1048576 | + * | 5 | 33554432 | + * | 6 | Invalid | + * | 7 | Invalid | + * ---------------------------------------------- + */ + if (val > 5) { + pr_warn("Invalid LTR scale factor.\n"); + return 0; + } + + return 1U << (5 * val); +} + +static int pmc_core_ltr_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map *map = pmcdev->map->ltr_show_sts; + u64 decoded_snoop_ltr, decoded_non_snoop_ltr; + u32 ltr_raw_data, scale, val; + u16 snoop_ltr, nonsnoop_ltr; + int index; + + for (index = 0; map[index].name ; index++) { + decoded_snoop_ltr = decoded_non_snoop_ltr = 0; + ltr_raw_data = pmc_core_reg_read(pmcdev, + map[index].bit_mask); + snoop_ltr = ltr_raw_data & ~MTPMC_MASK; + nonsnoop_ltr = (ltr_raw_data >> 0x10) & ~MTPMC_MASK; + + if (FIELD_GET(LTR_REQ_NONSNOOP, ltr_raw_data)) { + scale = FIELD_GET(LTR_DECODED_SCALE, nonsnoop_ltr); + val = FIELD_GET(LTR_DECODED_VAL, nonsnoop_ltr); + decoded_non_snoop_ltr = val * convert_ltr_scale(scale); + } + + if (FIELD_GET(LTR_REQ_SNOOP, ltr_raw_data)) { + scale = FIELD_GET(LTR_DECODED_SCALE, snoop_ltr); + val = FIELD_GET(LTR_DECODED_VAL, snoop_ltr); + decoded_snoop_ltr = val * convert_ltr_scale(scale); + } + + seq_printf(s, "%-32s\tLTR: RAW: 0x%-16x\tNon-Snoop(ns): %-16llu\tSnoop(ns): %-16llu\n", + map[index].name, ltr_raw_data, + decoded_non_snoop_ltr, + decoded_snoop_ltr); + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr); + +static inline u64 adjust_lpm_residency(struct pmc_dev *pmcdev, u32 offset, + const int lpm_adj_x2) +{ + u64 lpm_res = pmc_core_reg_read(pmcdev, offset); + + return GET_X2_COUNTER((u64)lpm_adj_x2 * lpm_res); +} + +static int pmc_core_substate_res_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const int lpm_adj_x2 = pmcdev->map->lpm_res_counter_step_x2; + u32 offset = pmcdev->map->lpm_residency_offset; + int i, mode; + + seq_printf(s, "%-10s %-15s\n", "Substate", "Residency"); + + pmc_for_each_mode(i, mode, pmcdev) { + seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode], + adjust_lpm_residency(pmcdev, offset + (4 * mode), lpm_adj_x2)); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_res); + +static int pmc_core_substate_sts_regs_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; + u32 offset = pmcdev->map->lpm_status_offset; + + pmc_core_lpm_display(pmcdev, NULL, s, offset, "STATUS", maps); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_sts_regs); + +static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; + u32 offset = pmcdev->map->lpm_live_status_offset; + + pmc_core_lpm_display(pmcdev, NULL, s, offset, "LIVE_STATUS", maps); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs); + +static void pmc_core_substate_req_header_show(struct seq_file *s) +{ + struct pmc_dev *pmcdev = s->private; + int i, mode; + + seq_printf(s, "%30s |", "Element"); + pmc_for_each_mode(i, mode, pmcdev) + seq_printf(s, " %9s |", pmc_lpm_modes[mode]); + + seq_printf(s, " %9s |\n", "Status"); +} + +static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; + const struct pmc_bit_map *map; + const int num_maps = pmcdev->map->lpm_num_maps; + u32 sts_offset = pmcdev->map->lpm_status_offset; + u32 *lpm_req_regs = pmcdev->lpm_req_regs; + int mp; + + /* Display the header */ + pmc_core_substate_req_header_show(s); + + /* Loop over maps */ + for (mp = 0; mp < num_maps; mp++) { + u32 req_mask = 0; + u32 lpm_status; + int mode, idx, i, len = 32; + + /* + * Capture the requirements and create a mask so that we only + * show an element if it's required for at least one of the + * enabled low power modes + */ + pmc_for_each_mode(idx, mode, pmcdev) + req_mask |= lpm_req_regs[mp + (mode * num_maps)]; + + /* Get the last latched status for this map */ + lpm_status = pmc_core_reg_read(pmcdev, sts_offset + (mp * 4)); + + /* Loop over elements in this map */ + map = maps[mp]; + for (i = 0; map[i].name && i < len; i++) { + u32 bit_mask = map[i].bit_mask; + + if (!(bit_mask & req_mask)) + /* + * Not required for any enabled states + * so don't display + */ + continue; + + /* Display the element name in the first column */ + seq_printf(s, "%30s |", map[i].name); + + /* Loop over the enabled states and display if required */ + pmc_for_each_mode(idx, mode, pmcdev) { + if (lpm_req_regs[mp + (mode * num_maps)] & bit_mask) + seq_printf(s, " %9s |", + "Required"); + else + seq_printf(s, " %9s |", " "); + } + + /* In Status column, show the last captured state of this agent */ + if (lpm_status & bit_mask) + seq_printf(s, " %9s |", "Yes"); + else + seq_printf(s, " %9s |", " "); + + seq_puts(s, "\n"); + } + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs); + +static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + bool c10; + u32 reg; + int idx, mode; + + reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); + if (reg & LPM_STS_LATCH_MODE) { + seq_puts(s, "c10"); + c10 = false; + } else { + seq_puts(s, "[c10]"); + c10 = true; + } + + pmc_for_each_mode(idx, mode, pmcdev) { + if ((BIT(mode) & reg) && !c10) + seq_printf(s, " [%s]", pmc_lpm_modes[mode]); + else + seq_printf(s, " %s", pmc_lpm_modes[mode]); + } + + seq_puts(s, " clear\n"); + + return 0; +} + +static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct pmc_dev *pmcdev = s->private; + bool clear = false, c10 = false; + unsigned char buf[8]; + int idx, m, mode; + u32 reg; + + if (count > sizeof(buf) - 1) + return -EINVAL; + if (copy_from_user(buf, userbuf, count)) + return -EFAULT; + buf[count] = '\0'; + + /* + * Allowed strings are: + * Any enabled substate, e.g. 'S0i2.0' + * 'c10' + * 'clear' + */ + mode = sysfs_match_string(pmc_lpm_modes, buf); + + /* Check string matches enabled mode */ + pmc_for_each_mode(idx, m, pmcdev) + if (mode == m) + break; + + if (mode != m || mode < 0) { + if (sysfs_streq(buf, "clear")) + clear = true; + else if (sysfs_streq(buf, "c10")) + c10 = true; + else + return -EINVAL; + } + + if (clear) { + mutex_lock(&pmcdev->lock); + + reg = pmc_core_reg_read(pmcdev, pmcdev->map->etr3_offset); + reg |= ETR3_CLEAR_LPM_EVENTS; + pmc_core_reg_write(pmcdev, pmcdev->map->etr3_offset, reg); + + mutex_unlock(&pmcdev->lock); + + return count; + } + + if (c10) { + mutex_lock(&pmcdev->lock); + + reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); + reg &= ~LPM_STS_LATCH_MODE; + pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); + + mutex_unlock(&pmcdev->lock); + + return count; + } + + /* + * For LPM mode latching we set the latch enable bit and selected mode + * and clear everything else. + */ + reg = LPM_STS_LATCH_MODE | BIT(mode); + mutex_lock(&pmcdev->lock); + pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); + mutex_unlock(&pmcdev->lock); + + return count; +} +DEFINE_PMC_CORE_ATTR_WRITE(pmc_core_lpm_latch_mode); + +static int pmc_core_pkgc_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + const struct pmc_bit_map *map = pmcdev->map->msr_sts; + u64 pcstate_count; + int index; + + for (index = 0; map[index].name ; index++) { + if (rdmsrl_safe(map[index].bit_mask, &pcstate_count)) + continue; + + pcstate_count *= 1000; + do_div(pcstate_count, tsc_khz); + seq_printf(s, "%-8s : %llu\n", map[index].name, + pcstate_count); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc); + +static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order) +{ + int i, j; + + if (!lpm_pri) + return false; + /* + * Each byte contains the priority level for 2 modes (7:4 and 3:0). + * In a 32 bit register this allows for describing 8 modes. Store the + * levels and look for values out of range. + */ + for (i = 0; i < 8; i++) { + int level = lpm_pri & GENMASK(3, 0); + + if (level >= LPM_MAX_NUM_MODES) + return false; + + mode_order[i] = level; + lpm_pri >>= 4; + } + + /* Check that we have unique values */ + for (i = 0; i < LPM_MAX_NUM_MODES - 1; i++) + for (j = i + 1; j < LPM_MAX_NUM_MODES; j++) + if (mode_order[i] == mode_order[j]) + return false; + + return true; +} + +static void pmc_core_get_low_power_modes(struct platform_device *pdev) +{ + struct pmc_dev *pmcdev = platform_get_drvdata(pdev); + u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI; + u8 mode_order[LPM_MAX_NUM_MODES]; + u32 lpm_pri; + u32 lpm_en; + int mode, i, p; + + /* Use LPM Maps to indicate support for substates */ + if (!pmcdev->map->lpm_num_maps) + return; + + lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset); + pmcdev->num_lpm_modes = hweight32(lpm_en); + + /* Read 32 bit LPM_PRI register */ + lpm_pri = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_priority_offset); + + + /* + * If lpm_pri value passes verification, then override the default + * modes here. Otherwise stick with the default. + */ + if (pmc_core_pri_verify(lpm_pri, mode_order)) + /* Get list of modes in priority order */ + for (mode = 0; mode < LPM_MAX_NUM_MODES; mode++) + pri_order[mode_order[mode]] = mode; + else + dev_warn(&pdev->dev, "Assuming a default substate order for this platform\n"); + + /* + * Loop through all modes from lowest to highest priority, + * and capture all enabled modes in order + */ + i = 0; + for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) { + int mode = pri_order[p]; + + if (!(BIT(mode) & lpm_en)) + continue; + + pmcdev->lpm_en_modes[i++] = mode; + } +} + +static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) +{ + debugfs_remove_recursive(pmcdev->dbgfs_dir); +} + +static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) +{ + struct dentry *dir; + + dir = debugfs_create_dir("pmc_core", NULL); + pmcdev->dbgfs_dir = dir; + + debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev, + &pmc_core_dev_state); + + if (pmcdev->map->pfear_sts) + debugfs_create_file("pch_ip_power_gating_status", 0444, dir, + pmcdev, &pmc_core_ppfear_fops); + + debugfs_create_file("ltr_ignore", 0644, dir, pmcdev, + &pmc_core_ltr_ignore_ops); + + debugfs_create_file("ltr_show", 0444, dir, pmcdev, &pmc_core_ltr_fops); + + debugfs_create_file("package_cstate_show", 0444, dir, pmcdev, + &pmc_core_pkgc_fops); + + if (pmcdev->map->pll_sts) + debugfs_create_file("pll_status", 0444, dir, pmcdev, + &pmc_core_pll_fops); + + if (pmcdev->map->mphy_sts) + debugfs_create_file("mphy_core_lanes_power_gating_status", + 0444, dir, pmcdev, + &pmc_core_mphy_pg_fops); + + if (pmcdev->map->slps0_dbg_maps) { + debugfs_create_file("slp_s0_debug_status", 0444, + dir, pmcdev, + &pmc_core_slps0_dbg_fops); + + debugfs_create_bool("slp_s0_dbg_latch", 0644, + dir, &slps0_dbg_latch); + } + + if (pmcdev->map->lpm_en_offset) { + debugfs_create_file("substate_residencies", 0444, + pmcdev->dbgfs_dir, pmcdev, + &pmc_core_substate_res_fops); + } + + if (pmcdev->map->lpm_status_offset) { + debugfs_create_file("substate_status_registers", 0444, + pmcdev->dbgfs_dir, pmcdev, + &pmc_core_substate_sts_regs_fops); + debugfs_create_file("substate_live_status_registers", 0444, + pmcdev->dbgfs_dir, pmcdev, + &pmc_core_substate_l_sts_regs_fops); + debugfs_create_file("lpm_latch_mode", 0644, + pmcdev->dbgfs_dir, pmcdev, + &pmc_core_lpm_latch_mode_fops); + } + + if (pmcdev->lpm_req_regs) { + debugfs_create_file("substate_requirements", 0444, + pmcdev->dbgfs_dir, pmcdev, + &pmc_core_substate_req_regs_fops); + } +} + +static const struct x86_cpu_id intel_pmc_core_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, &spt_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, &spt_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, &spt_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, &spt_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, &cnp_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, &icl_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_NNPI, &icl_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, &cnp_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, &cnp_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, &tgl_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, &tgl_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, &tgl_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L, &icl_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, &tgl_reg_map), + X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, &tgl_reg_map), + {} +}; + +MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_ids); + +static const struct pci_device_id pmc_pci_ids[] = { + { PCI_VDEVICE(INTEL, SPT_PMC_PCI_DEVICE_ID) }, + { } +}; + +/* + * This quirk can be used on those platforms where + * the platform BIOS enforces 24Mhz crystal to shutdown + * before PMC can assert SLP_S0#. + */ +static bool xtal_ignore; +static int quirk_xtal_ignore(const struct dmi_system_id *id) +{ + xtal_ignore = true; + return 0; +} + +static void pmc_core_xtal_ignore(struct pmc_dev *pmcdev) +{ + u32 value; + + value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_vric1_offset); + /* 24MHz Crystal Shutdown Qualification Disable */ + value |= SPT_PMC_VRIC1_XTALSDQDIS; + /* Low Voltage Mode Enable */ + value &= ~SPT_PMC_VRIC1_SLPS0LVEN; + pmc_core_reg_write(pmcdev, pmcdev->map->pm_vric1_offset, value); +} + +static const struct dmi_system_id pmc_core_dmi_table[] = { + { + .callback = quirk_xtal_ignore, + .ident = "HP Elite x2 1013 G3", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite x2 1013 G3"), + }, + }, + {} +}; + +static void pmc_core_do_dmi_quirks(struct pmc_dev *pmcdev) +{ + dmi_check_system(pmc_core_dmi_table); + + if (xtal_ignore) + pmc_core_xtal_ignore(pmcdev); +} + +static int pmc_core_probe(struct platform_device *pdev) +{ + static bool device_initialized; + struct pmc_dev *pmcdev; + const struct x86_cpu_id *cpu_id; + u64 slp_s0_addr; + + if (device_initialized) + return -ENODEV; + + pmcdev = devm_kzalloc(&pdev->dev, sizeof(*pmcdev), GFP_KERNEL); + if (!pmcdev) + return -ENOMEM; + + platform_set_drvdata(pdev, pmcdev); + + cpu_id = x86_match_cpu(intel_pmc_core_ids); + if (!cpu_id) + return -ENODEV; + + pmcdev->map = (struct pmc_reg_map *)cpu_id->driver_data; + + /* + * Coffee Lake has CPU ID of Kaby Lake and Cannon Lake PCH. So here + * Sunrisepoint PCH regmap can't be used. Use Cannon Lake PCH regmap + * in this case. + */ + if (pmcdev->map == &spt_reg_map && !pci_dev_present(pmc_pci_ids)) + pmcdev->map = &cnp_reg_map; + + if (lpit_read_residency_count_address(&slp_s0_addr)) { + pmcdev->base_addr = PMC_BASE_ADDR_DEFAULT; + + if (page_is_ram(PHYS_PFN(pmcdev->base_addr))) + return -ENODEV; + } else { + pmcdev->base_addr = slp_s0_addr - pmcdev->map->slp_s0_offset; + } + + pmcdev->regbase = ioremap(pmcdev->base_addr, + pmcdev->map->regmap_length); + if (!pmcdev->regbase) + return -ENOMEM; + + mutex_init(&pmcdev->lock); + + pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(pmcdev); + pmc_core_get_low_power_modes(pdev); + pmc_core_do_dmi_quirks(pmcdev); + + if (pmcdev->map == &tgl_reg_map) + pmc_core_get_tgl_lpm_reqs(pdev); + + /* + * On TGL, due to a hardware limitation, the GBE LTR blocks PC10 when + * a cable is attached. Tell the PMC to ignore it. + */ + if (pmcdev->map == &tgl_reg_map) { + dev_dbg(&pdev->dev, "ignoring GBE LTR\n"); + pmc_core_send_ltr_ignore(pmcdev, 3); + } + + pmc_core_dbgfs_register(pmcdev); + + device_initialized = true; + dev_info(&pdev->dev, " initialized\n"); + + return 0; +} + +static int pmc_core_remove(struct platform_device *pdev) +{ + struct pmc_dev *pmcdev = platform_get_drvdata(pdev); + + pmc_core_dbgfs_unregister(pmcdev); + platform_set_drvdata(pdev, NULL); + mutex_destroy(&pmcdev->lock); + iounmap(pmcdev->regbase); + return 0; +} + +static bool warn_on_s0ix_failures; +module_param(warn_on_s0ix_failures, bool, 0644); +MODULE_PARM_DESC(warn_on_s0ix_failures, "Check and warn for S0ix failures"); + +static __maybe_unused int pmc_core_suspend(struct device *dev) +{ + struct pmc_dev *pmcdev = dev_get_drvdata(dev); + + pmcdev->check_counters = false; + + /* No warnings on S0ix failures */ + if (!warn_on_s0ix_failures) + return 0; + + /* Check if the syspend will actually use S0ix */ + if (pm_suspend_via_firmware()) + return 0; + + /* Save PC10 residency for checking later */ + if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pmcdev->pc10_counter)) + return -EIO; + + /* Save S0ix residency for checking later */ + if (pmc_core_dev_state_get(pmcdev, &pmcdev->s0ix_counter)) + return -EIO; + + pmcdev->check_counters = true; + return 0; +} + +static inline bool pmc_core_is_pc10_failed(struct pmc_dev *pmcdev) +{ + u64 pc10_counter; + + if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pc10_counter)) + return false; + + if (pc10_counter == pmcdev->pc10_counter) + return true; + + return false; +} + +static inline bool pmc_core_is_s0ix_failed(struct pmc_dev *pmcdev) +{ + u64 s0ix_counter; + + if (pmc_core_dev_state_get(pmcdev, &s0ix_counter)) + return false; + + if (s0ix_counter == pmcdev->s0ix_counter) + return true; + + return false; +} + +static __maybe_unused int pmc_core_resume(struct device *dev) +{ + struct pmc_dev *pmcdev = dev_get_drvdata(dev); + const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; + int offset = pmcdev->map->lpm_status_offset; + + if (!pmcdev->check_counters) + return 0; + + if (!pmc_core_is_s0ix_failed(pmcdev)) + return 0; + + if (pmc_core_is_pc10_failed(pmcdev)) { + /* S0ix failed because of PC10 entry failure */ + dev_info(dev, "CPU did not enter PC10!!! (PC10 cnt=0x%llx)\n", + pmcdev->pc10_counter); + return 0; + } + + /* The real interesting case - S0ix failed - lets ask PMC why. */ + dev_warn(dev, "CPU did not enter SLP_S0!!! (S0ix cnt=%llu)\n", + pmcdev->s0ix_counter); + if (pmcdev->map->slps0_dbg_maps) + pmc_core_slps0_display(pmcdev, dev, NULL); + if (pmcdev->map->lpm_sts) + pmc_core_lpm_display(pmcdev, dev, NULL, offset, "STATUS", maps); + + return 0; +} + +static const struct dev_pm_ops pmc_core_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(pmc_core_suspend, pmc_core_resume) +}; + +static const struct acpi_device_id pmc_core_acpi_ids[] = { + {"INT33A1", 0}, /* _HID for Intel Power Engine, _CID PNP0D80*/ + { } +}; +MODULE_DEVICE_TABLE(acpi, pmc_core_acpi_ids); + +static struct platform_driver pmc_core_driver = { + .driver = { + .name = "intel_pmc_core", + .acpi_match_table = ACPI_PTR(pmc_core_acpi_ids), + .pm = &pmc_core_pm_ops, + .dev_groups = pmc_dev_groups, + }, + .probe = pmc_core_probe, + .remove = pmc_core_remove, +}; + +module_platform_driver(pmc_core_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel PMC Core Driver"); diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h new file mode 100644 index 000000000000..b9bf3d3d6f7a --- /dev/null +++ b/drivers/platform/x86/intel/pmc/core.h @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Core SoC Power Management Controller Header File + * + * Copyright (c) 2016, Intel Corporation. + * All Rights Reserved. + * + * Authors: Rajneesh Bhardwaj + * Vishwanath Somayaji + */ + +#ifndef PMC_CORE_H +#define PMC_CORE_H + +#include + +#define PMC_BASE_ADDR_DEFAULT 0xFE000000 + +/* Sunrise Point Power Management Controller PCI Device ID */ +#define SPT_PMC_PCI_DEVICE_ID 0x9d21 +#define SPT_PMC_BASE_ADDR_OFFSET 0x48 +#define SPT_PMC_SLP_S0_RES_COUNTER_OFFSET 0x13c +#define SPT_PMC_PM_CFG_OFFSET 0x18 +#define SPT_PMC_PM_STS_OFFSET 0x1c +#define SPT_PMC_MTPMC_OFFSET 0x20 +#define SPT_PMC_MFPMC_OFFSET 0x38 +#define SPT_PMC_LTR_IGNORE_OFFSET 0x30C +#define SPT_PMC_VRIC1_OFFSET 0x31c +#define SPT_PMC_MPHY_CORE_STS_0 0x1143 +#define SPT_PMC_MPHY_CORE_STS_1 0x1142 +#define SPT_PMC_MPHY_COM_STS_0 0x1155 +#define SPT_PMC_MMIO_REG_LEN 0x1000 +#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 SPT_PPFEAR_NUM_ENTRIES 5 +#define SPT_PMC_READ_DISABLE_BIT 0x16 +#define SPT_PMC_MSG_FULL_STS_BIT 0x18 +#define NUM_RETRIES 100 +#define SPT_NUM_IP_IGN_ALLOWED 17 + +#define SPT_PMC_LTR_CUR_PLT 0x350 +#define SPT_PMC_LTR_CUR_ASLT 0x354 +#define SPT_PMC_LTR_SPA 0x360 +#define SPT_PMC_LTR_SPB 0x364 +#define SPT_PMC_LTR_SATA 0x368 +#define SPT_PMC_LTR_GBE 0x36C +#define SPT_PMC_LTR_XHCI 0x370 +#define SPT_PMC_LTR_RESERVED 0x374 +#define SPT_PMC_LTR_ME 0x378 +#define SPT_PMC_LTR_EVA 0x37C +#define SPT_PMC_LTR_SPC 0x380 +#define SPT_PMC_LTR_AZ 0x384 +#define SPT_PMC_LTR_LPSS 0x38C +#define SPT_PMC_LTR_CAM 0x390 +#define SPT_PMC_LTR_SPD 0x394 +#define SPT_PMC_LTR_SPE 0x398 +#define SPT_PMC_LTR_ESPI 0x39C +#define SPT_PMC_LTR_SCC 0x3A0 +#define SPT_PMC_LTR_ISH 0x3A4 + +/* Sunrise Point: PGD PFET Enable Ack Status Registers */ +enum ppfear_regs { + SPT_PMC_XRAM_PPFEAR0A = 0x590, + SPT_PMC_XRAM_PPFEAR0B, + SPT_PMC_XRAM_PPFEAR0C, + SPT_PMC_XRAM_PPFEAR0D, + SPT_PMC_XRAM_PPFEAR1A, +}; + +#define SPT_PMC_BIT_PMC BIT(0) +#define SPT_PMC_BIT_OPI BIT(1) +#define SPT_PMC_BIT_SPI BIT(2) +#define SPT_PMC_BIT_XHCI BIT(3) +#define SPT_PMC_BIT_SPA BIT(4) +#define SPT_PMC_BIT_SPB BIT(5) +#define SPT_PMC_BIT_SPC BIT(6) +#define SPT_PMC_BIT_GBE BIT(7) + +#define SPT_PMC_BIT_SATA BIT(0) +#define SPT_PMC_BIT_HDA_PGD0 BIT(1) +#define SPT_PMC_BIT_HDA_PGD1 BIT(2) +#define SPT_PMC_BIT_HDA_PGD2 BIT(3) +#define SPT_PMC_BIT_HDA_PGD3 BIT(4) +#define SPT_PMC_BIT_RSVD_0B BIT(5) +#define SPT_PMC_BIT_LPSS BIT(6) +#define SPT_PMC_BIT_LPC BIT(7) + +#define SPT_PMC_BIT_SMB BIT(0) +#define SPT_PMC_BIT_ISH BIT(1) +#define SPT_PMC_BIT_P2SB BIT(2) +#define SPT_PMC_BIT_DFX BIT(3) +#define SPT_PMC_BIT_SCC BIT(4) +#define SPT_PMC_BIT_RSVD_0C BIT(5) +#define SPT_PMC_BIT_FUSE BIT(6) +#define SPT_PMC_BIT_CAMREA BIT(7) + +#define SPT_PMC_BIT_RSVD_0D BIT(0) +#define SPT_PMC_BIT_USB3_OTG BIT(1) +#define SPT_PMC_BIT_EXI BIT(2) +#define SPT_PMC_BIT_CSE BIT(3) +#define SPT_PMC_BIT_CSME_KVM BIT(4) +#define SPT_PMC_BIT_CSME_PMT BIT(5) +#define SPT_PMC_BIT_CSME_CLINK BIT(6) +#define SPT_PMC_BIT_CSME_PTIO BIT(7) + +#define SPT_PMC_BIT_CSME_USBR BIT(0) +#define SPT_PMC_BIT_CSME_SUSRAM BIT(1) +#define SPT_PMC_BIT_CSME_SMT BIT(2) +#define SPT_PMC_BIT_RSVD_1A BIT(3) +#define SPT_PMC_BIT_CSME_SMS2 BIT(4) +#define SPT_PMC_BIT_CSME_SMS1 BIT(5) +#define SPT_PMC_BIT_CSME_RTC BIT(6) +#define SPT_PMC_BIT_CSME_PSF BIT(7) + +#define SPT_PMC_BIT_MPHY_LANE0 BIT(0) +#define SPT_PMC_BIT_MPHY_LANE1 BIT(1) +#define SPT_PMC_BIT_MPHY_LANE2 BIT(2) +#define SPT_PMC_BIT_MPHY_LANE3 BIT(3) +#define SPT_PMC_BIT_MPHY_LANE4 BIT(4) +#define SPT_PMC_BIT_MPHY_LANE5 BIT(5) +#define SPT_PMC_BIT_MPHY_LANE6 BIT(6) +#define SPT_PMC_BIT_MPHY_LANE7 BIT(7) + +#define SPT_PMC_BIT_MPHY_LANE8 BIT(0) +#define SPT_PMC_BIT_MPHY_LANE9 BIT(1) +#define SPT_PMC_BIT_MPHY_LANE10 BIT(2) +#define SPT_PMC_BIT_MPHY_LANE11 BIT(3) +#define SPT_PMC_BIT_MPHY_LANE12 BIT(4) +#define SPT_PMC_BIT_MPHY_LANE13 BIT(5) +#define SPT_PMC_BIT_MPHY_LANE14 BIT(6) +#define SPT_PMC_BIT_MPHY_LANE15 BIT(7) + +#define SPT_PMC_BIT_MPHY_CMN_LANE0 BIT(0) +#define SPT_PMC_BIT_MPHY_CMN_LANE1 BIT(1) +#define SPT_PMC_BIT_MPHY_CMN_LANE2 BIT(2) +#define SPT_PMC_BIT_MPHY_CMN_LANE3 BIT(3) + +#define SPT_PMC_VRIC1_SLPS0LVEN BIT(13) +#define SPT_PMC_VRIC1_XTALSDQDIS BIT(22) + +/* Cannonlake Power Management Controller register offsets */ +#define CNP_PMC_SLPS0_DBG_OFFSET 0x10B4 +#define CNP_PMC_PM_CFG_OFFSET 0x1818 +#define CNP_PMC_SLP_S0_RES_COUNTER_OFFSET 0x193C +#define CNP_PMC_LTR_IGNORE_OFFSET 0x1B0C +/* Cannonlake: PGD PFET Enable Ack Status Register(s) start */ +#define CNP_PMC_HOST_PPFEAR0A 0x1D90 + +#define CNP_PMC_LATCH_SLPS0_EVENTS BIT(31) + +#define CNP_PMC_MMIO_REG_LEN 0x2000 +#define CNP_PPFEAR_NUM_ENTRIES 8 +#define CNP_PMC_READ_DISABLE_BIT 22 +#define CNP_NUM_IP_IGN_ALLOWED 19 +#define CNP_PMC_LTR_CUR_PLT 0x1B50 +#define CNP_PMC_LTR_CUR_ASLT 0x1B54 +#define CNP_PMC_LTR_SPA 0x1B60 +#define CNP_PMC_LTR_SPB 0x1B64 +#define CNP_PMC_LTR_SATA 0x1B68 +#define CNP_PMC_LTR_GBE 0x1B6C +#define CNP_PMC_LTR_XHCI 0x1B70 +#define CNP_PMC_LTR_RESERVED 0x1B74 +#define CNP_PMC_LTR_ME 0x1B78 +#define CNP_PMC_LTR_EVA 0x1B7C +#define CNP_PMC_LTR_SPC 0x1B80 +#define CNP_PMC_LTR_AZ 0x1B84 +#define CNP_PMC_LTR_LPSS 0x1B8C +#define CNP_PMC_LTR_CAM 0x1B90 +#define CNP_PMC_LTR_SPD 0x1B94 +#define CNP_PMC_LTR_SPE 0x1B98 +#define CNP_PMC_LTR_ESPI 0x1B9C +#define CNP_PMC_LTR_SCC 0x1BA0 +#define CNP_PMC_LTR_ISH 0x1BA4 +#define CNP_PMC_LTR_CNV 0x1BF0 +#define CNP_PMC_LTR_EMMC 0x1BF4 +#define CNP_PMC_LTR_UFSX2 0x1BF8 + +#define LTR_DECODED_VAL GENMASK(9, 0) +#define LTR_DECODED_SCALE GENMASK(12, 10) +#define LTR_REQ_SNOOP BIT(15) +#define LTR_REQ_NONSNOOP BIT(31) + +#define ICL_PPFEAR_NUM_ENTRIES 9 +#define ICL_NUM_IP_IGN_ALLOWED 20 +#define ICL_PMC_LTR_WIGIG 0x1BFC +#define ICL_PMC_SLP_S0_RES_COUNTER_STEP 0x64 + +#define LPM_MAX_NUM_MODES 8 +#define LPM_DEFAULT_PRI { 7, 6, 2, 5, 4, 1, 3, 0 } + +#define GET_X2_COUNTER(v) ((v) >> 1) +#define LPM_STS_LATCH_MODE BIT(31) + +#define TGL_PMC_SLP_S0_RES_COUNTER_STEP 0x7A +#define TGL_PMC_LTR_THC0 0x1C04 +#define TGL_PMC_LTR_THC1 0x1C08 +#define TGL_NUM_IP_IGN_ALLOWED 23 +#define TGL_PMC_LPM_RES_COUNTER_STEP_X2 61 /* 30.5us * 2 */ + +/* + * Tigerlake Power Management Controller register offsets + */ +#define TGL_LPM_STS_LATCH_EN_OFFSET 0x1C34 +#define TGL_LPM_EN_OFFSET 0x1C78 +#define TGL_LPM_RESIDENCY_OFFSET 0x1C80 + +/* Tigerlake Low Power Mode debug registers */ +#define TGL_LPM_STATUS_OFFSET 0x1C3C +#define TGL_LPM_LIVE_STATUS_OFFSET 0x1C5C +#define TGL_LPM_PRI_OFFSET 0x1C7C +#define TGL_LPM_NUM_MAPS 6 + +/* Extended Test Mode Register 3 (CNL and later) */ +#define ETR3_OFFSET 0x1048 +#define ETR3_CF9GR BIT(20) +#define ETR3_CF9LOCK BIT(31) + +/* Extended Test Mode Register LPM bits (TGL and later */ +#define ETR3_CLEAR_LPM_EVENTS BIT(28) + +const char *pmc_lpm_modes[] = { + "S0i2.0", + "S0i2.1", + "S0i2.2", + "S0i3.0", + "S0i3.1", + "S0i3.2", + "S0i3.3", + "S0i3.4", + NULL +}; + +struct pmc_bit_map { + const char *name; + u32 bit_mask; +}; + +/** + * struct pmc_reg_map - Structure used to define parameter unique to a + PCH family + * @pfear_sts: Maps name of IP block to PPFEAR* bit + * @mphy_sts: Maps name of MPHY lane to MPHY status lane status bit + * @pll_sts: Maps name of PLL to corresponding bit status + * @slps0_dbg_maps: Array of SLP_S0_DBG* registers containing debug info + * @ltr_show_sts: Maps PCH IP Names to their MMIO register offsets + * @slp_s0_offset: PWRMBASE offset to read SLP_S0 residency + * @ltr_ignore_offset: PWRMBASE offset to read/write LTR ignore bit + * @regmap_length: Length of memory to map from PWRMBASE address to access + * @ppfear0_offset: PWRMBASE offset to to read PPFEAR* + * @ppfear_buckets: Number of 8 bits blocks to read all IP blocks from + * PPFEAR + * @pm_cfg_offset: PWRMBASE offset to PM_CFG register + * @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE + * @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG* + * + * Each PCH has unique set of register offsets and bit indexes. This structure + * captures them to have a common implementation. + */ +struct pmc_reg_map { + const struct pmc_bit_map **pfear_sts; + const struct pmc_bit_map *mphy_sts; + const struct pmc_bit_map *pll_sts; + const struct pmc_bit_map **slps0_dbg_maps; + const struct pmc_bit_map *ltr_show_sts; + const struct pmc_bit_map *msr_sts; + const struct pmc_bit_map **lpm_sts; + const u32 slp_s0_offset; + const int slp_s0_res_counter_step; + const u32 ltr_ignore_offset; + const int regmap_length; + const u32 ppfear0_offset; + const int ppfear_buckets; + const u32 pm_cfg_offset; + const int pm_read_disable_bit; + const u32 slps0_dbg_offset; + const u32 ltr_ignore_max; + const u32 pm_vric1_offset; + /* Low Power Mode registers */ + const int lpm_num_maps; + const int lpm_res_counter_step_x2; + const u32 lpm_sts_latch_en_offset; + const u32 lpm_en_offset; + const u32 lpm_priority_offset; + const u32 lpm_residency_offset; + const u32 lpm_status_offset; + const u32 lpm_live_status_offset; + const u32 etr3_offset; +}; + +/** + * struct pmc_dev - pmc device structure + * @base_addr: contains pmc base address + * @regbase: pointer to io-remapped memory location + * @map: pointer to pmc_reg_map struct that contains platform + * specific attributes + * @dbgfs_dir: path to debugfs interface + * @pmc_xram_read_bit: flag to indicate whether PMC XRAM shadow registers + * used to read MPHY PG and PLL status are available + * @mutex_lock: mutex to complete one transcation + * @check_counters: On resume, check if counters are getting incremented + * @pc10_counter: PC10 residency counter + * @s0ix_counter: S0ix residency (step adjusted) + * @num_lpm_modes: Count of enabled modes + * @lpm_en_modes: Array of enabled modes from lowest to highest priority + * @lpm_req_regs: List of substate requirements + * + * pmc_dev contains info about power management controller device. + */ +struct pmc_dev { + u32 base_addr; + void __iomem *regbase; + const struct pmc_reg_map *map; + struct dentry *dbgfs_dir; + int pmc_xram_read_bit; + struct mutex lock; /* generic mutex lock for PMC Core */ + + bool check_counters; /* Check for counter increments on resume */ + u64 pc10_counter; + u64 s0ix_counter; + int num_lpm_modes; + int lpm_en_modes[LPM_MAX_NUM_MODES]; + u32 *lpm_req_regs; +}; + +#define pmc_for_each_mode(i, mode, pmcdev) \ + for (i = 0, mode = pmcdev->lpm_en_modes[i]; \ + i < pmcdev->num_lpm_modes; \ + i++, mode = pmcdev->lpm_en_modes[i]) + +#define DEFINE_PMC_CORE_ATTR_WRITE(__name) \ +static int __name ## _open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, __name ## _show, inode->i_private); \ +} \ + \ +static const struct file_operations __name ## _fops = { \ + .owner = THIS_MODULE, \ + .open = __name ## _open, \ + .read = seq_read, \ + .write = __name ## _write, \ + .release = single_release, \ +} + +#endif /* PMC_CORE_H */ diff --git a/drivers/platform/x86/intel/pmc/pltdrv.c b/drivers/platform/x86/intel/pmc/pltdrv.c new file mode 100644 index 000000000000..73797680b895 --- /dev/null +++ b/drivers/platform/x86/intel/pmc/pltdrv.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Intel PMC Core platform init + * Copyright (c) 2019, Google Inc. + * Author - Rajat Jain + * + * This code instantiates platform devices for intel_pmc_core driver, only + * on supported platforms that may not have the ACPI devices in the ACPI tables. + * No new platforms should be added here, because we expect that new platforms + * should all have the ACPI device, which is the preferred way of enumeration. + */ + +#include +#include +#include + +#include +#include + +static void intel_pmc_core_release(struct device *dev) +{ + kfree(dev); +} + +static struct platform_device *pmc_core_device; + +/* + * intel_pmc_core_platform_ids is the list of platforms where we want to + * instantiate the platform_device if not already instantiated. This is + * different than intel_pmc_core_ids in intel_pmc_core.c which is the + * list of platforms that the driver supports for pmc_core device. The + * other list may grow, but this list should not. + */ +static const struct x86_cpu_id intel_pmc_core_platform_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, &pmc_core_device), + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, &pmc_core_device), + X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, &pmc_core_device), + X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, &pmc_core_device), + X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, &pmc_core_device), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, &pmc_core_device), + X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, &pmc_core_device), + X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, &pmc_core_device), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_platform_ids); + +static int __init pmc_core_platform_init(void) +{ + int retval; + + /* Skip creating the platform device if ACPI already has a device */ + if (acpi_dev_present("INT33A1", NULL, -1)) + return -ENODEV; + + if (!x86_match_cpu(intel_pmc_core_platform_ids)) + return -ENODEV; + + pmc_core_device = kzalloc(sizeof(*pmc_core_device), GFP_KERNEL); + if (!pmc_core_device) + return -ENOMEM; + + pmc_core_device->name = "intel_pmc_core"; + pmc_core_device->dev.release = intel_pmc_core_release; + + retval = platform_device_register(pmc_core_device); + if (retval) + kfree(pmc_core_device); + + return retval; +} + +static void __exit pmc_core_platform_exit(void) +{ + platform_device_unregister(pmc_core_device); +} + +module_init(pmc_core_platform_init); +module_exit(pmc_core_platform_exit); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c deleted file mode 100644 index ae410a358ffe..000000000000 --- a/drivers/platform/x86/intel_pmc_core.c +++ /dev/null @@ -1,1859 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Core SoC Power Management Controller Driver - * - * Copyright (c) 2016, Intel Corporation. - * All Rights Reserved. - * - * Authors: Rajneesh Bhardwaj - * Vishwanath Somayaji - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "intel_pmc_core.h" - -#define ACPI_S0IX_DSM_UUID "57a6512e-3979-4e9d-9708-ff13b2508972" -#define ACPI_GET_LOW_MODE_REGISTERS 1 - -/* PKGC MSRs are common across Intel Core SoCs */ -static const struct pmc_bit_map msr_map[] = { - {"Package C2", MSR_PKG_C2_RESIDENCY}, - {"Package C3", MSR_PKG_C3_RESIDENCY}, - {"Package C6", MSR_PKG_C6_RESIDENCY}, - {"Package C7", MSR_PKG_C7_RESIDENCY}, - {"Package C8", MSR_PKG_C8_RESIDENCY}, - {"Package C9", MSR_PKG_C9_RESIDENCY}, - {"Package C10", MSR_PKG_C10_RESIDENCY}, - {} -}; - -static const struct pmc_bit_map spt_pll_map[] = { - {"MIPI PLL", SPT_PMC_BIT_MPHY_CMN_LANE0}, - {"GEN2 USB2PCIE2 PLL", SPT_PMC_BIT_MPHY_CMN_LANE1}, - {"DMIPCIE3 PLL", SPT_PMC_BIT_MPHY_CMN_LANE2}, - {"SATA PLL", SPT_PMC_BIT_MPHY_CMN_LANE3}, - {} -}; - -static const struct pmc_bit_map spt_mphy_map[] = { - {"MPHY CORE LANE 0", SPT_PMC_BIT_MPHY_LANE0}, - {"MPHY CORE LANE 1", SPT_PMC_BIT_MPHY_LANE1}, - {"MPHY CORE LANE 2", SPT_PMC_BIT_MPHY_LANE2}, - {"MPHY CORE LANE 3", SPT_PMC_BIT_MPHY_LANE3}, - {"MPHY CORE LANE 4", SPT_PMC_BIT_MPHY_LANE4}, - {"MPHY CORE LANE 5", SPT_PMC_BIT_MPHY_LANE5}, - {"MPHY CORE LANE 6", SPT_PMC_BIT_MPHY_LANE6}, - {"MPHY CORE LANE 7", SPT_PMC_BIT_MPHY_LANE7}, - {"MPHY CORE LANE 8", SPT_PMC_BIT_MPHY_LANE8}, - {"MPHY CORE LANE 9", SPT_PMC_BIT_MPHY_LANE9}, - {"MPHY CORE LANE 10", SPT_PMC_BIT_MPHY_LANE10}, - {"MPHY CORE LANE 11", SPT_PMC_BIT_MPHY_LANE11}, - {"MPHY CORE LANE 12", SPT_PMC_BIT_MPHY_LANE12}, - {"MPHY CORE LANE 13", SPT_PMC_BIT_MPHY_LANE13}, - {"MPHY CORE LANE 14", SPT_PMC_BIT_MPHY_LANE14}, - {"MPHY CORE LANE 15", SPT_PMC_BIT_MPHY_LANE15}, - {} -}; - -static const struct pmc_bit_map spt_pfear_map[] = { - {"PMC", SPT_PMC_BIT_PMC}, - {"OPI-DMI", SPT_PMC_BIT_OPI}, - {"SPI / eSPI", SPT_PMC_BIT_SPI}, - {"XHCI", SPT_PMC_BIT_XHCI}, - {"SPA", SPT_PMC_BIT_SPA}, - {"SPB", SPT_PMC_BIT_SPB}, - {"SPC", SPT_PMC_BIT_SPC}, - {"GBE", SPT_PMC_BIT_GBE}, - {"SATA", SPT_PMC_BIT_SATA}, - {"HDA-PGD0", SPT_PMC_BIT_HDA_PGD0}, - {"HDA-PGD1", SPT_PMC_BIT_HDA_PGD1}, - {"HDA-PGD2", SPT_PMC_BIT_HDA_PGD2}, - {"HDA-PGD3", SPT_PMC_BIT_HDA_PGD3}, - {"RSVD", SPT_PMC_BIT_RSVD_0B}, - {"LPSS", SPT_PMC_BIT_LPSS}, - {"LPC", SPT_PMC_BIT_LPC}, - {"SMB", SPT_PMC_BIT_SMB}, - {"ISH", SPT_PMC_BIT_ISH}, - {"P2SB", SPT_PMC_BIT_P2SB}, - {"DFX", SPT_PMC_BIT_DFX}, - {"SCC", SPT_PMC_BIT_SCC}, - {"RSVD", SPT_PMC_BIT_RSVD_0C}, - {"FUSE", SPT_PMC_BIT_FUSE}, - {"CAMERA", SPT_PMC_BIT_CAMREA}, - {"RSVD", SPT_PMC_BIT_RSVD_0D}, - {"USB3-OTG", SPT_PMC_BIT_USB3_OTG}, - {"EXI", SPT_PMC_BIT_EXI}, - {"CSE", SPT_PMC_BIT_CSE}, - {"CSME_KVM", SPT_PMC_BIT_CSME_KVM}, - {"CSME_PMT", SPT_PMC_BIT_CSME_PMT}, - {"CSME_CLINK", SPT_PMC_BIT_CSME_CLINK}, - {"CSME_PTIO", SPT_PMC_BIT_CSME_PTIO}, - {"CSME_USBR", SPT_PMC_BIT_CSME_USBR}, - {"CSME_SUSRAM", SPT_PMC_BIT_CSME_SUSRAM}, - {"CSME_SMT", SPT_PMC_BIT_CSME_SMT}, - {"RSVD", SPT_PMC_BIT_RSVD_1A}, - {"CSME_SMS2", SPT_PMC_BIT_CSME_SMS2}, - {"CSME_SMS1", SPT_PMC_BIT_CSME_SMS1}, - {"CSME_RTC", SPT_PMC_BIT_CSME_RTC}, - {"CSME_PSF", SPT_PMC_BIT_CSME_PSF}, - {} -}; - -static const struct pmc_bit_map *ext_spt_pfear_map[] = { - /* - * Check intel_pmc_core_ids[] users of spt_reg_map for - * a list of core SoCs using this. - */ - spt_pfear_map, - NULL -}; - -static const struct pmc_bit_map spt_ltr_show_map[] = { - {"SOUTHPORT_A", SPT_PMC_LTR_SPA}, - {"SOUTHPORT_B", SPT_PMC_LTR_SPB}, - {"SATA", SPT_PMC_LTR_SATA}, - {"GIGABIT_ETHERNET", SPT_PMC_LTR_GBE}, - {"XHCI", SPT_PMC_LTR_XHCI}, - {"Reserved", SPT_PMC_LTR_RESERVED}, - {"ME", SPT_PMC_LTR_ME}, - /* EVA is Enterprise Value Add, doesn't really exist on PCH */ - {"EVA", SPT_PMC_LTR_EVA}, - {"SOUTHPORT_C", SPT_PMC_LTR_SPC}, - {"HD_AUDIO", SPT_PMC_LTR_AZ}, - {"LPSS", SPT_PMC_LTR_LPSS}, - {"SOUTHPORT_D", SPT_PMC_LTR_SPD}, - {"SOUTHPORT_E", SPT_PMC_LTR_SPE}, - {"CAMERA", SPT_PMC_LTR_CAM}, - {"ESPI", SPT_PMC_LTR_ESPI}, - {"SCC", SPT_PMC_LTR_SCC}, - {"ISH", SPT_PMC_LTR_ISH}, - /* Below two cannot be used for LTR_IGNORE */ - {"CURRENT_PLATFORM", SPT_PMC_LTR_CUR_PLT}, - {"AGGREGATED_SYSTEM", SPT_PMC_LTR_CUR_ASLT}, - {} -}; - -static const struct pmc_reg_map spt_reg_map = { - .pfear_sts = ext_spt_pfear_map, - .mphy_sts = spt_mphy_map, - .pll_sts = spt_pll_map, - .ltr_show_sts = spt_ltr_show_map, - .msr_sts = msr_map, - .slp_s0_offset = SPT_PMC_SLP_S0_RES_COUNTER_OFFSET, - .slp_s0_res_counter_step = SPT_PMC_SLP_S0_RES_COUNTER_STEP, - .ltr_ignore_offset = SPT_PMC_LTR_IGNORE_OFFSET, - .regmap_length = SPT_PMC_MMIO_REG_LEN, - .ppfear0_offset = SPT_PMC_XRAM_PPFEAR0A, - .ppfear_buckets = SPT_PPFEAR_NUM_ENTRIES, - .pm_cfg_offset = SPT_PMC_PM_CFG_OFFSET, - .pm_read_disable_bit = SPT_PMC_READ_DISABLE_BIT, - .ltr_ignore_max = SPT_NUM_IP_IGN_ALLOWED, - .pm_vric1_offset = SPT_PMC_VRIC1_OFFSET, -}; - -/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */ -static const struct pmc_bit_map cnp_pfear_map[] = { - {"PMC", BIT(0)}, - {"OPI-DMI", BIT(1)}, - {"SPI/eSPI", BIT(2)}, - {"XHCI", BIT(3)}, - {"SPA", BIT(4)}, - {"SPB", BIT(5)}, - {"SPC", BIT(6)}, - {"GBE", BIT(7)}, - - {"SATA", BIT(0)}, - {"HDA_PGD0", BIT(1)}, - {"HDA_PGD1", BIT(2)}, - {"HDA_PGD2", BIT(3)}, - {"HDA_PGD3", BIT(4)}, - {"SPD", BIT(5)}, - {"LPSS", BIT(6)}, - {"LPC", BIT(7)}, - - {"SMB", BIT(0)}, - {"ISH", BIT(1)}, - {"P2SB", BIT(2)}, - {"NPK_VNN", BIT(3)}, - {"SDX", BIT(4)}, - {"SPE", BIT(5)}, - {"Fuse", BIT(6)}, - {"SBR8", BIT(7)}, - - {"CSME_FSC", BIT(0)}, - {"USB3_OTG", BIT(1)}, - {"EXI", BIT(2)}, - {"CSE", BIT(3)}, - {"CSME_KVM", BIT(4)}, - {"CSME_PMT", BIT(5)}, - {"CSME_CLINK", BIT(6)}, - {"CSME_PTIO", BIT(7)}, - - {"CSME_USBR", BIT(0)}, - {"CSME_SUSRAM", BIT(1)}, - {"CSME_SMT1", BIT(2)}, - {"CSME_SMT4", BIT(3)}, - {"CSME_SMS2", BIT(4)}, - {"CSME_SMS1", BIT(5)}, - {"CSME_RTC", BIT(6)}, - {"CSME_PSF", BIT(7)}, - - {"SBR0", BIT(0)}, - {"SBR1", BIT(1)}, - {"SBR2", BIT(2)}, - {"SBR3", BIT(3)}, - {"SBR4", BIT(4)}, - {"SBR5", BIT(5)}, - {"CSME_PECI", BIT(6)}, - {"PSF1", BIT(7)}, - - {"PSF2", BIT(0)}, - {"PSF3", BIT(1)}, - {"PSF4", BIT(2)}, - {"CNVI", BIT(3)}, - {"UFS0", BIT(4)}, - {"EMMC", BIT(5)}, - {"SPF", BIT(6)}, - {"SBR6", BIT(7)}, - - {"SBR7", BIT(0)}, - {"NPK_AON", BIT(1)}, - {"HDA_PGD4", BIT(2)}, - {"HDA_PGD5", BIT(3)}, - {"HDA_PGD6", BIT(4)}, - {"PSF6", BIT(5)}, - {"PSF7", BIT(6)}, - {"PSF8", BIT(7)}, - {} -}; - -static const struct pmc_bit_map *ext_cnp_pfear_map[] = { - /* - * Check intel_pmc_core_ids[] users of cnp_reg_map for - * a list of core SoCs using this. - */ - cnp_pfear_map, - NULL -}; - -static const struct pmc_bit_map icl_pfear_map[] = { - {"RES_65", BIT(0)}, - {"RES_66", BIT(1)}, - {"RES_67", BIT(2)}, - {"TAM", BIT(3)}, - {"GBETSN", BIT(4)}, - {"TBTLSX", BIT(5)}, - {"RES_71", BIT(6)}, - {"RES_72", BIT(7)}, - {} -}; - -static const struct pmc_bit_map *ext_icl_pfear_map[] = { - /* - * Check intel_pmc_core_ids[] users of icl_reg_map for - * a list of core SoCs using this. - */ - cnp_pfear_map, - icl_pfear_map, - NULL -}; - -static const struct pmc_bit_map tgl_pfear_map[] = { - {"PSF9", BIT(0)}, - {"RES_66", BIT(1)}, - {"RES_67", BIT(2)}, - {"RES_68", BIT(3)}, - {"RES_69", BIT(4)}, - {"RES_70", BIT(5)}, - {"TBTLSX", BIT(6)}, - {} -}; - -static const struct pmc_bit_map *ext_tgl_pfear_map[] = { - /* - * Check intel_pmc_core_ids[] users of tgl_reg_map for - * a list of core SoCs using this. - */ - cnp_pfear_map, - tgl_pfear_map, - NULL -}; - -static const struct pmc_bit_map cnp_slps0_dbg0_map[] = { - {"AUDIO_D3", BIT(0)}, - {"OTG_D3", BIT(1)}, - {"XHCI_D3", BIT(2)}, - {"LPIO_D3", BIT(3)}, - {"SDX_D3", BIT(4)}, - {"SATA_D3", BIT(5)}, - {"UFS0_D3", BIT(6)}, - {"UFS1_D3", BIT(7)}, - {"EMMC_D3", BIT(8)}, - {} -}; - -static const struct pmc_bit_map cnp_slps0_dbg1_map[] = { - {"SDIO_PLL_OFF", BIT(0)}, - {"USB2_PLL_OFF", BIT(1)}, - {"AUDIO_PLL_OFF", BIT(2)}, - {"OC_PLL_OFF", BIT(3)}, - {"MAIN_PLL_OFF", BIT(4)}, - {"XOSC_OFF", BIT(5)}, - {"LPC_CLKS_GATED", BIT(6)}, - {"PCIE_CLKREQS_IDLE", BIT(7)}, - {"AUDIO_ROSC_OFF", BIT(8)}, - {"HPET_XOSC_CLK_REQ", BIT(9)}, - {"PMC_ROSC_SLOW_CLK", BIT(10)}, - {"AON2_ROSC_GATED", BIT(11)}, - {"CLKACKS_DEASSERTED", BIT(12)}, - {} -}; - -static const struct pmc_bit_map cnp_slps0_dbg2_map[] = { - {"MPHY_CORE_GATED", BIT(0)}, - {"CSME_GATED", BIT(1)}, - {"USB2_SUS_GATED", BIT(2)}, - {"DYN_FLEX_IO_IDLE", BIT(3)}, - {"GBE_NO_LINK", BIT(4)}, - {"THERM_SEN_DISABLED", BIT(5)}, - {"PCIE_LOW_POWER", BIT(6)}, - {"ISH_VNNAON_REQ_ACT", BIT(7)}, - {"ISH_VNN_REQ_ACT", BIT(8)}, - {"CNV_VNNAON_REQ_ACT", BIT(9)}, - {"CNV_VNN_REQ_ACT", BIT(10)}, - {"NPK_VNNON_REQ_ACT", BIT(11)}, - {"PMSYNC_STATE_IDLE", BIT(12)}, - {"ALST_GT_THRES", BIT(13)}, - {"PMC_ARC_PG_READY", BIT(14)}, - {} -}; - -static const struct pmc_bit_map *cnp_slps0_dbg_maps[] = { - cnp_slps0_dbg0_map, - cnp_slps0_dbg1_map, - cnp_slps0_dbg2_map, - NULL -}; - -static const struct pmc_bit_map cnp_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}, - {"Reserved", CNP_PMC_LTR_RESERVED}, - {"ME", CNP_PMC_LTR_ME}, - /* EVA is Enterprise Value Add, doesn't really exist on PCH */ - {"EVA", 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}, - {"CAMERA", CNP_PMC_LTR_CAM}, - {"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}, - /* - * Check intel_pmc_core_ids[] users of cnp_reg_map for - * a list of core SoCs using this. - */ - {"WIGIG", ICL_PMC_LTR_WIGIG}, - {"THC0", TGL_PMC_LTR_THC0}, - {"THC1", TGL_PMC_LTR_THC1}, - /* Below two cannot be used for LTR_IGNORE */ - {"CURRENT_PLATFORM", CNP_PMC_LTR_CUR_PLT}, - {"AGGREGATED_SYSTEM", CNP_PMC_LTR_CUR_ASLT}, - {} -}; - -static const struct pmc_reg_map cnp_reg_map = { - .pfear_sts = ext_cnp_pfear_map, - .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, - .slp_s0_res_counter_step = SPT_PMC_SLP_S0_RES_COUNTER_STEP, - .slps0_dbg_maps = cnp_slps0_dbg_maps, - .ltr_show_sts = cnp_ltr_show_map, - .msr_sts = msr_map, - .slps0_dbg_offset = CNP_PMC_SLPS0_DBG_OFFSET, - .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, - .regmap_length = CNP_PMC_MMIO_REG_LEN, - .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, - .ppfear_buckets = CNP_PPFEAR_NUM_ENTRIES, - .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, - .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, - .ltr_ignore_max = CNP_NUM_IP_IGN_ALLOWED, - .etr3_offset = ETR3_OFFSET, -}; - -static const struct pmc_reg_map icl_reg_map = { - .pfear_sts = ext_icl_pfear_map, - .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, - .slp_s0_res_counter_step = ICL_PMC_SLP_S0_RES_COUNTER_STEP, - .slps0_dbg_maps = cnp_slps0_dbg_maps, - .ltr_show_sts = cnp_ltr_show_map, - .msr_sts = msr_map, - .slps0_dbg_offset = CNP_PMC_SLPS0_DBG_OFFSET, - .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, - .regmap_length = CNP_PMC_MMIO_REG_LEN, - .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, - .ppfear_buckets = ICL_PPFEAR_NUM_ENTRIES, - .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, - .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, - .ltr_ignore_max = ICL_NUM_IP_IGN_ALLOWED, - .etr3_offset = ETR3_OFFSET, -}; - -static const struct pmc_bit_map tgl_clocksource_status_map[] = { - {"USB2PLL_OFF_STS", BIT(18)}, - {"PCIe/USB3.1_Gen2PLL_OFF_STS", BIT(19)}, - {"PCIe_Gen3PLL_OFF_STS", BIT(20)}, - {"OPIOPLL_OFF_STS", BIT(21)}, - {"OCPLL_OFF_STS", BIT(22)}, - {"MainPLL_OFF_STS", BIT(23)}, - {"MIPIPLL_OFF_STS", BIT(24)}, - {"Fast_XTAL_Osc_OFF_STS", BIT(25)}, - {"AC_Ring_Osc_OFF_STS", BIT(26)}, - {"MC_Ring_Osc_OFF_STS", BIT(27)}, - {"SATAPLL_OFF_STS", BIT(29)}, - {"XTAL_USB2PLL_OFF_STS", BIT(31)}, - {} -}; - -static const struct pmc_bit_map tgl_power_gating_status_map[] = { - {"CSME_PG_STS", BIT(0)}, - {"SATA_PG_STS", BIT(1)}, - {"xHCI_PG_STS", BIT(2)}, - {"UFSX2_PG_STS", BIT(3)}, - {"OTG_PG_STS", BIT(5)}, - {"SPA_PG_STS", BIT(6)}, - {"SPB_PG_STS", BIT(7)}, - {"SPC_PG_STS", BIT(8)}, - {"SPD_PG_STS", BIT(9)}, - {"SPE_PG_STS", BIT(10)}, - {"SPF_PG_STS", BIT(11)}, - {"LSX_PG_STS", BIT(13)}, - {"P2SB_PG_STS", BIT(14)}, - {"PSF_PG_STS", BIT(15)}, - {"SBR_PG_STS", BIT(16)}, - {"OPIDMI_PG_STS", BIT(17)}, - {"THC0_PG_STS", BIT(18)}, - {"THC1_PG_STS", BIT(19)}, - {"GBETSN_PG_STS", BIT(20)}, - {"GBE_PG_STS", BIT(21)}, - {"LPSS_PG_STS", BIT(22)}, - {"MMP_UFSX2_PG_STS", BIT(23)}, - {"MMP_UFSX2B_PG_STS", BIT(24)}, - {"FIA_PG_STS", BIT(25)}, - {} -}; - -static const struct pmc_bit_map tgl_d3_status_map[] = { - {"ADSP_D3_STS", BIT(0)}, - {"SATA_D3_STS", BIT(1)}, - {"xHCI0_D3_STS", BIT(2)}, - {"xDCI1_D3_STS", BIT(5)}, - {"SDX_D3_STS", BIT(6)}, - {"EMMC_D3_STS", BIT(7)}, - {"IS_D3_STS", BIT(8)}, - {"THC0_D3_STS", BIT(9)}, - {"THC1_D3_STS", BIT(10)}, - {"GBE_D3_STS", BIT(11)}, - {"GBE_TSN_D3_STS", BIT(12)}, - {} -}; - -static const struct pmc_bit_map tgl_vnn_req_status_map[] = { - {"GPIO_COM0_VNN_REQ_STS", BIT(1)}, - {"GPIO_COM1_VNN_REQ_STS", BIT(2)}, - {"GPIO_COM2_VNN_REQ_STS", BIT(3)}, - {"GPIO_COM3_VNN_REQ_STS", BIT(4)}, - {"GPIO_COM4_VNN_REQ_STS", BIT(5)}, - {"GPIO_COM5_VNN_REQ_STS", BIT(6)}, - {"Audio_VNN_REQ_STS", BIT(7)}, - {"ISH_VNN_REQ_STS", BIT(8)}, - {"CNVI_VNN_REQ_STS", BIT(9)}, - {"eSPI_VNN_REQ_STS", BIT(10)}, - {"Display_VNN_REQ_STS", BIT(11)}, - {"DTS_VNN_REQ_STS", BIT(12)}, - {"SMBUS_VNN_REQ_STS", BIT(14)}, - {"CSME_VNN_REQ_STS", BIT(15)}, - {"SMLINK0_VNN_REQ_STS", BIT(16)}, - {"SMLINK1_VNN_REQ_STS", BIT(17)}, - {"CLINK_VNN_REQ_STS", BIT(20)}, - {"DCI_VNN_REQ_STS", BIT(21)}, - {"ITH_VNN_REQ_STS", BIT(22)}, - {"CSME_VNN_REQ_STS", BIT(24)}, - {"GBE_VNN_REQ_STS", BIT(25)}, - {} -}; - -static const struct pmc_bit_map tgl_vnn_misc_status_map[] = { - {"CPU_C10_REQ_STS_0", BIT(0)}, - {"PCIe_LPM_En_REQ_STS_3", BIT(3)}, - {"ITH_REQ_STS_5", BIT(5)}, - {"CNVI_REQ_STS_6", BIT(6)}, - {"ISH_REQ_STS_7", BIT(7)}, - {"USB2_SUS_PG_Sys_REQ_STS_10", BIT(10)}, - {"PCIe_Clk_REQ_STS_12", BIT(12)}, - {"MPHY_Core_DL_REQ_STS_16", BIT(16)}, - {"Break-even_En_REQ_STS_17", BIT(17)}, - {"Auto-demo_En_REQ_STS_18", BIT(18)}, - {"MPHY_SUS_REQ_STS_22", BIT(22)}, - {"xDCI_attached_REQ_STS_24", BIT(24)}, - {} -}; - -static const struct pmc_bit_map tgl_signal_status_map[] = { - {"LSX_Wake0_En_STS", BIT(0)}, - {"LSX_Wake0_Pol_STS", BIT(1)}, - {"LSX_Wake1_En_STS", BIT(2)}, - {"LSX_Wake1_Pol_STS", BIT(3)}, - {"LSX_Wake2_En_STS", BIT(4)}, - {"LSX_Wake2_Pol_STS", BIT(5)}, - {"LSX_Wake3_En_STS", BIT(6)}, - {"LSX_Wake3_Pol_STS", BIT(7)}, - {"LSX_Wake4_En_STS", BIT(8)}, - {"LSX_Wake4_Pol_STS", BIT(9)}, - {"LSX_Wake5_En_STS", BIT(10)}, - {"LSX_Wake5_Pol_STS", BIT(11)}, - {"LSX_Wake6_En_STS", BIT(12)}, - {"LSX_Wake6_Pol_STS", BIT(13)}, - {"LSX_Wake7_En_STS", BIT(14)}, - {"LSX_Wake7_Pol_STS", BIT(15)}, - {"Intel_Se_IO_Wake0_En_STS", BIT(16)}, - {"Intel_Se_IO_Wake0_Pol_STS", BIT(17)}, - {"Intel_Se_IO_Wake1_En_STS", BIT(18)}, - {"Intel_Se_IO_Wake1_Pol_STS", BIT(19)}, - {"Int_Timer_SS_Wake0_En_STS", BIT(20)}, - {"Int_Timer_SS_Wake0_Pol_STS", BIT(21)}, - {"Int_Timer_SS_Wake1_En_STS", BIT(22)}, - {"Int_Timer_SS_Wake1_Pol_STS", BIT(23)}, - {"Int_Timer_SS_Wake2_En_STS", BIT(24)}, - {"Int_Timer_SS_Wake2_Pol_STS", BIT(25)}, - {"Int_Timer_SS_Wake3_En_STS", BIT(26)}, - {"Int_Timer_SS_Wake3_Pol_STS", BIT(27)}, - {"Int_Timer_SS_Wake4_En_STS", BIT(28)}, - {"Int_Timer_SS_Wake4_Pol_STS", BIT(29)}, - {"Int_Timer_SS_Wake5_En_STS", BIT(30)}, - {"Int_Timer_SS_Wake5_Pol_STS", BIT(31)}, - {} -}; - -static const struct pmc_bit_map *tgl_lpm_maps[] = { - tgl_clocksource_status_map, - tgl_power_gating_status_map, - tgl_d3_status_map, - tgl_vnn_req_status_map, - tgl_vnn_misc_status_map, - tgl_signal_status_map, - NULL -}; - -static const struct pmc_reg_map tgl_reg_map = { - .pfear_sts = ext_tgl_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 = cnp_ltr_show_map, - .msr_sts = msr_map, - .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, - .regmap_length = CNP_PMC_MMIO_REG_LEN, - .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, - .ppfear_buckets = ICL_PPFEAR_NUM_ENTRIES, - .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, - .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, - .ltr_ignore_max = TGL_NUM_IP_IGN_ALLOWED, - .lpm_num_maps = TGL_LPM_NUM_MAPS, - .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, - .lpm_sts_latch_en_offset = TGL_LPM_STS_LATCH_EN_OFFSET, - .lpm_en_offset = TGL_LPM_EN_OFFSET, - .lpm_priority_offset = TGL_LPM_PRI_OFFSET, - .lpm_residency_offset = TGL_LPM_RESIDENCY_OFFSET, - .lpm_sts = tgl_lpm_maps, - .lpm_status_offset = TGL_LPM_STATUS_OFFSET, - .lpm_live_status_offset = TGL_LPM_LIVE_STATUS_OFFSET, - .etr3_offset = ETR3_OFFSET, -}; - -static void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev) -{ - struct pmc_dev *pmcdev = platform_get_drvdata(pdev); - const int num_maps = pmcdev->map->lpm_num_maps; - u32 lpm_size = LPM_MAX_NUM_MODES * num_maps * 4; - union acpi_object *out_obj; - struct acpi_device *adev; - guid_t s0ix_dsm_guid; - u32 *lpm_req_regs, *addr; - - adev = ACPI_COMPANION(&pdev->dev); - if (!adev) - return; - - guid_parse(ACPI_S0IX_DSM_UUID, &s0ix_dsm_guid); - - out_obj = acpi_evaluate_dsm(adev->handle, &s0ix_dsm_guid, 0, - ACPI_GET_LOW_MODE_REGISTERS, NULL); - if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) { - u32 size = out_obj->buffer.length; - - if (size != lpm_size) { - acpi_handle_debug(adev->handle, - "_DSM returned unexpected buffer size, have %u, expect %u\n", - size, lpm_size); - goto free_acpi_obj; - } - } else { - acpi_handle_debug(adev->handle, - "_DSM function 0 evaluation failed\n"); - goto free_acpi_obj; - } - - addr = (u32 *)out_obj->buffer.pointer; - - lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32), - GFP_KERNEL); - if (!lpm_req_regs) - goto free_acpi_obj; - - memcpy(lpm_req_regs, addr, lpm_size); - pmcdev->lpm_req_regs = lpm_req_regs; - -free_acpi_obj: - ACPI_FREE(out_obj); -} - -static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset) -{ - return readl(pmcdev->regbase + reg_offset); -} - -static inline void pmc_core_reg_write(struct pmc_dev *pmcdev, int reg_offset, - u32 val) -{ - writel(val, pmcdev->regbase + reg_offset); -} - -static inline u64 pmc_core_adjust_slp_s0_step(struct pmc_dev *pmcdev, u32 value) -{ - return (u64)value * pmcdev->map->slp_s0_res_counter_step; -} - -static int set_etr3(struct pmc_dev *pmcdev) -{ - const struct pmc_reg_map *map = pmcdev->map; - u32 reg; - int err; - - if (!map->etr3_offset) - return -EOPNOTSUPP; - - mutex_lock(&pmcdev->lock); - - /* check if CF9 is locked */ - reg = pmc_core_reg_read(pmcdev, map->etr3_offset); - if (reg & ETR3_CF9LOCK) { - err = -EACCES; - goto out_unlock; - } - - /* write CF9 global reset bit */ - reg |= ETR3_CF9GR; - pmc_core_reg_write(pmcdev, map->etr3_offset, reg); - - reg = pmc_core_reg_read(pmcdev, map->etr3_offset); - if (!(reg & ETR3_CF9GR)) { - err = -EIO; - goto out_unlock; - } - - err = 0; - -out_unlock: - mutex_unlock(&pmcdev->lock); - return err; -} -static umode_t etr3_is_visible(struct kobject *kobj, - struct attribute *attr, - int idx) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct pmc_dev *pmcdev = dev_get_drvdata(dev); - const struct pmc_reg_map *map = pmcdev->map; - u32 reg; - - mutex_lock(&pmcdev->lock); - reg = pmc_core_reg_read(pmcdev, map->etr3_offset); - mutex_unlock(&pmcdev->lock); - - return reg & ETR3_CF9LOCK ? attr->mode & (SYSFS_PREALLOC | 0444) : attr->mode; -} - -static ssize_t etr3_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct pmc_dev *pmcdev = dev_get_drvdata(dev); - const struct pmc_reg_map *map = pmcdev->map; - u32 reg; - - if (!map->etr3_offset) - return -EOPNOTSUPP; - - mutex_lock(&pmcdev->lock); - - reg = pmc_core_reg_read(pmcdev, map->etr3_offset); - reg &= ETR3_CF9GR | ETR3_CF9LOCK; - - mutex_unlock(&pmcdev->lock); - - return sysfs_emit(buf, "0x%08x", reg); -} - -static ssize_t etr3_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct pmc_dev *pmcdev = dev_get_drvdata(dev); - int err; - u32 reg; - - err = kstrtouint(buf, 16, ®); - if (err) - return err; - - /* allow only CF9 writes */ - if (reg != ETR3_CF9GR) - return -EINVAL; - - err = set_etr3(pmcdev); - if (err) - return err; - - return len; -} -static DEVICE_ATTR_RW(etr3); - -static struct attribute *pmc_attrs[] = { - &dev_attr_etr3.attr, - NULL -}; - -static const struct attribute_group pmc_attr_group = { - .attrs = pmc_attrs, - .is_visible = etr3_is_visible, -}; - -static const struct attribute_group *pmc_dev_groups[] = { - &pmc_attr_group, - NULL -}; - -static int pmc_core_dev_state_get(void *data, u64 *val) -{ - struct pmc_dev *pmcdev = data; - const struct pmc_reg_map *map = pmcdev->map; - u32 value; - - value = pmc_core_reg_read(pmcdev, map->slp_s0_offset); - *val = pmc_core_adjust_slp_s0_step(pmcdev, value); - - return 0; -} - -DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n"); - -static int pmc_core_check_read_lock_bit(struct pmc_dev *pmcdev) -{ - u32 value; - - value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_cfg_offset); - return value & BIT(pmcdev->map->pm_read_disable_bit); -} - -static void pmc_core_slps0_display(struct pmc_dev *pmcdev, struct device *dev, - struct seq_file *s) -{ - const struct pmc_bit_map **maps = pmcdev->map->slps0_dbg_maps; - const struct pmc_bit_map *map; - int offset = pmcdev->map->slps0_dbg_offset; - u32 data; - - while (*maps) { - map = *maps; - data = pmc_core_reg_read(pmcdev, offset); - offset += 4; - while (map->name) { - if (dev) - dev_info(dev, "SLP_S0_DBG: %-32s\tState: %s\n", - map->name, - data & map->bit_mask ? "Yes" : "No"); - if (s) - seq_printf(s, "SLP_S0_DBG: %-32s\tState: %s\n", - map->name, - data & map->bit_mask ? "Yes" : "No"); - ++map; - } - ++maps; - } -} - -static int pmc_core_lpm_get_arr_size(const struct pmc_bit_map **maps) -{ - int idx; - - for (idx = 0; maps[idx]; idx++) - ;/* Nothing */ - - return idx; -} - -static void pmc_core_lpm_display(struct pmc_dev *pmcdev, struct device *dev, - struct seq_file *s, u32 offset, - const char *str, - const struct pmc_bit_map **maps) -{ - int index, idx, len = 32, bit_mask, arr_size; - u32 *lpm_regs; - - arr_size = pmc_core_lpm_get_arr_size(maps); - lpm_regs = kmalloc_array(arr_size, sizeof(*lpm_regs), GFP_KERNEL); - if (!lpm_regs) - return; - - for (index = 0; index < arr_size; index++) { - lpm_regs[index] = pmc_core_reg_read(pmcdev, offset); - offset += 4; - } - - for (idx = 0; idx < arr_size; idx++) { - if (dev) - dev_info(dev, "\nLPM_%s_%d:\t0x%x\n", str, idx, - lpm_regs[idx]); - if (s) - seq_printf(s, "\nLPM_%s_%d:\t0x%x\n", str, idx, - lpm_regs[idx]); - for (index = 0; maps[idx][index].name && index < len; index++) { - bit_mask = maps[idx][index].bit_mask; - if (dev) - dev_info(dev, "%-30s %-30d\n", - maps[idx][index].name, - lpm_regs[idx] & bit_mask ? 1 : 0); - if (s) - seq_printf(s, "%-30s %-30d\n", - maps[idx][index].name, - lpm_regs[idx] & bit_mask ? 1 : 0); - } - } - - kfree(lpm_regs); -} - -static bool slps0_dbg_latch; - -static inline u8 pmc_core_reg_read_byte(struct pmc_dev *pmcdev, int offset) -{ - return readb(pmcdev->regbase + offset); -} - -static void pmc_core_display_map(struct seq_file *s, int index, int idx, int ip, - u8 pf_reg, const struct pmc_bit_map **pf_map) -{ - seq_printf(s, "PCH IP: %-2d - %-32s\tState: %s\n", - ip, pf_map[idx][index].name, - pf_map[idx][index].bit_mask & pf_reg ? "Off" : "On"); -} - -static int pmc_core_ppfear_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map **maps = pmcdev->map->pfear_sts; - u8 pf_regs[PPFEAR_MAX_NUM_ENTRIES]; - int index, iter, idx, ip = 0; - - iter = pmcdev->map->ppfear0_offset; - - for (index = 0; index < pmcdev->map->ppfear_buckets && - index < PPFEAR_MAX_NUM_ENTRIES; index++, iter++) - pf_regs[index] = pmc_core_reg_read_byte(pmcdev, iter); - - for (idx = 0; maps[idx]; idx++) { - for (index = 0; maps[idx][index].name && - index < pmcdev->map->ppfear_buckets * 8; ip++, index++) - pmc_core_display_map(s, index, idx, ip, - pf_regs[index / 8], maps); - } - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_ppfear); - -/* This function should return link status, 0 means ready */ -static int pmc_core_mtpmc_link_status(struct pmc_dev *pmcdev) -{ - u32 value; - - value = pmc_core_reg_read(pmcdev, SPT_PMC_PM_STS_OFFSET); - return value & BIT(SPT_PMC_MSG_FULL_STS_BIT); -} - -static int pmc_core_send_msg(struct pmc_dev *pmcdev, u32 *addr_xram) -{ - u32 dest; - int timeout; - - for (timeout = NUM_RETRIES; timeout > 0; timeout--) { - if (pmc_core_mtpmc_link_status(pmcdev) == 0) - break; - msleep(5); - } - - if (timeout <= 0 && pmc_core_mtpmc_link_status(pmcdev)) - return -EBUSY; - - dest = (*addr_xram & MTPMC_MASK) | (1U << 1); - pmc_core_reg_write(pmcdev, SPT_PMC_MTPMC_OFFSET, dest); - return 0; -} - -static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map *map = pmcdev->map->mphy_sts; - u32 mphy_core_reg_low, mphy_core_reg_high; - u32 val_low, val_high; - int index, err = 0; - - if (pmcdev->pmc_xram_read_bit) { - seq_puts(s, "Access denied: please disable PMC_READ_DISABLE setting in BIOS."); - return 0; - } - - mphy_core_reg_low = (SPT_PMC_MPHY_CORE_STS_0 << 16); - mphy_core_reg_high = (SPT_PMC_MPHY_CORE_STS_1 << 16); - - mutex_lock(&pmcdev->lock); - - if (pmc_core_send_msg(pmcdev, &mphy_core_reg_low) != 0) { - err = -EBUSY; - goto out_unlock; - } - - msleep(10); - val_low = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET); - - if (pmc_core_send_msg(pmcdev, &mphy_core_reg_high) != 0) { - err = -EBUSY; - goto out_unlock; - } - - msleep(10); - val_high = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET); - - for (index = 0; index < 8 && map[index].name; index++) { - seq_printf(s, "%-32s\tState: %s\n", - map[index].name, - map[index].bit_mask & val_low ? "Not power gated" : - "Power gated"); - } - - for (index = 8; map[index].name; index++) { - seq_printf(s, "%-32s\tState: %s\n", - map[index].name, - map[index].bit_mask & val_high ? "Not power gated" : - "Power gated"); - } - -out_unlock: - mutex_unlock(&pmcdev->lock); - return err; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_mphy_pg); - -static int pmc_core_pll_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map *map = pmcdev->map->pll_sts; - u32 mphy_common_reg, val; - int index, err = 0; - - if (pmcdev->pmc_xram_read_bit) { - seq_puts(s, "Access denied: please disable PMC_READ_DISABLE setting in BIOS."); - return 0; - } - - mphy_common_reg = (SPT_PMC_MPHY_COM_STS_0 << 16); - mutex_lock(&pmcdev->lock); - - if (pmc_core_send_msg(pmcdev, &mphy_common_reg) != 0) { - err = -EBUSY; - goto out_unlock; - } - - /* Observed PMC HW response latency for MTPMC-MFPMC is ~10 ms */ - msleep(10); - val = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET); - - for (index = 0; map[index].name ; index++) { - seq_printf(s, "%-32s\tState: %s\n", - map[index].name, - map[index].bit_mask & val ? "Active" : "Idle"); - } - -out_unlock: - mutex_unlock(&pmcdev->lock); - return err; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_pll); - -static int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value) -{ - const struct pmc_reg_map *map = pmcdev->map; - u32 reg; - int err = 0; - - mutex_lock(&pmcdev->lock); - - if (value > map->ltr_ignore_max) { - err = -EINVAL; - goto out_unlock; - } - - reg = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset); - reg |= BIT(value); - pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, reg); - -out_unlock: - mutex_unlock(&pmcdev->lock); - - return err; -} - -static ssize_t pmc_core_ltr_ignore_write(struct file *file, - const char __user *userbuf, - size_t count, loff_t *ppos) -{ - struct seq_file *s = file->private_data; - struct pmc_dev *pmcdev = s->private; - u32 buf_size, value; - int err; - - buf_size = min_t(u32, count, 64); - - err = kstrtou32_from_user(userbuf, buf_size, 10, &value); - if (err) - return err; - - err = pmc_core_send_ltr_ignore(pmcdev, value); - - return err == 0 ? count : err; -} - -static int pmc_core_ltr_ignore_show(struct seq_file *s, void *unused) -{ - return 0; -} - -static int pmc_core_ltr_ignore_open(struct inode *inode, struct file *file) -{ - return single_open(file, pmc_core_ltr_ignore_show, inode->i_private); -} - -static const struct file_operations pmc_core_ltr_ignore_ops = { - .open = pmc_core_ltr_ignore_open, - .read = seq_read, - .write = pmc_core_ltr_ignore_write, - .llseek = seq_lseek, - .release = single_release, -}; - -static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset) -{ - const struct pmc_reg_map *map = pmcdev->map; - u32 fd; - - mutex_lock(&pmcdev->lock); - - if (!reset && !slps0_dbg_latch) - goto out_unlock; - - fd = pmc_core_reg_read(pmcdev, map->slps0_dbg_offset); - if (reset) - fd &= ~CNP_PMC_LATCH_SLPS0_EVENTS; - else - fd |= CNP_PMC_LATCH_SLPS0_EVENTS; - pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd); - - slps0_dbg_latch = false; - -out_unlock: - mutex_unlock(&pmcdev->lock); -} - -static int pmc_core_slps0_dbg_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - - pmc_core_slps0_dbg_latch(pmcdev, false); - pmc_core_slps0_display(pmcdev, NULL, s); - pmc_core_slps0_dbg_latch(pmcdev, true); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_slps0_dbg); - -static u32 convert_ltr_scale(u32 val) -{ - /* - * As per PCIE specification supporting document - * ECN_LatencyTolnReporting_14Aug08.pdf the Latency - * Tolerance Reporting data payload is encoded in a - * 3 bit scale and 10 bit value fields. Values are - * multiplied by the indicated scale to yield an absolute time - * value, expressible in a range from 1 nanosecond to - * 2^25*(2^10-1) = 34,326,183,936 nanoseconds. - * - * scale encoding is as follows: - * - * ---------------------------------------------- - * |scale factor | Multiplier (ns) | - * ---------------------------------------------- - * | 0 | 1 | - * | 1 | 32 | - * | 2 | 1024 | - * | 3 | 32768 | - * | 4 | 1048576 | - * | 5 | 33554432 | - * | 6 | Invalid | - * | 7 | Invalid | - * ---------------------------------------------- - */ - if (val > 5) { - pr_warn("Invalid LTR scale factor.\n"); - return 0; - } - - return 1U << (5 * val); -} - -static int pmc_core_ltr_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map *map = pmcdev->map->ltr_show_sts; - u64 decoded_snoop_ltr, decoded_non_snoop_ltr; - u32 ltr_raw_data, scale, val; - u16 snoop_ltr, nonsnoop_ltr; - int index; - - for (index = 0; map[index].name ; index++) { - decoded_snoop_ltr = decoded_non_snoop_ltr = 0; - ltr_raw_data = pmc_core_reg_read(pmcdev, - map[index].bit_mask); - snoop_ltr = ltr_raw_data & ~MTPMC_MASK; - nonsnoop_ltr = (ltr_raw_data >> 0x10) & ~MTPMC_MASK; - - if (FIELD_GET(LTR_REQ_NONSNOOP, ltr_raw_data)) { - scale = FIELD_GET(LTR_DECODED_SCALE, nonsnoop_ltr); - val = FIELD_GET(LTR_DECODED_VAL, nonsnoop_ltr); - decoded_non_snoop_ltr = val * convert_ltr_scale(scale); - } - - if (FIELD_GET(LTR_REQ_SNOOP, ltr_raw_data)) { - scale = FIELD_GET(LTR_DECODED_SCALE, snoop_ltr); - val = FIELD_GET(LTR_DECODED_VAL, snoop_ltr); - decoded_snoop_ltr = val * convert_ltr_scale(scale); - } - - seq_printf(s, "%-32s\tLTR: RAW: 0x%-16x\tNon-Snoop(ns): %-16llu\tSnoop(ns): %-16llu\n", - map[index].name, ltr_raw_data, - decoded_non_snoop_ltr, - decoded_snoop_ltr); - } - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr); - -static inline u64 adjust_lpm_residency(struct pmc_dev *pmcdev, u32 offset, - const int lpm_adj_x2) -{ - u64 lpm_res = pmc_core_reg_read(pmcdev, offset); - - return GET_X2_COUNTER((u64)lpm_adj_x2 * lpm_res); -} - -static int pmc_core_substate_res_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const int lpm_adj_x2 = pmcdev->map->lpm_res_counter_step_x2; - u32 offset = pmcdev->map->lpm_residency_offset; - int i, mode; - - seq_printf(s, "%-10s %-15s\n", "Substate", "Residency"); - - pmc_for_each_mode(i, mode, pmcdev) { - seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode], - adjust_lpm_residency(pmcdev, offset + (4 * mode), lpm_adj_x2)); - } - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_res); - -static int pmc_core_substate_sts_regs_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; - u32 offset = pmcdev->map->lpm_status_offset; - - pmc_core_lpm_display(pmcdev, NULL, s, offset, "STATUS", maps); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_sts_regs); - -static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; - u32 offset = pmcdev->map->lpm_live_status_offset; - - pmc_core_lpm_display(pmcdev, NULL, s, offset, "LIVE_STATUS", maps); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs); - -static void pmc_core_substate_req_header_show(struct seq_file *s) -{ - struct pmc_dev *pmcdev = s->private; - int i, mode; - - seq_printf(s, "%30s |", "Element"); - pmc_for_each_mode(i, mode, pmcdev) - seq_printf(s, " %9s |", pmc_lpm_modes[mode]); - - seq_printf(s, " %9s |\n", "Status"); -} - -static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; - const struct pmc_bit_map *map; - const int num_maps = pmcdev->map->lpm_num_maps; - u32 sts_offset = pmcdev->map->lpm_status_offset; - u32 *lpm_req_regs = pmcdev->lpm_req_regs; - int mp; - - /* Display the header */ - pmc_core_substate_req_header_show(s); - - /* Loop over maps */ - for (mp = 0; mp < num_maps; mp++) { - u32 req_mask = 0; - u32 lpm_status; - int mode, idx, i, len = 32; - - /* - * Capture the requirements and create a mask so that we only - * show an element if it's required for at least one of the - * enabled low power modes - */ - pmc_for_each_mode(idx, mode, pmcdev) - req_mask |= lpm_req_regs[mp + (mode * num_maps)]; - - /* Get the last latched status for this map */ - lpm_status = pmc_core_reg_read(pmcdev, sts_offset + (mp * 4)); - - /* Loop over elements in this map */ - map = maps[mp]; - for (i = 0; map[i].name && i < len; i++) { - u32 bit_mask = map[i].bit_mask; - - if (!(bit_mask & req_mask)) - /* - * Not required for any enabled states - * so don't display - */ - continue; - - /* Display the element name in the first column */ - seq_printf(s, "%30s |", map[i].name); - - /* Loop over the enabled states and display if required */ - pmc_for_each_mode(idx, mode, pmcdev) { - if (lpm_req_regs[mp + (mode * num_maps)] & bit_mask) - seq_printf(s, " %9s |", - "Required"); - else - seq_printf(s, " %9s |", " "); - } - - /* In Status column, show the last captured state of this agent */ - if (lpm_status & bit_mask) - seq_printf(s, " %9s |", "Yes"); - else - seq_printf(s, " %9s |", " "); - - seq_puts(s, "\n"); - } - } - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs); - -static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - bool c10; - u32 reg; - int idx, mode; - - reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); - if (reg & LPM_STS_LATCH_MODE) { - seq_puts(s, "c10"); - c10 = false; - } else { - seq_puts(s, "[c10]"); - c10 = true; - } - - pmc_for_each_mode(idx, mode, pmcdev) { - if ((BIT(mode) & reg) && !c10) - seq_printf(s, " [%s]", pmc_lpm_modes[mode]); - else - seq_printf(s, " %s", pmc_lpm_modes[mode]); - } - - seq_puts(s, " clear\n"); - - return 0; -} - -static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, - const char __user *userbuf, - size_t count, loff_t *ppos) -{ - struct seq_file *s = file->private_data; - struct pmc_dev *pmcdev = s->private; - bool clear = false, c10 = false; - unsigned char buf[8]; - int idx, m, mode; - u32 reg; - - if (count > sizeof(buf) - 1) - return -EINVAL; - if (copy_from_user(buf, userbuf, count)) - return -EFAULT; - buf[count] = '\0'; - - /* - * Allowed strings are: - * Any enabled substate, e.g. 'S0i2.0' - * 'c10' - * 'clear' - */ - mode = sysfs_match_string(pmc_lpm_modes, buf); - - /* Check string matches enabled mode */ - pmc_for_each_mode(idx, m, pmcdev) - if (mode == m) - break; - - if (mode != m || mode < 0) { - if (sysfs_streq(buf, "clear")) - clear = true; - else if (sysfs_streq(buf, "c10")) - c10 = true; - else - return -EINVAL; - } - - if (clear) { - mutex_lock(&pmcdev->lock); - - reg = pmc_core_reg_read(pmcdev, pmcdev->map->etr3_offset); - reg |= ETR3_CLEAR_LPM_EVENTS; - pmc_core_reg_write(pmcdev, pmcdev->map->etr3_offset, reg); - - mutex_unlock(&pmcdev->lock); - - return count; - } - - if (c10) { - mutex_lock(&pmcdev->lock); - - reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); - reg &= ~LPM_STS_LATCH_MODE; - pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); - - mutex_unlock(&pmcdev->lock); - - return count; - } - - /* - * For LPM mode latching we set the latch enable bit and selected mode - * and clear everything else. - */ - reg = LPM_STS_LATCH_MODE | BIT(mode); - mutex_lock(&pmcdev->lock); - pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); - mutex_unlock(&pmcdev->lock); - - return count; -} -DEFINE_PMC_CORE_ATTR_WRITE(pmc_core_lpm_latch_mode); - -static int pmc_core_pkgc_show(struct seq_file *s, void *unused) -{ - struct pmc_dev *pmcdev = s->private; - const struct pmc_bit_map *map = pmcdev->map->msr_sts; - u64 pcstate_count; - int index; - - for (index = 0; map[index].name ; index++) { - if (rdmsrl_safe(map[index].bit_mask, &pcstate_count)) - continue; - - pcstate_count *= 1000; - do_div(pcstate_count, tsc_khz); - seq_printf(s, "%-8s : %llu\n", map[index].name, - pcstate_count); - } - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc); - -static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order) -{ - int i, j; - - if (!lpm_pri) - return false; - /* - * Each byte contains the priority level for 2 modes (7:4 and 3:0). - * In a 32 bit register this allows for describing 8 modes. Store the - * levels and look for values out of range. - */ - for (i = 0; i < 8; i++) { - int level = lpm_pri & GENMASK(3, 0); - - if (level >= LPM_MAX_NUM_MODES) - return false; - - mode_order[i] = level; - lpm_pri >>= 4; - } - - /* Check that we have unique values */ - for (i = 0; i < LPM_MAX_NUM_MODES - 1; i++) - for (j = i + 1; j < LPM_MAX_NUM_MODES; j++) - if (mode_order[i] == mode_order[j]) - return false; - - return true; -} - -static void pmc_core_get_low_power_modes(struct platform_device *pdev) -{ - struct pmc_dev *pmcdev = platform_get_drvdata(pdev); - u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI; - u8 mode_order[LPM_MAX_NUM_MODES]; - u32 lpm_pri; - u32 lpm_en; - int mode, i, p; - - /* Use LPM Maps to indicate support for substates */ - if (!pmcdev->map->lpm_num_maps) - return; - - lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset); - pmcdev->num_lpm_modes = hweight32(lpm_en); - - /* Read 32 bit LPM_PRI register */ - lpm_pri = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_priority_offset); - - - /* - * If lpm_pri value passes verification, then override the default - * modes here. Otherwise stick with the default. - */ - if (pmc_core_pri_verify(lpm_pri, mode_order)) - /* Get list of modes in priority order */ - for (mode = 0; mode < LPM_MAX_NUM_MODES; mode++) - pri_order[mode_order[mode]] = mode; - else - dev_warn(&pdev->dev, "Assuming a default substate order for this platform\n"); - - /* - * Loop through all modes from lowest to highest priority, - * and capture all enabled modes in order - */ - i = 0; - for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) { - int mode = pri_order[p]; - - if (!(BIT(mode) & lpm_en)) - continue; - - pmcdev->lpm_en_modes[i++] = mode; - } -} - -static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) -{ - debugfs_remove_recursive(pmcdev->dbgfs_dir); -} - -static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) -{ - struct dentry *dir; - - dir = debugfs_create_dir("pmc_core", NULL); - pmcdev->dbgfs_dir = dir; - - debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev, - &pmc_core_dev_state); - - if (pmcdev->map->pfear_sts) - debugfs_create_file("pch_ip_power_gating_status", 0444, dir, - pmcdev, &pmc_core_ppfear_fops); - - debugfs_create_file("ltr_ignore", 0644, dir, pmcdev, - &pmc_core_ltr_ignore_ops); - - debugfs_create_file("ltr_show", 0444, dir, pmcdev, &pmc_core_ltr_fops); - - debugfs_create_file("package_cstate_show", 0444, dir, pmcdev, - &pmc_core_pkgc_fops); - - if (pmcdev->map->pll_sts) - debugfs_create_file("pll_status", 0444, dir, pmcdev, - &pmc_core_pll_fops); - - if (pmcdev->map->mphy_sts) - debugfs_create_file("mphy_core_lanes_power_gating_status", - 0444, dir, pmcdev, - &pmc_core_mphy_pg_fops); - - if (pmcdev->map->slps0_dbg_maps) { - debugfs_create_file("slp_s0_debug_status", 0444, - dir, pmcdev, - &pmc_core_slps0_dbg_fops); - - debugfs_create_bool("slp_s0_dbg_latch", 0644, - dir, &slps0_dbg_latch); - } - - if (pmcdev->map->lpm_en_offset) { - debugfs_create_file("substate_residencies", 0444, - pmcdev->dbgfs_dir, pmcdev, - &pmc_core_substate_res_fops); - } - - if (pmcdev->map->lpm_status_offset) { - debugfs_create_file("substate_status_registers", 0444, - pmcdev->dbgfs_dir, pmcdev, - &pmc_core_substate_sts_regs_fops); - debugfs_create_file("substate_live_status_registers", 0444, - pmcdev->dbgfs_dir, pmcdev, - &pmc_core_substate_l_sts_regs_fops); - debugfs_create_file("lpm_latch_mode", 0644, - pmcdev->dbgfs_dir, pmcdev, - &pmc_core_lpm_latch_mode_fops); - } - - if (pmcdev->lpm_req_regs) { - debugfs_create_file("substate_requirements", 0444, - pmcdev->dbgfs_dir, pmcdev, - &pmc_core_substate_req_regs_fops); - } -} - -static const struct x86_cpu_id intel_pmc_core_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, &spt_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, &spt_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, &spt_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, &spt_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, &cnp_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, &icl_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_NNPI, &icl_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, &cnp_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, &cnp_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, &tgl_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, &tgl_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, &tgl_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L, &icl_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, &tgl_reg_map), - X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, &tgl_reg_map), - {} -}; - -MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_ids); - -static const struct pci_device_id pmc_pci_ids[] = { - { PCI_VDEVICE(INTEL, SPT_PMC_PCI_DEVICE_ID) }, - { } -}; - -/* - * This quirk can be used on those platforms where - * the platform BIOS enforces 24Mhz crystal to shutdown - * before PMC can assert SLP_S0#. - */ -static bool xtal_ignore; -static int quirk_xtal_ignore(const struct dmi_system_id *id) -{ - xtal_ignore = true; - return 0; -} - -static void pmc_core_xtal_ignore(struct pmc_dev *pmcdev) -{ - u32 value; - - value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_vric1_offset); - /* 24MHz Crystal Shutdown Qualification Disable */ - value |= SPT_PMC_VRIC1_XTALSDQDIS; - /* Low Voltage Mode Enable */ - value &= ~SPT_PMC_VRIC1_SLPS0LVEN; - pmc_core_reg_write(pmcdev, pmcdev->map->pm_vric1_offset, value); -} - -static const struct dmi_system_id pmc_core_dmi_table[] = { - { - .callback = quirk_xtal_ignore, - .ident = "HP Elite x2 1013 G3", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "HP"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite x2 1013 G3"), - }, - }, - {} -}; - -static void pmc_core_do_dmi_quirks(struct pmc_dev *pmcdev) -{ - dmi_check_system(pmc_core_dmi_table); - - if (xtal_ignore) - pmc_core_xtal_ignore(pmcdev); -} - -static int pmc_core_probe(struct platform_device *pdev) -{ - static bool device_initialized; - struct pmc_dev *pmcdev; - const struct x86_cpu_id *cpu_id; - u64 slp_s0_addr; - - if (device_initialized) - return -ENODEV; - - pmcdev = devm_kzalloc(&pdev->dev, sizeof(*pmcdev), GFP_KERNEL); - if (!pmcdev) - return -ENOMEM; - - platform_set_drvdata(pdev, pmcdev); - - cpu_id = x86_match_cpu(intel_pmc_core_ids); - if (!cpu_id) - return -ENODEV; - - pmcdev->map = (struct pmc_reg_map *)cpu_id->driver_data; - - /* - * Coffee Lake has CPU ID of Kaby Lake and Cannon Lake PCH. So here - * Sunrisepoint PCH regmap can't be used. Use Cannon Lake PCH regmap - * in this case. - */ - if (pmcdev->map == &spt_reg_map && !pci_dev_present(pmc_pci_ids)) - pmcdev->map = &cnp_reg_map; - - if (lpit_read_residency_count_address(&slp_s0_addr)) { - pmcdev->base_addr = PMC_BASE_ADDR_DEFAULT; - - if (page_is_ram(PHYS_PFN(pmcdev->base_addr))) - return -ENODEV; - } else { - pmcdev->base_addr = slp_s0_addr - pmcdev->map->slp_s0_offset; - } - - pmcdev->regbase = ioremap(pmcdev->base_addr, - pmcdev->map->regmap_length); - if (!pmcdev->regbase) - return -ENOMEM; - - mutex_init(&pmcdev->lock); - - pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(pmcdev); - pmc_core_get_low_power_modes(pdev); - pmc_core_do_dmi_quirks(pmcdev); - - if (pmcdev->map == &tgl_reg_map) - pmc_core_get_tgl_lpm_reqs(pdev); - - /* - * On TGL, due to a hardware limitation, the GBE LTR blocks PC10 when - * a cable is attached. Tell the PMC to ignore it. - */ - if (pmcdev->map == &tgl_reg_map) { - dev_dbg(&pdev->dev, "ignoring GBE LTR\n"); - pmc_core_send_ltr_ignore(pmcdev, 3); - } - - pmc_core_dbgfs_register(pmcdev); - - device_initialized = true; - dev_info(&pdev->dev, " initialized\n"); - - return 0; -} - -static int pmc_core_remove(struct platform_device *pdev) -{ - struct pmc_dev *pmcdev = platform_get_drvdata(pdev); - - pmc_core_dbgfs_unregister(pmcdev); - platform_set_drvdata(pdev, NULL); - mutex_destroy(&pmcdev->lock); - iounmap(pmcdev->regbase); - return 0; -} - -static bool warn_on_s0ix_failures; -module_param(warn_on_s0ix_failures, bool, 0644); -MODULE_PARM_DESC(warn_on_s0ix_failures, "Check and warn for S0ix failures"); - -static __maybe_unused int pmc_core_suspend(struct device *dev) -{ - struct pmc_dev *pmcdev = dev_get_drvdata(dev); - - pmcdev->check_counters = false; - - /* No warnings on S0ix failures */ - if (!warn_on_s0ix_failures) - return 0; - - /* Check if the syspend will actually use S0ix */ - if (pm_suspend_via_firmware()) - return 0; - - /* Save PC10 residency for checking later */ - if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pmcdev->pc10_counter)) - return -EIO; - - /* Save S0ix residency for checking later */ - if (pmc_core_dev_state_get(pmcdev, &pmcdev->s0ix_counter)) - return -EIO; - - pmcdev->check_counters = true; - return 0; -} - -static inline bool pmc_core_is_pc10_failed(struct pmc_dev *pmcdev) -{ - u64 pc10_counter; - - if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pc10_counter)) - return false; - - if (pc10_counter == pmcdev->pc10_counter) - return true; - - return false; -} - -static inline bool pmc_core_is_s0ix_failed(struct pmc_dev *pmcdev) -{ - u64 s0ix_counter; - - if (pmc_core_dev_state_get(pmcdev, &s0ix_counter)) - return false; - - if (s0ix_counter == pmcdev->s0ix_counter) - return true; - - return false; -} - -static __maybe_unused int pmc_core_resume(struct device *dev) -{ - struct pmc_dev *pmcdev = dev_get_drvdata(dev); - const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; - int offset = pmcdev->map->lpm_status_offset; - - if (!pmcdev->check_counters) - return 0; - - if (!pmc_core_is_s0ix_failed(pmcdev)) - return 0; - - if (pmc_core_is_pc10_failed(pmcdev)) { - /* S0ix failed because of PC10 entry failure */ - dev_info(dev, "CPU did not enter PC10!!! (PC10 cnt=0x%llx)\n", - pmcdev->pc10_counter); - return 0; - } - - /* The real interesting case - S0ix failed - lets ask PMC why. */ - dev_warn(dev, "CPU did not enter SLP_S0!!! (S0ix cnt=%llu)\n", - pmcdev->s0ix_counter); - if (pmcdev->map->slps0_dbg_maps) - pmc_core_slps0_display(pmcdev, dev, NULL); - if (pmcdev->map->lpm_sts) - pmc_core_lpm_display(pmcdev, dev, NULL, offset, "STATUS", maps); - - return 0; -} - -static const struct dev_pm_ops pmc_core_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(pmc_core_suspend, pmc_core_resume) -}; - -static const struct acpi_device_id pmc_core_acpi_ids[] = { - {"INT33A1", 0}, /* _HID for Intel Power Engine, _CID PNP0D80*/ - { } -}; -MODULE_DEVICE_TABLE(acpi, pmc_core_acpi_ids); - -static struct platform_driver pmc_core_driver = { - .driver = { - .name = "intel_pmc_core", - .acpi_match_table = ACPI_PTR(pmc_core_acpi_ids), - .pm = &pmc_core_pm_ops, - .dev_groups = pmc_dev_groups, - }, - .probe = pmc_core_probe, - .remove = pmc_core_remove, -}; - -module_platform_driver(pmc_core_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel PMC Core Driver"); diff --git a/drivers/platform/x86/intel_pmc_core.h b/drivers/platform/x86/intel_pmc_core.h deleted file mode 100644 index b9bf3d3d6f7a..000000000000 --- a/drivers/platform/x86/intel_pmc_core.h +++ /dev/null @@ -1,346 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Intel Core SoC Power Management Controller Header File - * - * Copyright (c) 2016, Intel Corporation. - * All Rights Reserved. - * - * Authors: Rajneesh Bhardwaj - * Vishwanath Somayaji - */ - -#ifndef PMC_CORE_H -#define PMC_CORE_H - -#include - -#define PMC_BASE_ADDR_DEFAULT 0xFE000000 - -/* Sunrise Point Power Management Controller PCI Device ID */ -#define SPT_PMC_PCI_DEVICE_ID 0x9d21 -#define SPT_PMC_BASE_ADDR_OFFSET 0x48 -#define SPT_PMC_SLP_S0_RES_COUNTER_OFFSET 0x13c -#define SPT_PMC_PM_CFG_OFFSET 0x18 -#define SPT_PMC_PM_STS_OFFSET 0x1c -#define SPT_PMC_MTPMC_OFFSET 0x20 -#define SPT_PMC_MFPMC_OFFSET 0x38 -#define SPT_PMC_LTR_IGNORE_OFFSET 0x30C -#define SPT_PMC_VRIC1_OFFSET 0x31c -#define SPT_PMC_MPHY_CORE_STS_0 0x1143 -#define SPT_PMC_MPHY_CORE_STS_1 0x1142 -#define SPT_PMC_MPHY_COM_STS_0 0x1155 -#define SPT_PMC_MMIO_REG_LEN 0x1000 -#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 SPT_PPFEAR_NUM_ENTRIES 5 -#define SPT_PMC_READ_DISABLE_BIT 0x16 -#define SPT_PMC_MSG_FULL_STS_BIT 0x18 -#define NUM_RETRIES 100 -#define SPT_NUM_IP_IGN_ALLOWED 17 - -#define SPT_PMC_LTR_CUR_PLT 0x350 -#define SPT_PMC_LTR_CUR_ASLT 0x354 -#define SPT_PMC_LTR_SPA 0x360 -#define SPT_PMC_LTR_SPB 0x364 -#define SPT_PMC_LTR_SATA 0x368 -#define SPT_PMC_LTR_GBE 0x36C -#define SPT_PMC_LTR_XHCI 0x370 -#define SPT_PMC_LTR_RESERVED 0x374 -#define SPT_PMC_LTR_ME 0x378 -#define SPT_PMC_LTR_EVA 0x37C -#define SPT_PMC_LTR_SPC 0x380 -#define SPT_PMC_LTR_AZ 0x384 -#define SPT_PMC_LTR_LPSS 0x38C -#define SPT_PMC_LTR_CAM 0x390 -#define SPT_PMC_LTR_SPD 0x394 -#define SPT_PMC_LTR_SPE 0x398 -#define SPT_PMC_LTR_ESPI 0x39C -#define SPT_PMC_LTR_SCC 0x3A0 -#define SPT_PMC_LTR_ISH 0x3A4 - -/* Sunrise Point: PGD PFET Enable Ack Status Registers */ -enum ppfear_regs { - SPT_PMC_XRAM_PPFEAR0A = 0x590, - SPT_PMC_XRAM_PPFEAR0B, - SPT_PMC_XRAM_PPFEAR0C, - SPT_PMC_XRAM_PPFEAR0D, - SPT_PMC_XRAM_PPFEAR1A, -}; - -#define SPT_PMC_BIT_PMC BIT(0) -#define SPT_PMC_BIT_OPI BIT(1) -#define SPT_PMC_BIT_SPI BIT(2) -#define SPT_PMC_BIT_XHCI BIT(3) -#define SPT_PMC_BIT_SPA BIT(4) -#define SPT_PMC_BIT_SPB BIT(5) -#define SPT_PMC_BIT_SPC BIT(6) -#define SPT_PMC_BIT_GBE BIT(7) - -#define SPT_PMC_BIT_SATA BIT(0) -#define SPT_PMC_BIT_HDA_PGD0 BIT(1) -#define SPT_PMC_BIT_HDA_PGD1 BIT(2) -#define SPT_PMC_BIT_HDA_PGD2 BIT(3) -#define SPT_PMC_BIT_HDA_PGD3 BIT(4) -#define SPT_PMC_BIT_RSVD_0B BIT(5) -#define SPT_PMC_BIT_LPSS BIT(6) -#define SPT_PMC_BIT_LPC BIT(7) - -#define SPT_PMC_BIT_SMB BIT(0) -#define SPT_PMC_BIT_ISH BIT(1) -#define SPT_PMC_BIT_P2SB BIT(2) -#define SPT_PMC_BIT_DFX BIT(3) -#define SPT_PMC_BIT_SCC BIT(4) -#define SPT_PMC_BIT_RSVD_0C BIT(5) -#define SPT_PMC_BIT_FUSE BIT(6) -#define SPT_PMC_BIT_CAMREA BIT(7) - -#define SPT_PMC_BIT_RSVD_0D BIT(0) -#define SPT_PMC_BIT_USB3_OTG BIT(1) -#define SPT_PMC_BIT_EXI BIT(2) -#define SPT_PMC_BIT_CSE BIT(3) -#define SPT_PMC_BIT_CSME_KVM BIT(4) -#define SPT_PMC_BIT_CSME_PMT BIT(5) -#define SPT_PMC_BIT_CSME_CLINK BIT(6) -#define SPT_PMC_BIT_CSME_PTIO BIT(7) - -#define SPT_PMC_BIT_CSME_USBR BIT(0) -#define SPT_PMC_BIT_CSME_SUSRAM BIT(1) -#define SPT_PMC_BIT_CSME_SMT BIT(2) -#define SPT_PMC_BIT_RSVD_1A BIT(3) -#define SPT_PMC_BIT_CSME_SMS2 BIT(4) -#define SPT_PMC_BIT_CSME_SMS1 BIT(5) -#define SPT_PMC_BIT_CSME_RTC BIT(6) -#define SPT_PMC_BIT_CSME_PSF BIT(7) - -#define SPT_PMC_BIT_MPHY_LANE0 BIT(0) -#define SPT_PMC_BIT_MPHY_LANE1 BIT(1) -#define SPT_PMC_BIT_MPHY_LANE2 BIT(2) -#define SPT_PMC_BIT_MPHY_LANE3 BIT(3) -#define SPT_PMC_BIT_MPHY_LANE4 BIT(4) -#define SPT_PMC_BIT_MPHY_LANE5 BIT(5) -#define SPT_PMC_BIT_MPHY_LANE6 BIT(6) -#define SPT_PMC_BIT_MPHY_LANE7 BIT(7) - -#define SPT_PMC_BIT_MPHY_LANE8 BIT(0) -#define SPT_PMC_BIT_MPHY_LANE9 BIT(1) -#define SPT_PMC_BIT_MPHY_LANE10 BIT(2) -#define SPT_PMC_BIT_MPHY_LANE11 BIT(3) -#define SPT_PMC_BIT_MPHY_LANE12 BIT(4) -#define SPT_PMC_BIT_MPHY_LANE13 BIT(5) -#define SPT_PMC_BIT_MPHY_LANE14 BIT(6) -#define SPT_PMC_BIT_MPHY_LANE15 BIT(7) - -#define SPT_PMC_BIT_MPHY_CMN_LANE0 BIT(0) -#define SPT_PMC_BIT_MPHY_CMN_LANE1 BIT(1) -#define SPT_PMC_BIT_MPHY_CMN_LANE2 BIT(2) -#define SPT_PMC_BIT_MPHY_CMN_LANE3 BIT(3) - -#define SPT_PMC_VRIC1_SLPS0LVEN BIT(13) -#define SPT_PMC_VRIC1_XTALSDQDIS BIT(22) - -/* Cannonlake Power Management Controller register offsets */ -#define CNP_PMC_SLPS0_DBG_OFFSET 0x10B4 -#define CNP_PMC_PM_CFG_OFFSET 0x1818 -#define CNP_PMC_SLP_S0_RES_COUNTER_OFFSET 0x193C -#define CNP_PMC_LTR_IGNORE_OFFSET 0x1B0C -/* Cannonlake: PGD PFET Enable Ack Status Register(s) start */ -#define CNP_PMC_HOST_PPFEAR0A 0x1D90 - -#define CNP_PMC_LATCH_SLPS0_EVENTS BIT(31) - -#define CNP_PMC_MMIO_REG_LEN 0x2000 -#define CNP_PPFEAR_NUM_ENTRIES 8 -#define CNP_PMC_READ_DISABLE_BIT 22 -#define CNP_NUM_IP_IGN_ALLOWED 19 -#define CNP_PMC_LTR_CUR_PLT 0x1B50 -#define CNP_PMC_LTR_CUR_ASLT 0x1B54 -#define CNP_PMC_LTR_SPA 0x1B60 -#define CNP_PMC_LTR_SPB 0x1B64 -#define CNP_PMC_LTR_SATA 0x1B68 -#define CNP_PMC_LTR_GBE 0x1B6C -#define CNP_PMC_LTR_XHCI 0x1B70 -#define CNP_PMC_LTR_RESERVED 0x1B74 -#define CNP_PMC_LTR_ME 0x1B78 -#define CNP_PMC_LTR_EVA 0x1B7C -#define CNP_PMC_LTR_SPC 0x1B80 -#define CNP_PMC_LTR_AZ 0x1B84 -#define CNP_PMC_LTR_LPSS 0x1B8C -#define CNP_PMC_LTR_CAM 0x1B90 -#define CNP_PMC_LTR_SPD 0x1B94 -#define CNP_PMC_LTR_SPE 0x1B98 -#define CNP_PMC_LTR_ESPI 0x1B9C -#define CNP_PMC_LTR_SCC 0x1BA0 -#define CNP_PMC_LTR_ISH 0x1BA4 -#define CNP_PMC_LTR_CNV 0x1BF0 -#define CNP_PMC_LTR_EMMC 0x1BF4 -#define CNP_PMC_LTR_UFSX2 0x1BF8 - -#define LTR_DECODED_VAL GENMASK(9, 0) -#define LTR_DECODED_SCALE GENMASK(12, 10) -#define LTR_REQ_SNOOP BIT(15) -#define LTR_REQ_NONSNOOP BIT(31) - -#define ICL_PPFEAR_NUM_ENTRIES 9 -#define ICL_NUM_IP_IGN_ALLOWED 20 -#define ICL_PMC_LTR_WIGIG 0x1BFC -#define ICL_PMC_SLP_S0_RES_COUNTER_STEP 0x64 - -#define LPM_MAX_NUM_MODES 8 -#define LPM_DEFAULT_PRI { 7, 6, 2, 5, 4, 1, 3, 0 } - -#define GET_X2_COUNTER(v) ((v) >> 1) -#define LPM_STS_LATCH_MODE BIT(31) - -#define TGL_PMC_SLP_S0_RES_COUNTER_STEP 0x7A -#define TGL_PMC_LTR_THC0 0x1C04 -#define TGL_PMC_LTR_THC1 0x1C08 -#define TGL_NUM_IP_IGN_ALLOWED 23 -#define TGL_PMC_LPM_RES_COUNTER_STEP_X2 61 /* 30.5us * 2 */ - -/* - * Tigerlake Power Management Controller register offsets - */ -#define TGL_LPM_STS_LATCH_EN_OFFSET 0x1C34 -#define TGL_LPM_EN_OFFSET 0x1C78 -#define TGL_LPM_RESIDENCY_OFFSET 0x1C80 - -/* Tigerlake Low Power Mode debug registers */ -#define TGL_LPM_STATUS_OFFSET 0x1C3C -#define TGL_LPM_LIVE_STATUS_OFFSET 0x1C5C -#define TGL_LPM_PRI_OFFSET 0x1C7C -#define TGL_LPM_NUM_MAPS 6 - -/* Extended Test Mode Register 3 (CNL and later) */ -#define ETR3_OFFSET 0x1048 -#define ETR3_CF9GR BIT(20) -#define ETR3_CF9LOCK BIT(31) - -/* Extended Test Mode Register LPM bits (TGL and later */ -#define ETR3_CLEAR_LPM_EVENTS BIT(28) - -const char *pmc_lpm_modes[] = { - "S0i2.0", - "S0i2.1", - "S0i2.2", - "S0i3.0", - "S0i3.1", - "S0i3.2", - "S0i3.3", - "S0i3.4", - NULL -}; - -struct pmc_bit_map { - const char *name; - u32 bit_mask; -}; - -/** - * struct pmc_reg_map - Structure used to define parameter unique to a - PCH family - * @pfear_sts: Maps name of IP block to PPFEAR* bit - * @mphy_sts: Maps name of MPHY lane to MPHY status lane status bit - * @pll_sts: Maps name of PLL to corresponding bit status - * @slps0_dbg_maps: Array of SLP_S0_DBG* registers containing debug info - * @ltr_show_sts: Maps PCH IP Names to their MMIO register offsets - * @slp_s0_offset: PWRMBASE offset to read SLP_S0 residency - * @ltr_ignore_offset: PWRMBASE offset to read/write LTR ignore bit - * @regmap_length: Length of memory to map from PWRMBASE address to access - * @ppfear0_offset: PWRMBASE offset to to read PPFEAR* - * @ppfear_buckets: Number of 8 bits blocks to read all IP blocks from - * PPFEAR - * @pm_cfg_offset: PWRMBASE offset to PM_CFG register - * @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE - * @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG* - * - * Each PCH has unique set of register offsets and bit indexes. This structure - * captures them to have a common implementation. - */ -struct pmc_reg_map { - const struct pmc_bit_map **pfear_sts; - const struct pmc_bit_map *mphy_sts; - const struct pmc_bit_map *pll_sts; - const struct pmc_bit_map **slps0_dbg_maps; - const struct pmc_bit_map *ltr_show_sts; - const struct pmc_bit_map *msr_sts; - const struct pmc_bit_map **lpm_sts; - const u32 slp_s0_offset; - const int slp_s0_res_counter_step; - const u32 ltr_ignore_offset; - const int regmap_length; - const u32 ppfear0_offset; - const int ppfear_buckets; - const u32 pm_cfg_offset; - const int pm_read_disable_bit; - const u32 slps0_dbg_offset; - const u32 ltr_ignore_max; - const u32 pm_vric1_offset; - /* Low Power Mode registers */ - const int lpm_num_maps; - const int lpm_res_counter_step_x2; - const u32 lpm_sts_latch_en_offset; - const u32 lpm_en_offset; - const u32 lpm_priority_offset; - const u32 lpm_residency_offset; - const u32 lpm_status_offset; - const u32 lpm_live_status_offset; - const u32 etr3_offset; -}; - -/** - * struct pmc_dev - pmc device structure - * @base_addr: contains pmc base address - * @regbase: pointer to io-remapped memory location - * @map: pointer to pmc_reg_map struct that contains platform - * specific attributes - * @dbgfs_dir: path to debugfs interface - * @pmc_xram_read_bit: flag to indicate whether PMC XRAM shadow registers - * used to read MPHY PG and PLL status are available - * @mutex_lock: mutex to complete one transcation - * @check_counters: On resume, check if counters are getting incremented - * @pc10_counter: PC10 residency counter - * @s0ix_counter: S0ix residency (step adjusted) - * @num_lpm_modes: Count of enabled modes - * @lpm_en_modes: Array of enabled modes from lowest to highest priority - * @lpm_req_regs: List of substate requirements - * - * pmc_dev contains info about power management controller device. - */ -struct pmc_dev { - u32 base_addr; - void __iomem *regbase; - const struct pmc_reg_map *map; - struct dentry *dbgfs_dir; - int pmc_xram_read_bit; - struct mutex lock; /* generic mutex lock for PMC Core */ - - bool check_counters; /* Check for counter increments on resume */ - u64 pc10_counter; - u64 s0ix_counter; - int num_lpm_modes; - int lpm_en_modes[LPM_MAX_NUM_MODES]; - u32 *lpm_req_regs; -}; - -#define pmc_for_each_mode(i, mode, pmcdev) \ - for (i = 0, mode = pmcdev->lpm_en_modes[i]; \ - i < pmcdev->num_lpm_modes; \ - i++, mode = pmcdev->lpm_en_modes[i]) - -#define DEFINE_PMC_CORE_ATTR_WRITE(__name) \ -static int __name ## _open(struct inode *inode, struct file *file) \ -{ \ - return single_open(file, __name ## _show, inode->i_private); \ -} \ - \ -static const struct file_operations __name ## _fops = { \ - .owner = THIS_MODULE, \ - .open = __name ## _open, \ - .read = seq_read, \ - .write = __name ## _write, \ - .release = single_release, \ -} - -#endif /* PMC_CORE_H */ diff --git a/drivers/platform/x86/intel_pmc_core_pltdrv.c b/drivers/platform/x86/intel_pmc_core_pltdrv.c deleted file mode 100644 index 73797680b895..000000000000 --- a/drivers/platform/x86/intel_pmc_core_pltdrv.c +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/* - * Intel PMC Core platform init - * Copyright (c) 2019, Google Inc. - * Author - Rajat Jain - * - * This code instantiates platform devices for intel_pmc_core driver, only - * on supported platforms that may not have the ACPI devices in the ACPI tables. - * No new platforms should be added here, because we expect that new platforms - * should all have the ACPI device, which is the preferred way of enumeration. - */ - -#include -#include -#include - -#include -#include - -static void intel_pmc_core_release(struct device *dev) -{ - kfree(dev); -} - -static struct platform_device *pmc_core_device; - -/* - * intel_pmc_core_platform_ids is the list of platforms where we want to - * instantiate the platform_device if not already instantiated. This is - * different than intel_pmc_core_ids in intel_pmc_core.c which is the - * list of platforms that the driver supports for pmc_core device. The - * other list may grow, but this list should not. - */ -static const struct x86_cpu_id intel_pmc_core_platform_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, &pmc_core_device), - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, &pmc_core_device), - X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, &pmc_core_device), - X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, &pmc_core_device), - X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, &pmc_core_device), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, &pmc_core_device), - X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, &pmc_core_device), - X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, &pmc_core_device), - {} -}; -MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_platform_ids); - -static int __init pmc_core_platform_init(void) -{ - int retval; - - /* Skip creating the platform device if ACPI already has a device */ - if (acpi_dev_present("INT33A1", NULL, -1)) - return -ENODEV; - - if (!x86_match_cpu(intel_pmc_core_platform_ids)) - return -ENODEV; - - pmc_core_device = kzalloc(sizeof(*pmc_core_device), GFP_KERNEL); - if (!pmc_core_device) - return -ENOMEM; - - pmc_core_device->name = "intel_pmc_core"; - pmc_core_device->dev.release = intel_pmc_core_release; - - retval = platform_device_register(pmc_core_device); - if (retval) - kfree(pmc_core_device); - - return retval; -} - -static void __exit pmc_core_platform_exit(void) -{ - platform_device_unregister(pmc_core_device); -} - -module_init(pmc_core_platform_init); -module_exit(pmc_core_platform_exit); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 2b6cb8f2e88b416393d2b34cad51bfe6e1aae8a7 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:46 +0300 Subject: platform/x86: intel_telemetry: Move to intel sub-directory Move Intel telemetry driver to intel sub-directory to improve readability. While at it, spell APL fully in the Kconfig. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Reviewed-by: Rajneesh Bhardwaj Link: https://lore.kernel.org/r/20210820110458.73018-9-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 12 - drivers/platform/x86/Makefile | 3 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/telemetry/Kconfig | 16 + drivers/platform/x86/intel/telemetry/Makefile | 11 + drivers/platform/x86/intel/telemetry/core.c | 450 +++++++++ drivers/platform/x86/intel/telemetry/debugfs.c | 961 +++++++++++++++++++ drivers/platform/x86/intel/telemetry/pltdrv.c | 1189 ++++++++++++++++++++++++ drivers/platform/x86/intel_telemetry_core.c | 450 --------- drivers/platform/x86/intel_telemetry_debugfs.c | 961 ------------------- drivers/platform/x86/intel_telemetry_pltdrv.c | 1189 ------------------------ 13 files changed, 2630 insertions(+), 2616 deletions(-) create mode 100644 drivers/platform/x86/intel/telemetry/Kconfig create mode 100644 drivers/platform/x86/intel/telemetry/Makefile create mode 100644 drivers/platform/x86/intel/telemetry/core.c create mode 100644 drivers/platform/x86/intel/telemetry/debugfs.c create mode 100644 drivers/platform/x86/intel/telemetry/pltdrv.c delete mode 100644 drivers/platform/x86/intel_telemetry_core.c delete mode 100644 drivers/platform/x86/intel_telemetry_debugfs.c delete mode 100644 drivers/platform/x86/intel_telemetry_pltdrv.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index eefe4edb1b3d..bb0fcf309cc8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9559,7 +9559,7 @@ M: "David E. Box" L: platform-driver-x86@vger.kernel.org S: Maintained F: arch/x86/include/asm/intel_telemetry.h -F: drivers/platform/x86/intel_telemetry* +F: drivers/platform/x86/intel/telemetry/ INTEL UNCORE FREQUENCY CONTROL M: Srinivas Pandruvada diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index aed7570388ef..4e42ebac8892 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1206,18 +1206,6 @@ config INTEL_SCU_IPC_UTIL low level access for debug work and updating the firmware. Say N unless you will be doing this on an Intel MID platform. -config INTEL_TELEMETRY - tristate "Intel SoC Telemetry Driver" - depends on X86_64 - depends on MFD_INTEL_PMC_BXT - depends on INTEL_PUNIT_IPC - help - This driver provides interfaces to configure and use - telemetry for INTEL SoC from APL onwards. It is also - used to get various SoC events and parameters - directly via debugfs files. Various tools may use - this interface for SoC state monitoring. - endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 1668f7360833..bde20c13ceac 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -132,7 +132,4 @@ obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o obj-$(CONFIG_INTEL_SCU_WDT) += intel_scu_wdt.o obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o -obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ - intel_telemetry_pltdrv.o \ - intel_telemetry_debugfs.o obj-$(CONFIG_PMC_ATOM) += pmc_atom.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 7de11636904d..379d9e425cc7 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -21,6 +21,7 @@ source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" source "drivers/platform/x86/intel/pmc/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" +source "drivers/platform/x86/intel/telemetry/Kconfig" config INTEL_BXTWC_PMIC_TMU tristate "Intel Broxton Whiskey Cove TMU Driver" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index a1555a1e421d..e8c2b1249f87 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMC_CORE) += pmc/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ +obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ # Intel PMIC / PMC / P-Unit drivers intel_bxtwc_tmu-y := bxtwc_tmu.o diff --git a/drivers/platform/x86/intel/telemetry/Kconfig b/drivers/platform/x86/intel/telemetry/Kconfig new file mode 100644 index 000000000000..da887bd03731 --- /dev/null +++ b/drivers/platform/x86/intel/telemetry/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Intel x86 Platform Specific Drivers +# + +config INTEL_TELEMETRY + tristate "Intel SoC Telemetry driver" + depends on X86_64 + depends on MFD_INTEL_PMC_BXT + depends on INTEL_PUNIT_IPC + help + This driver provides interfaces to configure and use + telemetry for Intel SoC from Apollo Lake onwards. + It is also used to get various SoC events and parameters + directly via debugfs files. Various tools may use + this interface for SoC state monitoring. diff --git a/drivers/platform/x86/intel/telemetry/Makefile b/drivers/platform/x86/intel/telemetry/Makefile new file mode 100644 index 000000000000..bfdba5b6c59a --- /dev/null +++ b/drivers/platform/x86/intel/telemetry/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Intel x86 Platform Specific Drivers +# + +intel_telemetry_core-y := core.o +obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o +intel_telemetry_pltdrv-y := pltdrv.o +obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_pltdrv.o +intel_telemetry_debugfs-y := debugfs.o +obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_debugfs.o diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c new file mode 100644 index 000000000000..fdf55b5d6948 --- /dev/null +++ b/drivers/platform/x86/intel/telemetry/core.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel SoC Core Telemetry Driver + * Copyright (C) 2015, Intel Corporation. + * All Rights Reserved. + * + * Telemetry Framework provides platform related PM and performance statistics. + * This file provides the core telemetry API implementation. + */ +#include +#include + +#include + +#define DRIVER_NAME "intel_telemetry_core" + +struct telemetry_core_config { + struct telemetry_plt_config *plt_config; + const struct telemetry_core_ops *telem_ops; +}; + +static struct telemetry_core_config telm_core_conf; + +static int telemetry_def_update_events(struct telemetry_evtconfig pss_evtconfig, + struct telemetry_evtconfig ioss_evtconfig) +{ + return 0; +} + +static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period) +{ + return 0; +} + +static int telemetry_def_get_sampling_period(u8 *pss_min_period, + u8 *pss_max_period, + u8 *ioss_min_period, + u8 *ioss_max_period) +{ + return 0; +} + +static int telemetry_def_get_eventconfig( + struct telemetry_evtconfig *pss_evtconfig, + struct telemetry_evtconfig *ioss_evtconfig, + int pss_len, int ioss_len) +{ + return 0; +} + +static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit, + u32 *verbosity) +{ + return 0; +} + + +static int telemetry_def_set_trace_verbosity(enum telemetry_unit telem_unit, + u32 verbosity) +{ + return 0; +} + +static int telemetry_def_raw_read_eventlog(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, + int len, int log_all_evts) +{ + return 0; +} + +static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, + int len, int log_all_evts) +{ + return 0; +} + +static int telemetry_def_add_events(u8 num_pss_evts, u8 num_ioss_evts, + u32 *pss_evtmap, u32 *ioss_evtmap) +{ + return 0; +} + +static int telemetry_def_reset_events(void) +{ + return 0; +} + +static const struct telemetry_core_ops telm_defpltops = { + .set_sampling_period = telemetry_def_set_sampling_period, + .get_sampling_period = telemetry_def_get_sampling_period, + .get_trace_verbosity = telemetry_def_get_trace_verbosity, + .set_trace_verbosity = telemetry_def_set_trace_verbosity, + .raw_read_eventlog = telemetry_def_raw_read_eventlog, + .get_eventconfig = telemetry_def_get_eventconfig, + .read_eventlog = telemetry_def_read_eventlog, + .update_events = telemetry_def_update_events, + .reset_events = telemetry_def_reset_events, + .add_events = telemetry_def_add_events, +}; + +/** + * telemetry_update_events() - Update telemetry Configuration + * @pss_evtconfig: PSS related config. No change if num_evts = 0. + * @pss_evtconfig: IOSS related config. No change if num_evts = 0. + * + * This API updates the IOSS & PSS Telemetry configuration. Old config + * is overwritten. Call telemetry_reset_events when logging is over + * All sample period values should be in the form of: + * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) + * + * Return: 0 success, < 0 for failure + */ +int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig, + struct telemetry_evtconfig ioss_evtconfig) +{ + return telm_core_conf.telem_ops->update_events(pss_evtconfig, + ioss_evtconfig); +} +EXPORT_SYMBOL_GPL(telemetry_update_events); + + +/** + * telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period + * @pss_period: placeholder for PSS Period to be set. + * Set to 0 if not required to be updated + * @ioss_period: placeholder for IOSS Period to be set + * Set to 0 if not required to be updated + * + * All values should be in the form of: + * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) + * + * Return: 0 success, < 0 for failure + */ +int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period) +{ + return telm_core_conf.telem_ops->set_sampling_period(pss_period, + ioss_period); +} +EXPORT_SYMBOL_GPL(telemetry_set_sampling_period); + +/** + * telemetry_get_sampling_period() - Get IOSS & PSS min & max sampling period + * @pss_min_period: placeholder for PSS Min Period supported + * @pss_max_period: placeholder for PSS Max Period supported + * @ioss_min_period: placeholder for IOSS Min Period supported + * @ioss_max_period: placeholder for IOSS Max Period supported + * + * All values should be in the form of: + * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) + * + * Return: 0 success, < 0 for failure + */ +int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period, + u8 *ioss_min_period, u8 *ioss_max_period) +{ + return telm_core_conf.telem_ops->get_sampling_period(pss_min_period, + pss_max_period, + ioss_min_period, + ioss_max_period); +} +EXPORT_SYMBOL_GPL(telemetry_get_sampling_period); + + +/** + * telemetry_reset_events() - Restore the IOSS & PSS configuration to default + * + * Return: 0 success, < 0 for failure + */ +int telemetry_reset_events(void) +{ + return telm_core_conf.telem_ops->reset_events(); +} +EXPORT_SYMBOL_GPL(telemetry_reset_events); + +/** + * telemetry_get_eventconfig() - Returns the pss and ioss events enabled + * @pss_evtconfig: Pointer to PSS related configuration. + * @pss_evtconfig: Pointer to IOSS related configuration. + * @pss_len: Number of u32 elements allocated for pss_evtconfig array + * @ioss_len: Number of u32 elements allocated for ioss_evtconfig array + * + * Return: 0 success, < 0 for failure + */ +int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_evtconfig, + struct telemetry_evtconfig *ioss_evtconfig, + int pss_len, int ioss_len) +{ + return telm_core_conf.telem_ops->get_eventconfig(pss_evtconfig, + ioss_evtconfig, + pss_len, ioss_len); +} +EXPORT_SYMBOL_GPL(telemetry_get_eventconfig); + +/** + * telemetry_add_events() - Add IOSS & PSS configuration to existing settings. + * @num_pss_evts: Number of PSS Events (<29) in pss_evtmap. Can be 0. + * @num_ioss_evts: Number of IOSS Events (<29) in ioss_evtmap. Can be 0. + * @pss_evtmap: Array of PSS Event-IDs to Enable + * @ioss_evtmap: Array of PSS Event-IDs to Enable + * + * Events are appended to Old Configuration. In case of total events > 28, it + * returns error. Call telemetry_reset_events to reset after eventlog done + * + * Return: 0 success, < 0 for failure + */ +int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts, + u32 *pss_evtmap, u32 *ioss_evtmap) +{ + return telm_core_conf.telem_ops->add_events(num_pss_evts, + num_ioss_evts, pss_evtmap, + ioss_evtmap); +} +EXPORT_SYMBOL_GPL(telemetry_add_events); + +/** + * telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id + * @telem_unit: Specify whether IOSS or PSS Read + * @evtlog: Array of telemetry_evtlog structs to fill data + * evtlog.telem_evt_id specifies the ids to read + * @len: Length of array of evtlog + * + * Return: number of eventlogs read for success, < 0 for failure + */ +int telemetry_read_events(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, int len) +{ + return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog, + len, 0); +} +EXPORT_SYMBOL_GPL(telemetry_read_events); + +/** + * telemetry_raw_read_events() - Fetch samples specified by evtlog.telem_evt_id + * @telem_unit: Specify whether IOSS or PSS Read + * @evtlog: Array of telemetry_evtlog structs to fill data + * evtlog.telem_evt_id specifies the ids to read + * @len: Length of array of evtlog + * + * The caller must take care of locking in this case. + * + * Return: number of eventlogs read for success, < 0 for failure + */ +int telemetry_raw_read_events(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, int len) +{ + return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog, + len, 0); +} +EXPORT_SYMBOL_GPL(telemetry_raw_read_events); + +/** + * telemetry_read_eventlog() - Fetch the Telemetry log from PSS or IOSS + * @telem_unit: Specify whether IOSS or PSS Read + * @evtlog: Array of telemetry_evtlog structs to fill data + * @len: Length of array of evtlog + * + * Return: number of eventlogs read for success, < 0 for failure + */ +int telemetry_read_eventlog(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, int len) +{ + return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog, + len, 1); +} +EXPORT_SYMBOL_GPL(telemetry_read_eventlog); + +/** + * telemetry_raw_read_eventlog() - Fetch the Telemetry log from PSS or IOSS + * @telem_unit: Specify whether IOSS or PSS Read + * @evtlog: Array of telemetry_evtlog structs to fill data + * @len: Length of array of evtlog + * + * The caller must take care of locking in this case. + * + * Return: number of eventlogs read for success, < 0 for failure + */ +int telemetry_raw_read_eventlog(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, int len) +{ + return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog, + len, 1); +} +EXPORT_SYMBOL_GPL(telemetry_raw_read_eventlog); + + +/** + * telemetry_get_trace_verbosity() - Get the IOSS & PSS Trace verbosity + * @telem_unit: Specify whether IOSS or PSS Read + * @verbosity: Pointer to return Verbosity + * + * Return: 0 success, < 0 for failure + */ +int telemetry_get_trace_verbosity(enum telemetry_unit telem_unit, + u32 *verbosity) +{ + return telm_core_conf.telem_ops->get_trace_verbosity(telem_unit, + verbosity); +} +EXPORT_SYMBOL_GPL(telemetry_get_trace_verbosity); + + +/** + * telemetry_set_trace_verbosity() - Update the IOSS & PSS Trace verbosity + * @telem_unit: Specify whether IOSS or PSS Read + * @verbosity: Verbosity to set + * + * Return: 0 success, < 0 for failure + */ +int telemetry_set_trace_verbosity(enum telemetry_unit telem_unit, u32 verbosity) +{ + return telm_core_conf.telem_ops->set_trace_verbosity(telem_unit, + verbosity); +} +EXPORT_SYMBOL_GPL(telemetry_set_trace_verbosity); + +/** + * telemetry_set_pltdata() - Set the platform specific Data + * @ops: Pointer to ops structure + * @pltconfig: Platform config data + * + * Usage by other than telemetry pltdrv module is invalid + * + * Return: 0 success, < 0 for failure + */ +int telemetry_set_pltdata(const struct telemetry_core_ops *ops, + struct telemetry_plt_config *pltconfig) +{ + if (ops) + telm_core_conf.telem_ops = ops; + + if (pltconfig) + telm_core_conf.plt_config = pltconfig; + + return 0; +} +EXPORT_SYMBOL_GPL(telemetry_set_pltdata); + +/** + * telemetry_clear_pltdata() - Clear the platform specific Data + * + * Usage by other than telemetry pltdrv module is invalid + * + * Return: 0 success, < 0 for failure + */ +int telemetry_clear_pltdata(void) +{ + telm_core_conf.telem_ops = &telm_defpltops; + telm_core_conf.plt_config = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(telemetry_clear_pltdata); + +/** + * telemetry_get_pltdata() - Return telemetry platform config + * + * May be used by other telemetry modules to get platform specific + * configuration. + */ +struct telemetry_plt_config *telemetry_get_pltdata(void) +{ + return telm_core_conf.plt_config; +} +EXPORT_SYMBOL_GPL(telemetry_get_pltdata); + +static inline int telemetry_get_pssevtname(enum telemetry_unit telem_unit, + const char **name, int len) +{ + struct telemetry_unit_config psscfg; + int i; + + if (!telm_core_conf.plt_config) + return -EINVAL; + + psscfg = telm_core_conf.plt_config->pss_config; + + if (len > psscfg.ssram_evts_used) + len = psscfg.ssram_evts_used; + + for (i = 0; i < len; i++) + name[i] = psscfg.telem_evts[i].name; + + return 0; +} + +static inline int telemetry_get_iossevtname(enum telemetry_unit telem_unit, + const char **name, int len) +{ + struct telemetry_unit_config iosscfg; + int i; + + if (!(telm_core_conf.plt_config)) + return -EINVAL; + + iosscfg = telm_core_conf.plt_config->ioss_config; + + if (len > iosscfg.ssram_evts_used) + len = iosscfg.ssram_evts_used; + + for (i = 0; i < len; i++) + name[i] = iosscfg.telem_evts[i].name; + + return 0; + +} + +/** + * telemetry_get_evtname() - Checkif platform config is valid + * @telem_unit: Telemetry Unit to check + * @name: Array of character pointers to contain name + * @len: length of array name provided by user + * + * Usage by other than telemetry debugfs module is invalid + * + * Return: 0 success, < 0 for failure + */ +int telemetry_get_evtname(enum telemetry_unit telem_unit, + const char **name, int len) +{ + int ret = -EINVAL; + + if (telem_unit == TELEM_PSS) + ret = telemetry_get_pssevtname(telem_unit, name, len); + + else if (telem_unit == TELEM_IOSS) + ret = telemetry_get_iossevtname(telem_unit, name, len); + + return ret; +} +EXPORT_SYMBOL_GPL(telemetry_get_evtname); + +static int __init telemetry_module_init(void) +{ + pr_info(pr_fmt(DRIVER_NAME) " Init\n"); + + telm_core_conf.telem_ops = &telm_defpltops; + return 0; +} + +static void __exit telemetry_module_exit(void) +{ +} + +module_init(telemetry_module_init); +module_exit(telemetry_module_exit); + +MODULE_AUTHOR("Souvik Kumar Chakravarty "); +MODULE_DESCRIPTION("Intel SoC Telemetry Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/telemetry/debugfs.c b/drivers/platform/x86/intel/telemetry/debugfs.c new file mode 100644 index 000000000000..1d4d0fbfd63c --- /dev/null +++ b/drivers/platform/x86/intel/telemetry/debugfs.c @@ -0,0 +1,961 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel SOC Telemetry debugfs Driver: Currently supports APL + * Copyright (c) 2015, Intel Corporation. + * All Rights Reserved. + * + * This file provides the debugfs interfaces for telemetry. + * /sys/kernel/debug/telemetry/pss_info: Shows Primary Control Sub-Sys Counters + * /sys/kernel/debug/telemetry/ioss_info: Shows IO Sub-System Counters + * /sys/kernel/debug/telemetry/soc_states: Shows SoC State + * /sys/kernel/debug/telemetry/pss_trace_verbosity: Read and Change Tracing + * Verbosity via firmware + * /sys/kernel/debug/telemetry/ioss_race_verbosity: Write and Change Tracing + * Verbosity via firmware + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRIVER_NAME "telemetry_soc_debugfs" +#define DRIVER_VERSION "1.0.0" + +/* ApolloLake SoC Event-IDs */ +#define TELEM_APL_PSS_PSTATES_ID 0x2802 +#define TELEM_APL_PSS_IDLE_ID 0x2806 +#define TELEM_APL_PCS_IDLE_BLOCKED_ID 0x2C00 +#define TELEM_APL_PCS_S0IX_BLOCKED_ID 0x2C01 +#define TELEM_APL_PSS_WAKEUP_ID 0x2C02 +#define TELEM_APL_PSS_LTR_BLOCKING_ID 0x2C03 + +#define TELEM_APL_S0IX_TOTAL_OCC_ID 0x4000 +#define TELEM_APL_S0IX_SHLW_OCC_ID 0x4001 +#define TELEM_APL_S0IX_DEEP_OCC_ID 0x4002 +#define TELEM_APL_S0IX_TOTAL_RES_ID 0x4800 +#define TELEM_APL_S0IX_SHLW_RES_ID 0x4801 +#define TELEM_APL_S0IX_DEEP_RES_ID 0x4802 +#define TELEM_APL_D0IX_ID 0x581A +#define TELEM_APL_D3_ID 0x5819 +#define TELEM_APL_PG_ID 0x5818 + +#define TELEM_INFO_SRAMEVTS_MASK 0xFF00 +#define TELEM_INFO_SRAMEVTS_SHIFT 0x8 +#define TELEM_SSRAM_READ_TIMEOUT 10 + +#define TELEM_MASK_BIT 1 +#define TELEM_MASK_BYTE 0xFF +#define BYTES_PER_LONG 8 +#define TELEM_APL_MASK_PCS_STATE 0xF + +/* Max events in bitmap to check for */ +#define TELEM_PSS_IDLE_EVTS 25 +#define TELEM_PSS_IDLE_BLOCKED_EVTS 20 +#define TELEM_PSS_S0IX_BLOCKED_EVTS 20 +#define TELEM_PSS_S0IX_WAKEUP_EVTS 20 +#define TELEM_PSS_LTR_BLOCKING_EVTS 20 +#define TELEM_IOSS_DX_D0IX_EVTS 25 +#define TELEM_IOSS_PG_EVTS 30 + +#define TELEM_CHECK_AND_PARSE_EVTS(EVTID, EVTNUM, BUF, EVTLOG, EVTDAT, MASK) { \ + if (evtlog[index].telem_evtid == (EVTID)) { \ + for (idx = 0; idx < (EVTNUM); idx++) \ + (BUF)[idx] = ((EVTLOG) >> (EVTDAT)[idx].bit_pos) & \ + (MASK); \ + continue; \ + } \ +} + +#define TELEM_CHECK_AND_PARSE_CTRS(EVTID, CTR) { \ + if (evtlog[index].telem_evtid == (EVTID)) { \ + (CTR) = evtlog[index].telem_evtlog; \ + continue; \ + } \ +} + +static u8 suspend_prep_ok; +static u32 suspend_shlw_ctr_temp, suspend_deep_ctr_temp; +static u64 suspend_shlw_res_temp, suspend_deep_res_temp; + +struct telemetry_susp_stats { + u32 shlw_ctr; + u32 deep_ctr; + u64 shlw_res; + u64 deep_res; +}; + +/* Bitmap definitions for default counters in APL */ +struct telem_pss_idle_stateinfo { + const char *name; + u32 bit_pos; +}; + +static struct telem_pss_idle_stateinfo telem_apl_pss_idle_data[] = { + {"IA_CORE0_C1E", 0}, + {"IA_CORE1_C1E", 1}, + {"IA_CORE2_C1E", 2}, + {"IA_CORE3_C1E", 3}, + {"IA_CORE0_C6", 16}, + {"IA_CORE1_C6", 17}, + {"IA_CORE2_C6", 18}, + {"IA_CORE3_C6", 19}, + {"IA_MODULE0_C7", 32}, + {"IA_MODULE1_C7", 33}, + {"GT_RC6", 40}, + {"IUNIT_PROCESSING_IDLE", 41}, + {"FAR_MEM_IDLE", 43}, + {"DISPLAY_IDLE", 44}, + {"IUNIT_INPUT_SYSTEM_IDLE", 45}, + {"PCS_STATUS", 60}, +}; + +struct telem_pcs_blkd_info { + const char *name; + u32 bit_pos; +}; + +static struct telem_pcs_blkd_info telem_apl_pcs_idle_blkd_data[] = { + {"COMPUTE", 0}, + {"MISC", 8}, + {"MODULE_ACTIONS_PENDING", 16}, + {"LTR", 24}, + {"DISPLAY_WAKE", 32}, + {"ISP_WAKE", 40}, + {"PSF0_ACTIVE", 48}, +}; + +static struct telem_pcs_blkd_info telem_apl_pcs_s0ix_blkd_data[] = { + {"LTR", 0}, + {"IRTL", 8}, + {"WAKE_DEADLINE_PENDING", 16}, + {"DISPLAY", 24}, + {"ISP", 32}, + {"CORE", 40}, + {"PMC", 48}, + {"MISC", 56}, +}; + +struct telem_pss_ltr_info { + const char *name; + u32 bit_pos; +}; + +static struct telem_pss_ltr_info telem_apl_pss_ltr_data[] = { + {"CORE_ACTIVE", 0}, + {"MEM_UP", 8}, + {"DFX", 16}, + {"DFX_FORCE_LTR", 24}, + {"DISPLAY", 32}, + {"ISP", 40}, + {"SOUTH", 48}, +}; + +struct telem_pss_wakeup_info { + const char *name; + u32 bit_pos; +}; + +static struct telem_pss_wakeup_info telem_apl_pss_wakeup[] = { + {"IP_IDLE", 0}, + {"DISPLAY_WAKE", 8}, + {"VOLTAGE_REG_INT", 16}, + {"DROWSY_TIMER (HOTPLUG)", 24}, + {"CORE_WAKE", 32}, + {"MISC_S0IX", 40}, + {"MISC_ABORT", 56}, +}; + +struct telem_ioss_d0ix_stateinfo { + const char *name; + u32 bit_pos; +}; + +static struct telem_ioss_d0ix_stateinfo telem_apl_ioss_d0ix_data[] = { + {"CSE", 0}, + {"SCC2", 1}, + {"GMM", 2}, + {"XDCI", 3}, + {"XHCI", 4}, + {"ISH", 5}, + {"AVS", 6}, + {"PCIE0P1", 7}, + {"PECI0P0", 8}, + {"LPSS", 9}, + {"SCC", 10}, + {"PWM", 11}, + {"PCIE1_P3", 12}, + {"PCIE1_P2", 13}, + {"PCIE1_P1", 14}, + {"PCIE1_P0", 15}, + {"CNV", 16}, + {"SATA", 17}, + {"PRTC", 18}, +}; + +struct telem_ioss_pg_info { + const char *name; + u32 bit_pos; +}; + +static struct telem_ioss_pg_info telem_apl_ioss_pg_data[] = { + {"LPSS", 0}, + {"SCC", 1}, + {"P2SB", 2}, + {"SCC2", 3}, + {"GMM", 4}, + {"PCIE0", 5}, + {"XDCI", 6}, + {"xHCI", 7}, + {"CSE", 8}, + {"SPI", 9}, + {"AVSPGD4", 10}, + {"AVSPGD3", 11}, + {"AVSPGD2", 12}, + {"AVSPGD1", 13}, + {"ISH", 14}, + {"EXI", 15}, + {"NPKVRC", 16}, + {"NPKVNN", 17}, + {"CUNIT", 18}, + {"FUSE_CTRL", 19}, + {"PCIE1", 20}, + {"CNV", 21}, + {"LPC", 22}, + {"SATA", 23}, + {"SMB", 24}, + {"PRTC", 25}, +}; + +struct telemetry_debugfs_conf { + struct telemetry_susp_stats suspend_stats; + struct dentry *telemetry_dbg_dir; + + /* Bitmap Data */ + struct telem_ioss_d0ix_stateinfo *ioss_d0ix_data; + struct telem_pss_idle_stateinfo *pss_idle_data; + struct telem_pcs_blkd_info *pcs_idle_blkd_data; + struct telem_pcs_blkd_info *pcs_s0ix_blkd_data; + struct telem_pss_wakeup_info *pss_wakeup; + struct telem_pss_ltr_info *pss_ltr_data; + struct telem_ioss_pg_info *ioss_pg_data; + u8 pcs_idle_blkd_evts; + u8 pcs_s0ix_blkd_evts; + u8 pss_wakeup_evts; + u8 pss_idle_evts; + u8 pss_ltr_evts; + u8 ioss_d0ix_evts; + u8 ioss_pg_evts; + + /* IDs */ + u16 pss_ltr_blocking_id; + u16 pcs_idle_blkd_id; + u16 pcs_s0ix_blkd_id; + u16 s0ix_total_occ_id; + u16 s0ix_shlw_occ_id; + u16 s0ix_deep_occ_id; + u16 s0ix_total_res_id; + u16 s0ix_shlw_res_id; + u16 s0ix_deep_res_id; + u16 pss_wakeup_id; + u16 ioss_d0ix_id; + u16 pstates_id; + u16 pss_idle_id; + u16 ioss_d3_id; + u16 ioss_pg_id; +}; + +static struct telemetry_debugfs_conf *debugfs_conf; + +static struct telemetry_debugfs_conf telem_apl_debugfs_conf = { + .pss_idle_data = telem_apl_pss_idle_data, + .pcs_idle_blkd_data = telem_apl_pcs_idle_blkd_data, + .pcs_s0ix_blkd_data = telem_apl_pcs_s0ix_blkd_data, + .pss_ltr_data = telem_apl_pss_ltr_data, + .pss_wakeup = telem_apl_pss_wakeup, + .ioss_d0ix_data = telem_apl_ioss_d0ix_data, + .ioss_pg_data = telem_apl_ioss_pg_data, + + .pss_idle_evts = ARRAY_SIZE(telem_apl_pss_idle_data), + .pcs_idle_blkd_evts = ARRAY_SIZE(telem_apl_pcs_idle_blkd_data), + .pcs_s0ix_blkd_evts = ARRAY_SIZE(telem_apl_pcs_s0ix_blkd_data), + .pss_ltr_evts = ARRAY_SIZE(telem_apl_pss_ltr_data), + .pss_wakeup_evts = ARRAY_SIZE(telem_apl_pss_wakeup), + .ioss_d0ix_evts = ARRAY_SIZE(telem_apl_ioss_d0ix_data), + .ioss_pg_evts = ARRAY_SIZE(telem_apl_ioss_pg_data), + + .pstates_id = TELEM_APL_PSS_PSTATES_ID, + .pss_idle_id = TELEM_APL_PSS_IDLE_ID, + .pcs_idle_blkd_id = TELEM_APL_PCS_IDLE_BLOCKED_ID, + .pcs_s0ix_blkd_id = TELEM_APL_PCS_S0IX_BLOCKED_ID, + .pss_wakeup_id = TELEM_APL_PSS_WAKEUP_ID, + .pss_ltr_blocking_id = TELEM_APL_PSS_LTR_BLOCKING_ID, + .s0ix_total_occ_id = TELEM_APL_S0IX_TOTAL_OCC_ID, + .s0ix_shlw_occ_id = TELEM_APL_S0IX_SHLW_OCC_ID, + .s0ix_deep_occ_id = TELEM_APL_S0IX_DEEP_OCC_ID, + .s0ix_total_res_id = TELEM_APL_S0IX_TOTAL_RES_ID, + .s0ix_shlw_res_id = TELEM_APL_S0IX_SHLW_RES_ID, + .s0ix_deep_res_id = TELEM_APL_S0IX_DEEP_RES_ID, + .ioss_d0ix_id = TELEM_APL_D0IX_ID, + .ioss_d3_id = TELEM_APL_D3_ID, + .ioss_pg_id = TELEM_APL_PG_ID, +}; + +static const struct x86_cpu_id telemetry_debugfs_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, &telem_apl_debugfs_conf), + X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, &telem_apl_debugfs_conf), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, telemetry_debugfs_cpu_ids); + +static int telemetry_debugfs_check_evts(void) +{ + if ((debugfs_conf->pss_idle_evts > TELEM_PSS_IDLE_EVTS) || + (debugfs_conf->pcs_idle_blkd_evts > TELEM_PSS_IDLE_BLOCKED_EVTS) || + (debugfs_conf->pcs_s0ix_blkd_evts > TELEM_PSS_S0IX_BLOCKED_EVTS) || + (debugfs_conf->pss_ltr_evts > TELEM_PSS_LTR_BLOCKING_EVTS) || + (debugfs_conf->pss_wakeup_evts > TELEM_PSS_S0IX_WAKEUP_EVTS) || + (debugfs_conf->ioss_d0ix_evts > TELEM_IOSS_DX_D0IX_EVTS) || + (debugfs_conf->ioss_pg_evts > TELEM_IOSS_PG_EVTS)) + return -EINVAL; + + return 0; +} + +static int telem_pss_states_show(struct seq_file *s, void *unused) +{ + struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; + struct telemetry_debugfs_conf *conf = debugfs_conf; + const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS]; + u32 pcs_idle_blkd[TELEM_PSS_IDLE_BLOCKED_EVTS], + pcs_s0ix_blkd[TELEM_PSS_S0IX_BLOCKED_EVTS], + pss_s0ix_wakeup[TELEM_PSS_S0IX_WAKEUP_EVTS], + pss_ltr_blkd[TELEM_PSS_LTR_BLOCKING_EVTS], + pss_idle[TELEM_PSS_IDLE_EVTS]; + int index, idx, ret, err = 0; + u64 pstates = 0; + + ret = telemetry_read_eventlog(TELEM_PSS, evtlog, + TELEM_MAX_OS_ALLOCATED_EVENTS); + if (ret < 0) + return ret; + + err = telemetry_get_evtname(TELEM_PSS, name, + TELEM_MAX_OS_ALLOCATED_EVENTS); + if (err < 0) + return err; + + seq_puts(s, "\n----------------------------------------------------\n"); + seq_puts(s, "\tPSS TELEM EVENTLOG (Residency = field/19.2 us\n"); + seq_puts(s, "----------------------------------------------------\n"); + for (index = 0; index < ret; index++) { + seq_printf(s, "%-32s %llu\n", + name[index], evtlog[index].telem_evtlog); + + /* Fetch PSS IDLE State */ + if (evtlog[index].telem_evtid == conf->pss_idle_id) { + pss_idle[conf->pss_idle_evts - 1] = + (evtlog[index].telem_evtlog >> + conf->pss_idle_data[conf->pss_idle_evts - 1].bit_pos) & + TELEM_APL_MASK_PCS_STATE; + } + + TELEM_CHECK_AND_PARSE_EVTS(conf->pss_idle_id, + conf->pss_idle_evts - 1, + pss_idle, evtlog[index].telem_evtlog, + conf->pss_idle_data, TELEM_MASK_BIT); + + TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_idle_blkd_id, + conf->pcs_idle_blkd_evts, + pcs_idle_blkd, + evtlog[index].telem_evtlog, + conf->pcs_idle_blkd_data, + TELEM_MASK_BYTE); + + TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_s0ix_blkd_id, + conf->pcs_s0ix_blkd_evts, + pcs_s0ix_blkd, + evtlog[index].telem_evtlog, + conf->pcs_s0ix_blkd_data, + TELEM_MASK_BYTE); + + TELEM_CHECK_AND_PARSE_EVTS(conf->pss_wakeup_id, + conf->pss_wakeup_evts, + pss_s0ix_wakeup, + evtlog[index].telem_evtlog, + conf->pss_wakeup, TELEM_MASK_BYTE); + + TELEM_CHECK_AND_PARSE_EVTS(conf->pss_ltr_blocking_id, + conf->pss_ltr_evts, pss_ltr_blkd, + evtlog[index].telem_evtlog, + conf->pss_ltr_data, TELEM_MASK_BYTE); + + if (evtlog[index].telem_evtid == debugfs_conf->pstates_id) + pstates = evtlog[index].telem_evtlog; + } + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "PStates\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Domain\t\t\t\tFreq(Mhz)\n"); + seq_printf(s, " IA\t\t\t\t %llu\n GT\t\t\t\t %llu\n", + (pstates & TELEM_MASK_BYTE)*100, + ((pstates >> 8) & TELEM_MASK_BYTE)*50/3); + + seq_printf(s, " IUNIT\t\t\t\t %llu\n SA\t\t\t\t %llu\n", + ((pstates >> 16) & TELEM_MASK_BYTE)*25, + ((pstates >> 24) & TELEM_MASK_BYTE)*50/3); + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "PSS IDLE Status\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Device\t\t\t\t\tIDLE\n"); + for (index = 0; index < debugfs_conf->pss_idle_evts; index++) { + seq_printf(s, "%-32s\t%u\n", + debugfs_conf->pss_idle_data[index].name, + pss_idle[index]); + } + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "PSS Idle blkd Status (~1ms saturating bucket)\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Blocker\t\t\t\t\tCount\n"); + for (index = 0; index < debugfs_conf->pcs_idle_blkd_evts; index++) { + seq_printf(s, "%-32s\t%u\n", + debugfs_conf->pcs_idle_blkd_data[index].name, + pcs_idle_blkd[index]); + } + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "PSS S0ix blkd Status (~1ms saturating bucket)\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Blocker\t\t\t\t\tCount\n"); + for (index = 0; index < debugfs_conf->pcs_s0ix_blkd_evts; index++) { + seq_printf(s, "%-32s\t%u\n", + debugfs_conf->pcs_s0ix_blkd_data[index].name, + pcs_s0ix_blkd[index]); + } + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "LTR Blocking Status (~1ms saturating bucket)\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Blocker\t\t\t\t\tCount\n"); + for (index = 0; index < debugfs_conf->pss_ltr_evts; index++) { + seq_printf(s, "%-32s\t%u\n", + debugfs_conf->pss_ltr_data[index].name, + pss_s0ix_wakeup[index]); + } + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "Wakes Status (~1ms saturating bucket)\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Wakes\t\t\t\t\tCount\n"); + for (index = 0; index < debugfs_conf->pss_wakeup_evts; index++) { + seq_printf(s, "%-32s\t%u\n", + debugfs_conf->pss_wakeup[index].name, + pss_ltr_blkd[index]); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(telem_pss_states); + +static int telem_ioss_states_show(struct seq_file *s, void *unused) +{ + struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; + const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS]; + int index, ret, err; + + ret = telemetry_read_eventlog(TELEM_IOSS, evtlog, + TELEM_MAX_OS_ALLOCATED_EVENTS); + if (ret < 0) + return ret; + + err = telemetry_get_evtname(TELEM_IOSS, name, + TELEM_MAX_OS_ALLOCATED_EVENTS); + if (err < 0) + return err; + + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "\tI0SS TELEMETRY EVENTLOG\n"); + seq_puts(s, "--------------------------------------\n"); + for (index = 0; index < ret; index++) { + seq_printf(s, "%-32s 0x%llx\n", + name[index], evtlog[index].telem_evtlog); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(telem_ioss_states); + +static int telem_soc_states_show(struct seq_file *s, void *unused) +{ + u32 d3_sts[TELEM_IOSS_DX_D0IX_EVTS], d0ix_sts[TELEM_IOSS_DX_D0IX_EVTS]; + u32 pg_sts[TELEM_IOSS_PG_EVTS], pss_idle[TELEM_PSS_IDLE_EVTS]; + struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; + u32 s0ix_total_ctr = 0, s0ix_shlw_ctr = 0, s0ix_deep_ctr = 0; + u64 s0ix_total_res = 0, s0ix_shlw_res = 0, s0ix_deep_res = 0; + struct telemetry_debugfs_conf *conf = debugfs_conf; + struct pci_dev *dev = NULL; + int index, idx, ret; + u32 d3_state; + u16 pmcsr; + + ret = telemetry_read_eventlog(TELEM_IOSS, evtlog, + TELEM_MAX_OS_ALLOCATED_EVENTS); + if (ret < 0) + return ret; + + for (index = 0; index < ret; index++) { + TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d3_id, + conf->ioss_d0ix_evts, + d3_sts, evtlog[index].telem_evtlog, + conf->ioss_d0ix_data, + TELEM_MASK_BIT); + + TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_pg_id, conf->ioss_pg_evts, + pg_sts, evtlog[index].telem_evtlog, + conf->ioss_pg_data, TELEM_MASK_BIT); + + TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d0ix_id, + conf->ioss_d0ix_evts, + d0ix_sts, evtlog[index].telem_evtlog, + conf->ioss_d0ix_data, + TELEM_MASK_BIT); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_occ_id, + s0ix_total_ctr); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id, + s0ix_shlw_ctr); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id, + s0ix_deep_ctr); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_res_id, + s0ix_total_res); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id, + s0ix_shlw_res); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id, + s0ix_deep_res); + } + + seq_puts(s, "\n---------------------------------------------------\n"); + seq_puts(s, "S0IX Type\t\t\t Occurrence\t\t Residency(us)\n"); + seq_puts(s, "---------------------------------------------------\n"); + + seq_printf(s, "S0IX Shallow\t\t\t %10u\t %10llu\n", + s0ix_shlw_ctr - + conf->suspend_stats.shlw_ctr, + (u64)((s0ix_shlw_res - + conf->suspend_stats.shlw_res)*10/192)); + + seq_printf(s, "S0IX Deep\t\t\t %10u\t %10llu\n", + s0ix_deep_ctr - + conf->suspend_stats.deep_ctr, + (u64)((s0ix_deep_res - + conf->suspend_stats.deep_res)*10/192)); + + seq_printf(s, "Suspend(With S0ixShallow)\t %10u\t %10llu\n", + conf->suspend_stats.shlw_ctr, + (u64)(conf->suspend_stats.shlw_res*10)/192); + + seq_printf(s, "Suspend(With S0ixDeep)\t\t %10u\t %10llu\n", + conf->suspend_stats.deep_ctr, + (u64)(conf->suspend_stats.deep_res*10)/192); + + seq_printf(s, "TOTAL S0IX\t\t\t %10u\t %10llu\n", s0ix_total_ctr, + (u64)(s0ix_total_res*10/192)); + seq_puts(s, "\n-------------------------------------------------\n"); + seq_puts(s, "\t\tDEVICE STATES\n"); + seq_puts(s, "-------------------------------------------------\n"); + + for_each_pci_dev(dev) { + pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); + d3_state = ((pmcsr & PCI_PM_CTRL_STATE_MASK) == + (__force int)PCI_D3hot) ? 1 : 0; + + seq_printf(s, "pci %04x %04X %s %20.20s: ", + dev->vendor, dev->device, dev_name(&dev->dev), + dev_driver_string(&dev->dev)); + seq_printf(s, " d3:%x\n", d3_state); + } + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "D3/D0i3 Status\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Block\t\t D3\t D0i3\n"); + for (index = 0; index < conf->ioss_d0ix_evts; index++) { + seq_printf(s, "%-10s\t %u\t %u\n", + conf->ioss_d0ix_data[index].name, + d3_sts[index], d0ix_sts[index]); + } + + seq_puts(s, "\n--------------------------------------\n"); + seq_puts(s, "South Complex PowerGate Status\n"); + seq_puts(s, "--------------------------------------\n"); + seq_puts(s, "Device\t\t PG\n"); + for (index = 0; index < conf->ioss_pg_evts; index++) { + seq_printf(s, "%-10s\t %u\n", + conf->ioss_pg_data[index].name, + pg_sts[index]); + } + + evtlog->telem_evtid = conf->pss_idle_id; + ret = telemetry_read_events(TELEM_PSS, evtlog, 1); + if (ret < 0) + return ret; + + seq_puts(s, "\n-----------------------------------------\n"); + seq_puts(s, "North Idle Status\n"); + seq_puts(s, "-----------------------------------------\n"); + for (idx = 0; idx < conf->pss_idle_evts - 1; idx++) { + pss_idle[idx] = (evtlog->telem_evtlog >> + conf->pss_idle_data[idx].bit_pos) & + TELEM_MASK_BIT; + } + + pss_idle[idx] = (evtlog->telem_evtlog >> + conf->pss_idle_data[idx].bit_pos) & + TELEM_APL_MASK_PCS_STATE; + + for (index = 0; index < conf->pss_idle_evts; index++) { + seq_printf(s, "%-30s %u\n", + conf->pss_idle_data[index].name, + pss_idle[index]); + } + + seq_puts(s, "\nPCS_STATUS Code\n"); + seq_puts(s, "0:C0 1:C1 2:C1_DN_WT_DEV 3:C2 4:C2_WT_DE_MEM_UP\n"); + seq_puts(s, "5:C2_WT_DE_MEM_DOWN 6:C2_UP_WT_DEV 7:C2_DN 8:C2_VOA\n"); + seq_puts(s, "9:C2_VOA_UP 10:S0IX_PRE 11:S0IX\n"); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(telem_soc_states); + +static int telem_s0ix_res_get(void *data, u64 *val) +{ + struct telemetry_plt_config *plt_config = telemetry_get_pltdata(); + u64 s0ix_total_res; + int ret; + + ret = intel_pmc_s0ix_counter_read(plt_config->pmc, &s0ix_total_res); + if (ret) { + pr_err("Failed to read S0ix residency"); + return ret; + } + + *val = s0ix_total_res; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(telem_s0ix_fops, telem_s0ix_res_get, NULL, "%llu\n"); + +static int telem_pss_trc_verb_show(struct seq_file *s, void *unused) +{ + u32 verbosity; + int err; + + err = telemetry_get_trace_verbosity(TELEM_PSS, &verbosity); + if (err) { + pr_err("Get PSS Trace Verbosity Failed with Error %d\n", err); + return -EFAULT; + } + + seq_printf(s, "PSS Trace Verbosity %u\n", verbosity); + return 0; +} + +static ssize_t telem_pss_trc_verb_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + u32 verbosity; + int err; + + err = kstrtou32_from_user(userbuf, count, 0, &verbosity); + if (err) + return err; + + err = telemetry_set_trace_verbosity(TELEM_PSS, verbosity); + if (err) { + pr_err("Changing PSS Trace Verbosity Failed. Error %d\n", err); + return err; + } + + return count; +} + +static int telem_pss_trc_verb_open(struct inode *inode, struct file *file) +{ + return single_open(file, telem_pss_trc_verb_show, inode->i_private); +} + +static const struct file_operations telem_pss_trc_verb_ops = { + .open = telem_pss_trc_verb_open, + .read = seq_read, + .write = telem_pss_trc_verb_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int telem_ioss_trc_verb_show(struct seq_file *s, void *unused) +{ + u32 verbosity; + int err; + + err = telemetry_get_trace_verbosity(TELEM_IOSS, &verbosity); + if (err) { + pr_err("Get IOSS Trace Verbosity Failed with Error %d\n", err); + return -EFAULT; + } + + seq_printf(s, "IOSS Trace Verbosity %u\n", verbosity); + return 0; +} + +static ssize_t telem_ioss_trc_verb_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + u32 verbosity; + int err; + + err = kstrtou32_from_user(userbuf, count, 0, &verbosity); + if (err) + return err; + + err = telemetry_set_trace_verbosity(TELEM_IOSS, verbosity); + if (err) { + pr_err("Changing IOSS Trace Verbosity Failed. Error %d\n", err); + return err; + } + + return count; +} + +static int telem_ioss_trc_verb_open(struct inode *inode, struct file *file) +{ + return single_open(file, telem_ioss_trc_verb_show, inode->i_private); +} + +static const struct file_operations telem_ioss_trc_verb_ops = { + .open = telem_ioss_trc_verb_open, + .read = seq_read, + .write = telem_ioss_trc_verb_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int pm_suspend_prep_cb(void) +{ + struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; + struct telemetry_debugfs_conf *conf = debugfs_conf; + int ret, index; + + ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog, + TELEM_MAX_OS_ALLOCATED_EVENTS); + if (ret < 0) { + suspend_prep_ok = 0; + goto out; + } + + for (index = 0; index < ret; index++) { + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id, + suspend_shlw_ctr_temp); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id, + suspend_deep_ctr_temp); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id, + suspend_shlw_res_temp); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id, + suspend_deep_res_temp); + } + suspend_prep_ok = 1; +out: + return NOTIFY_OK; +} + +static int pm_suspend_exit_cb(void) +{ + struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; + static u32 suspend_shlw_ctr_exit, suspend_deep_ctr_exit; + static u64 suspend_shlw_res_exit, suspend_deep_res_exit; + struct telemetry_debugfs_conf *conf = debugfs_conf; + int ret, index; + + if (!suspend_prep_ok) + goto out; + + ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog, + TELEM_MAX_OS_ALLOCATED_EVENTS); + if (ret < 0) + goto out; + + for (index = 0; index < ret; index++) { + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id, + suspend_shlw_ctr_exit); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id, + suspend_deep_ctr_exit); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id, + suspend_shlw_res_exit); + + TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id, + suspend_deep_res_exit); + } + + if ((suspend_shlw_ctr_exit < suspend_shlw_ctr_temp) || + (suspend_deep_ctr_exit < suspend_deep_ctr_temp) || + (suspend_shlw_res_exit < suspend_shlw_res_temp) || + (suspend_deep_res_exit < suspend_deep_res_temp)) { + pr_err("Wrong s0ix counters detected\n"); + goto out; + } + + /* + * Due to some design limitations in the firmware, sometimes the + * counters do not get updated by the time we reach here. As a + * workaround, we try to see if this was a genuine case of sleep + * failure or not by cross-checking from PMC GCR registers directly. + */ + if (suspend_shlw_ctr_exit == suspend_shlw_ctr_temp && + suspend_deep_ctr_exit == suspend_deep_ctr_temp) { + struct telemetry_plt_config *plt_config = telemetry_get_pltdata(); + struct intel_pmc_dev *pmc = plt_config->pmc; + + ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_SHLW_S0IX_REG, + &suspend_shlw_res_exit); + if (ret < 0) + goto out; + + ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_DEEP_S0IX_REG, + &suspend_deep_res_exit); + if (ret < 0) + goto out; + + if (suspend_shlw_res_exit > suspend_shlw_res_temp) + suspend_shlw_ctr_exit++; + + if (suspend_deep_res_exit > suspend_deep_res_temp) + suspend_deep_ctr_exit++; + } + + suspend_shlw_ctr_exit -= suspend_shlw_ctr_temp; + suspend_deep_ctr_exit -= suspend_deep_ctr_temp; + suspend_shlw_res_exit -= suspend_shlw_res_temp; + suspend_deep_res_exit -= suspend_deep_res_temp; + + if (suspend_shlw_ctr_exit != 0) { + conf->suspend_stats.shlw_ctr += + suspend_shlw_ctr_exit; + + conf->suspend_stats.shlw_res += + suspend_shlw_res_exit; + } + + if (suspend_deep_ctr_exit != 0) { + conf->suspend_stats.deep_ctr += + suspend_deep_ctr_exit; + + conf->suspend_stats.deep_res += + suspend_deep_res_exit; + } + +out: + suspend_prep_ok = 0; + return NOTIFY_OK; +} + +static int pm_notification(struct notifier_block *this, + unsigned long event, void *ptr) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + return pm_suspend_prep_cb(); + case PM_POST_SUSPEND: + return pm_suspend_exit_cb(); + } + + return NOTIFY_DONE; +} + +static struct notifier_block pm_notifier = { + .notifier_call = pm_notification, +}; + +static int __init telemetry_debugfs_init(void) +{ + const struct x86_cpu_id *id; + int err; + struct dentry *dir; + + /* Only APL supported for now */ + id = x86_match_cpu(telemetry_debugfs_cpu_ids); + if (!id) + return -ENODEV; + + debugfs_conf = (struct telemetry_debugfs_conf *)id->driver_data; + + if (!telemetry_get_pltdata()) { + pr_info("Invalid pltconfig, ensure IPC1 device is enabled in BIOS\n"); + return -ENODEV; + } + + err = telemetry_debugfs_check_evts(); + if (err < 0) { + pr_info("telemetry_debugfs_check_evts failed\n"); + return -EINVAL; + } + + register_pm_notifier(&pm_notifier); + + dir = debugfs_create_dir("telemetry", NULL); + debugfs_conf->telemetry_dbg_dir = dir; + + debugfs_create_file("pss_info", S_IFREG | S_IRUGO, dir, NULL, + &telem_pss_states_fops); + debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, dir, NULL, + &telem_ioss_states_fops); + debugfs_create_file("soc_states", S_IFREG | S_IRUGO, dir, NULL, + &telem_soc_states_fops); + debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, dir, NULL, + &telem_s0ix_fops); + debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, dir, NULL, + &telem_pss_trc_verb_ops); + debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, dir, + NULL, &telem_ioss_trc_verb_ops); + return 0; +} + +static void __exit telemetry_debugfs_exit(void) +{ + debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir); + debugfs_conf->telemetry_dbg_dir = NULL; + unregister_pm_notifier(&pm_notifier); +} + +late_initcall(telemetry_debugfs_init); +module_exit(telemetry_debugfs_exit); + +MODULE_AUTHOR("Souvik Kumar Chakravarty "); +MODULE_DESCRIPTION("Intel SoC Telemetry debugfs Interface"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/telemetry/pltdrv.c b/drivers/platform/x86/intel/telemetry/pltdrv.c new file mode 100644 index 000000000000..405dea87de6b --- /dev/null +++ b/drivers/platform/x86/intel/telemetry/pltdrv.c @@ -0,0 +1,1189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel SOC Telemetry Platform Driver: Currently supports APL + * Copyright (c) 2015, Intel Corporation. + * All Rights Reserved. + * + * This file provides the platform specific telemetry implementation for APL. + * It used the PUNIT and PMC IPC interfaces for configuring the counters. + * The accumulated results are fetched from SRAM. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#define DRIVER_NAME "intel_telemetry" +#define DRIVER_VERSION "1.0.0" + +#define TELEM_TRC_VERBOSITY_MASK 0x3 + +#define TELEM_MIN_PERIOD(x) ((x) & 0x7F0000) +#define TELEM_MAX_PERIOD(x) ((x) & 0x7F000000) +#define TELEM_SAMPLE_PERIOD_INVALID(x) ((x) & (BIT(7))) +#define TELEM_CLEAR_SAMPLE_PERIOD(x) ((x) &= ~0x7F) + +#define TELEM_SAMPLING_DEFAULT_PERIOD 0xD + +#define TELEM_MAX_EVENTS_SRAM 28 +#define TELEM_SSRAM_STARTTIME_OFFSET 8 +#define TELEM_SSRAM_EVTLOG_OFFSET 16 + +#define IOSS_TELEM 0xeb +#define IOSS_TELEM_EVENT_READ 0x0 +#define IOSS_TELEM_EVENT_WRITE 0x1 +#define IOSS_TELEM_INFO_READ 0x2 +#define IOSS_TELEM_TRACE_CTL_READ 0x5 +#define IOSS_TELEM_TRACE_CTL_WRITE 0x6 +#define IOSS_TELEM_EVENT_CTL_READ 0x7 +#define IOSS_TELEM_EVENT_CTL_WRITE 0x8 +#define IOSS_TELEM_EVT_WRITE_SIZE 0x3 + +#define TELEM_INFO_SRAMEVTS_MASK 0xFF00 +#define TELEM_INFO_SRAMEVTS_SHIFT 0x8 +#define TELEM_SSRAM_READ_TIMEOUT 10 + +#define TELEM_INFO_NENABLES_MASK 0xFF +#define TELEM_EVENT_ENABLE 0x8000 + +#define TELEM_MASK_BIT 1 +#define TELEM_MASK_BYTE 0xFF +#define BYTES_PER_LONG 8 +#define TELEM_MASK_PCS_STATE 0xF + +#define TELEM_DISABLE(x) ((x) &= ~(BIT(31))) +#define TELEM_CLEAR_EVENTS(x) ((x) |= (BIT(30))) +#define TELEM_ENABLE_SRAM_EVT_TRACE(x) ((x) &= ~(BIT(30) | BIT(24))) +#define TELEM_ENABLE_PERIODIC(x) ((x) |= (BIT(23) | BIT(31) | BIT(7))) +#define TELEM_EXTRACT_VERBOSITY(x, y) ((y) = (((x) >> 27) & 0x3)) +#define TELEM_CLEAR_VERBOSITY_BITS(x) ((x) &= ~(BIT(27) | BIT(28))) +#define TELEM_SET_VERBOSITY_BITS(x, y) ((x) |= ((y) << 27)) + +enum telemetry_action { + TELEM_UPDATE = 0, + TELEM_ADD, + TELEM_RESET, + TELEM_ACTION_NONE +}; + +struct telem_ssram_region { + u64 timestamp; + u64 start_time; + u64 events[TELEM_MAX_EVENTS_SRAM]; +}; + +static struct telemetry_plt_config *telm_conf; + +/* + * The following counters are programmed by default during setup. + * Only 20 allocated to kernel driver + */ +static struct telemetry_evtmap + telemetry_apl_ioss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = { + {"SOC_S0IX_TOTAL_RES", 0x4800}, + {"SOC_S0IX_TOTAL_OCC", 0x4000}, + {"SOC_S0IX_SHALLOW_RES", 0x4801}, + {"SOC_S0IX_SHALLOW_OCC", 0x4001}, + {"SOC_S0IX_DEEP_RES", 0x4802}, + {"SOC_S0IX_DEEP_OCC", 0x4002}, + {"PMC_POWER_GATE", 0x5818}, + {"PMC_D3_STATES", 0x5819}, + {"PMC_D0I3_STATES", 0x581A}, + {"PMC_S0IX_WAKE_REASON_GPIO", 0x6000}, + {"PMC_S0IX_WAKE_REASON_TIMER", 0x6001}, + {"PMC_S0IX_WAKE_REASON_VNNREQ", 0x6002}, + {"PMC_S0IX_WAKE_REASON_LOWPOWER", 0x6003}, + {"PMC_S0IX_WAKE_REASON_EXTERNAL", 0x6004}, + {"PMC_S0IX_WAKE_REASON_MISC", 0x6005}, + {"PMC_S0IX_BLOCKING_IPS_D3_D0I3", 0x6006}, + {"PMC_S0IX_BLOCKING_IPS_PG", 0x6007}, + {"PMC_S0IX_BLOCKING_MISC_IPS_PG", 0x6008}, + {"PMC_S0IX_BLOCK_IPS_VNN_REQ", 0x6009}, + {"PMC_S0IX_BLOCK_IPS_CLOCKS", 0x600B}, +}; + + +static struct telemetry_evtmap + telemetry_apl_pss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = { + {"IA_CORE0_C6_RES", 0x0400}, + {"IA_CORE0_C6_CTR", 0x0000}, + {"IA_MODULE0_C7_RES", 0x0410}, + {"IA_MODULE0_C7_CTR", 0x000E}, + {"IA_C0_RES", 0x0805}, + {"PCS_LTR", 0x2801}, + {"PSTATES", 0x2802}, + {"SOC_S0I3_RES", 0x0409}, + {"SOC_S0I3_CTR", 0x000A}, + {"PCS_S0I3_CTR", 0x0009}, + {"PCS_C1E_RES", 0x041A}, + {"PCS_IDLE_STATUS", 0x2806}, + {"IA_PERF_LIMITS", 0x280B}, + {"GT_PERF_LIMITS", 0x280C}, + {"PCS_WAKEUP_S0IX_CTR", 0x0030}, + {"PCS_IDLE_BLOCKED", 0x2C00}, + {"PCS_S0IX_BLOCKED", 0x2C01}, + {"PCS_S0IX_WAKE_REASONS", 0x2C02}, + {"PCS_LTR_BLOCKING", 0x2C03}, + {"PC2_AND_MEM_SHALLOW_IDLE_RES", 0x1D40}, +}; + +static struct telemetry_evtmap + telemetry_glk_pss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = { + {"IA_CORE0_C6_RES", 0x0400}, + {"IA_CORE0_C6_CTR", 0x0000}, + {"IA_MODULE0_C7_RES", 0x0410}, + {"IA_MODULE0_C7_CTR", 0x000C}, + {"IA_C0_RES", 0x0805}, + {"PCS_LTR", 0x2801}, + {"PSTATES", 0x2802}, + {"SOC_S0I3_RES", 0x0407}, + {"SOC_S0I3_CTR", 0x0008}, + {"PCS_S0I3_CTR", 0x0007}, + {"PCS_C1E_RES", 0x0414}, + {"PCS_IDLE_STATUS", 0x2806}, + {"IA_PERF_LIMITS", 0x280B}, + {"GT_PERF_LIMITS", 0x280C}, + {"PCS_WAKEUP_S0IX_CTR", 0x0025}, + {"PCS_IDLE_BLOCKED", 0x2C00}, + {"PCS_S0IX_BLOCKED", 0x2C01}, + {"PCS_S0IX_WAKE_REASONS", 0x2C02}, + {"PCS_LTR_BLOCKING", 0x2C03}, + {"PC2_AND_MEM_SHALLOW_IDLE_RES", 0x1D40}, +}; + +/* APL specific Data */ +static struct telemetry_plt_config telem_apl_config = { + .pss_config = { + .telem_evts = telemetry_apl_pss_default_events, + }, + .ioss_config = { + .telem_evts = telemetry_apl_ioss_default_events, + }, +}; + +/* GLK specific Data */ +static struct telemetry_plt_config telem_glk_config = { + .pss_config = { + .telem_evts = telemetry_glk_pss_default_events, + }, + .ioss_config = { + .telem_evts = telemetry_apl_ioss_default_events, + }, +}; + +static const struct x86_cpu_id telemetry_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, &telem_apl_config), + X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, &telem_glk_config), + {} +}; + +MODULE_DEVICE_TABLE(x86cpu, telemetry_cpu_ids); + +static inline int telem_get_unitconfig(enum telemetry_unit telem_unit, + struct telemetry_unit_config **unit_config) +{ + if (telem_unit == TELEM_PSS) + *unit_config = &(telm_conf->pss_config); + else if (telem_unit == TELEM_IOSS) + *unit_config = &(telm_conf->ioss_config); + else + return -EINVAL; + + return 0; + +} + +static int telemetry_check_evtid(enum telemetry_unit telem_unit, + u32 *evtmap, u8 len, + enum telemetry_action action) +{ + struct telemetry_unit_config *unit_config; + int ret; + + ret = telem_get_unitconfig(telem_unit, &unit_config); + if (ret < 0) + return ret; + + switch (action) { + case TELEM_RESET: + if (len > TELEM_MAX_EVENTS_SRAM) + return -EINVAL; + + break; + + case TELEM_UPDATE: + if (len > TELEM_MAX_EVENTS_SRAM) + return -EINVAL; + + if ((len > 0) && (evtmap == NULL)) + return -EINVAL; + + break; + + case TELEM_ADD: + if ((len + unit_config->ssram_evts_used) > + TELEM_MAX_EVENTS_SRAM) + return -EINVAL; + + if ((len > 0) && (evtmap == NULL)) + return -EINVAL; + + break; + + default: + pr_err("Unknown Telemetry action specified %d\n", action); + return -EINVAL; + } + + return 0; +} + + +static inline int telemetry_plt_config_ioss_event(u32 evt_id, int index) +{ + u32 write_buf; + + write_buf = evt_id | TELEM_EVENT_ENABLE; + write_buf <<= BITS_PER_BYTE; + write_buf |= index; + + return intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, + IOSS_TELEM_EVENT_WRITE, &write_buf, + IOSS_TELEM_EVT_WRITE_SIZE, NULL, 0); +} + +static inline int telemetry_plt_config_pss_event(u32 evt_id, int index) +{ + u32 write_buf; + int ret; + + write_buf = evt_id | TELEM_EVENT_ENABLE; + ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT, + index, 0, &write_buf, NULL); + + return ret; +} + +static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig, + enum telemetry_action action) +{ + struct intel_scu_ipc_dev *scu = telm_conf->scu; + u8 num_ioss_evts, ioss_period; + int ret, index, idx; + u32 *ioss_evtmap; + u32 telem_ctrl; + + num_ioss_evts = evtconfig.num_evts; + ioss_period = evtconfig.period; + ioss_evtmap = evtconfig.evtmap; + + /* Get telemetry EVENT CTL */ + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_READ, NULL, 0, + &telem_ctrl, sizeof(telem_ctrl)); + if (ret) { + pr_err("IOSS TELEM_CTRL Read Failed\n"); + return ret; + } + + /* Disable Telemetry */ + TELEM_DISABLE(telem_ctrl); + + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_WRITE, &telem_ctrl, + sizeof(telem_ctrl), NULL, 0); + if (ret) { + pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); + return ret; + } + + + /* Reset Everything */ + if (action == TELEM_RESET) { + /* Clear All Events */ + TELEM_CLEAR_EVENTS(telem_ctrl); + + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_WRITE, + &telem_ctrl, sizeof(telem_ctrl), + NULL, 0); + if (ret) { + pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); + return ret; + } + telm_conf->ioss_config.ssram_evts_used = 0; + + /* Configure Events */ + for (idx = 0; idx < num_ioss_evts; idx++) { + if (telemetry_plt_config_ioss_event( + telm_conf->ioss_config.telem_evts[idx].evt_id, + idx)) { + pr_err("IOSS TELEM_RESET Fail for data: %x\n", + telm_conf->ioss_config.telem_evts[idx].evt_id); + continue; + } + telm_conf->ioss_config.ssram_evts_used++; + } + } + + /* Re-Configure Everything */ + if (action == TELEM_UPDATE) { + /* Clear All Events */ + TELEM_CLEAR_EVENTS(telem_ctrl); + + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_WRITE, + &telem_ctrl, sizeof(telem_ctrl), + NULL, 0); + if (ret) { + pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); + return ret; + } + telm_conf->ioss_config.ssram_evts_used = 0; + + /* Configure Events */ + for (index = 0; index < num_ioss_evts; index++) { + telm_conf->ioss_config.telem_evts[index].evt_id = + ioss_evtmap[index]; + + if (telemetry_plt_config_ioss_event( + telm_conf->ioss_config.telem_evts[index].evt_id, + index)) { + pr_err("IOSS TELEM_UPDATE Fail for Evt%x\n", + ioss_evtmap[index]); + continue; + } + telm_conf->ioss_config.ssram_evts_used++; + } + } + + /* Add some Events */ + if (action == TELEM_ADD) { + /* Configure Events */ + for (index = telm_conf->ioss_config.ssram_evts_used, idx = 0; + idx < num_ioss_evts; index++, idx++) { + telm_conf->ioss_config.telem_evts[index].evt_id = + ioss_evtmap[idx]; + + if (telemetry_plt_config_ioss_event( + telm_conf->ioss_config.telem_evts[index].evt_id, + index)) { + pr_err("IOSS TELEM_ADD Fail for Event %x\n", + ioss_evtmap[idx]); + continue; + } + telm_conf->ioss_config.ssram_evts_used++; + } + } + + /* Enable Periodic Telemetry Events and enable SRAM trace */ + TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); + TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); + TELEM_ENABLE_PERIODIC(telem_ctrl); + telem_ctrl |= ioss_period; + + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_WRITE, + &telem_ctrl, sizeof(telem_ctrl), NULL, 0); + if (ret) { + pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n"); + return ret; + } + + telm_conf->ioss_config.curr_period = ioss_period; + + return 0; +} + + +static int telemetry_setup_pssevtconfig(struct telemetry_evtconfig evtconfig, + enum telemetry_action action) +{ + u8 num_pss_evts, pss_period; + int ret, index, idx; + u32 *pss_evtmap; + u32 telem_ctrl; + + num_pss_evts = evtconfig.num_evts; + pss_period = evtconfig.period; + pss_evtmap = evtconfig.evtmap; + + /* PSS Config */ + /* Get telemetry EVENT CTL */ + ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL, + 0, 0, NULL, &telem_ctrl); + if (ret) { + pr_err("PSS TELEM_CTRL Read Failed\n"); + return ret; + } + + /* Disable Telemetry */ + TELEM_DISABLE(telem_ctrl); + ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, + 0, 0, &telem_ctrl, NULL); + if (ret) { + pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); + return ret; + } + + /* Reset Everything */ + if (action == TELEM_RESET) { + /* Clear All Events */ + TELEM_CLEAR_EVENTS(telem_ctrl); + + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, + 0, 0, &telem_ctrl, NULL); + if (ret) { + pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); + return ret; + } + telm_conf->pss_config.ssram_evts_used = 0; + /* Configure Events */ + for (idx = 0; idx < num_pss_evts; idx++) { + if (telemetry_plt_config_pss_event( + telm_conf->pss_config.telem_evts[idx].evt_id, + idx)) { + pr_err("PSS TELEM_RESET Fail for Event %x\n", + telm_conf->pss_config.telem_evts[idx].evt_id); + continue; + } + telm_conf->pss_config.ssram_evts_used++; + } + } + + /* Re-Configure Everything */ + if (action == TELEM_UPDATE) { + /* Clear All Events */ + TELEM_CLEAR_EVENTS(telem_ctrl); + + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, + 0, 0, &telem_ctrl, NULL); + if (ret) { + pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); + return ret; + } + telm_conf->pss_config.ssram_evts_used = 0; + + /* Configure Events */ + for (index = 0; index < num_pss_evts; index++) { + telm_conf->pss_config.telem_evts[index].evt_id = + pss_evtmap[index]; + + if (telemetry_plt_config_pss_event( + telm_conf->pss_config.telem_evts[index].evt_id, + index)) { + pr_err("PSS TELEM_UPDATE Fail for Event %x\n", + pss_evtmap[index]); + continue; + } + telm_conf->pss_config.ssram_evts_used++; + } + } + + /* Add some Events */ + if (action == TELEM_ADD) { + /* Configure Events */ + for (index = telm_conf->pss_config.ssram_evts_used, idx = 0; + idx < num_pss_evts; index++, idx++) { + + telm_conf->pss_config.telem_evts[index].evt_id = + pss_evtmap[idx]; + + if (telemetry_plt_config_pss_event( + telm_conf->pss_config.telem_evts[index].evt_id, + index)) { + pr_err("PSS TELEM_ADD Fail for Event %x\n", + pss_evtmap[idx]); + continue; + } + telm_conf->pss_config.ssram_evts_used++; + } + } + + /* Enable Periodic Telemetry Events and enable SRAM trace */ + TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); + TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); + TELEM_ENABLE_PERIODIC(telem_ctrl); + telem_ctrl |= pss_period; + + ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, + 0, 0, &telem_ctrl, NULL); + if (ret) { + pr_err("PSS TELEM_CTRL Event Enable Write Failed\n"); + return ret; + } + + telm_conf->pss_config.curr_period = pss_period; + + return 0; +} + +static int telemetry_setup_evtconfig(struct telemetry_evtconfig pss_evtconfig, + struct telemetry_evtconfig ioss_evtconfig, + enum telemetry_action action) +{ + int ret; + + mutex_lock(&(telm_conf->telem_lock)); + + if ((action == TELEM_UPDATE) && (telm_conf->telem_in_use)) { + ret = -EBUSY; + goto out; + } + + ret = telemetry_check_evtid(TELEM_PSS, pss_evtconfig.evtmap, + pss_evtconfig.num_evts, action); + if (ret) + goto out; + + ret = telemetry_check_evtid(TELEM_IOSS, ioss_evtconfig.evtmap, + ioss_evtconfig.num_evts, action); + if (ret) + goto out; + + if (ioss_evtconfig.num_evts) { + ret = telemetry_setup_iossevtconfig(ioss_evtconfig, action); + if (ret) + goto out; + } + + if (pss_evtconfig.num_evts) { + ret = telemetry_setup_pssevtconfig(pss_evtconfig, action); + if (ret) + goto out; + } + + if ((action == TELEM_UPDATE) || (action == TELEM_ADD)) + telm_conf->telem_in_use = true; + else + telm_conf->telem_in_use = false; + +out: + mutex_unlock(&(telm_conf->telem_lock)); + return ret; +} + +static int telemetry_setup(struct platform_device *pdev) +{ + struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; + u32 read_buf, events, event_regs; + int ret; + + ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, + IOSS_TELEM_INFO_READ, NULL, 0, + &read_buf, sizeof(read_buf)); + if (ret) { + dev_err(&pdev->dev, "IOSS TELEM_INFO Read Failed\n"); + return ret; + } + + /* Get telemetry Info */ + events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >> + TELEM_INFO_SRAMEVTS_SHIFT; + event_regs = read_buf & TELEM_INFO_NENABLES_MASK; + if ((events < TELEM_MAX_EVENTS_SRAM) || + (event_regs < TELEM_MAX_EVENTS_SRAM)) { + dev_err(&pdev->dev, "IOSS:Insufficient Space for SRAM Trace\n"); + dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n", + events, event_regs); + return -ENOMEM; + } + + telm_conf->ioss_config.min_period = TELEM_MIN_PERIOD(read_buf); + telm_conf->ioss_config.max_period = TELEM_MAX_PERIOD(read_buf); + + /* PUNIT Mailbox Setup */ + ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_INFO, 0, 0, + NULL, &read_buf); + if (ret) { + dev_err(&pdev->dev, "PSS TELEM_INFO Read Failed\n"); + return ret; + } + + /* Get telemetry Info */ + events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >> + TELEM_INFO_SRAMEVTS_SHIFT; + event_regs = read_buf & TELEM_INFO_SRAMEVTS_MASK; + if ((events < TELEM_MAX_EVENTS_SRAM) || + (event_regs < TELEM_MAX_EVENTS_SRAM)) { + dev_err(&pdev->dev, "PSS:Insufficient Space for SRAM Trace\n"); + dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n", + events, event_regs); + return -ENOMEM; + } + + telm_conf->pss_config.min_period = TELEM_MIN_PERIOD(read_buf); + telm_conf->pss_config.max_period = TELEM_MAX_PERIOD(read_buf); + + pss_evtconfig.evtmap = NULL; + pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; + pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; + + ioss_evtconfig.evtmap = NULL; + ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; + ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; + + ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, + TELEM_RESET); + if (ret) { + dev_err(&pdev->dev, "TELEMETRY Setup Failed\n"); + return ret; + } + return 0; +} + +static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig, + struct telemetry_evtconfig ioss_evtconfig) +{ + int ret; + + if ((pss_evtconfig.num_evts > 0) && + (TELEM_SAMPLE_PERIOD_INVALID(pss_evtconfig.period))) { + pr_err("PSS Sampling Period Out of Range\n"); + return -EINVAL; + } + + if ((ioss_evtconfig.num_evts > 0) && + (TELEM_SAMPLE_PERIOD_INVALID(ioss_evtconfig.period))) { + pr_err("IOSS Sampling Period Out of Range\n"); + return -EINVAL; + } + + ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, + TELEM_UPDATE); + if (ret) + pr_err("TELEMETRY Config Failed\n"); + + return ret; +} + + +static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period) +{ + u32 telem_ctrl = 0; + int ret = 0; + + mutex_lock(&(telm_conf->telem_lock)); + if (ioss_period) { + struct intel_scu_ipc_dev *scu = telm_conf->scu; + + if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) { + pr_err("IOSS Sampling Period Out of Range\n"); + ret = -EINVAL; + goto out; + } + + /* Get telemetry EVENT CTL */ + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_READ, NULL, 0, + &telem_ctrl, sizeof(telem_ctrl)); + if (ret) { + pr_err("IOSS TELEM_CTRL Read Failed\n"); + goto out; + } + + /* Disable Telemetry */ + TELEM_DISABLE(telem_ctrl); + + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_WRITE, + &telem_ctrl, sizeof(telem_ctrl), + NULL, 0); + if (ret) { + pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); + goto out; + } + + /* Enable Periodic Telemetry Events and enable SRAM trace */ + TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); + TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); + TELEM_ENABLE_PERIODIC(telem_ctrl); + telem_ctrl |= ioss_period; + + ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, + IOSS_TELEM_EVENT_CTL_WRITE, + &telem_ctrl, sizeof(telem_ctrl), + NULL, 0); + if (ret) { + pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n"); + goto out; + } + telm_conf->ioss_config.curr_period = ioss_period; + } + + if (pss_period) { + if (TELEM_SAMPLE_PERIOD_INVALID(pss_period)) { + pr_err("PSS Sampling Period Out of Range\n"); + ret = -EINVAL; + goto out; + } + + /* Get telemetry EVENT CTL */ + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL, + 0, 0, NULL, &telem_ctrl); + if (ret) { + pr_err("PSS TELEM_CTRL Read Failed\n"); + goto out; + } + + /* Disable Telemetry */ + TELEM_DISABLE(telem_ctrl); + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, + 0, 0, &telem_ctrl, NULL); + if (ret) { + pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); + goto out; + } + + /* Enable Periodic Telemetry Events and enable SRAM trace */ + TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); + TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); + TELEM_ENABLE_PERIODIC(telem_ctrl); + telem_ctrl |= pss_period; + + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, + 0, 0, &telem_ctrl, NULL); + if (ret) { + pr_err("PSS TELEM_CTRL Event Enable Write Failed\n"); + goto out; + } + telm_conf->pss_config.curr_period = pss_period; + } + +out: + mutex_unlock(&(telm_conf->telem_lock)); + return ret; +} + + +static int telemetry_plt_get_sampling_period(u8 *pss_min_period, + u8 *pss_max_period, + u8 *ioss_min_period, + u8 *ioss_max_period) +{ + *pss_min_period = telm_conf->pss_config.min_period; + *pss_max_period = telm_conf->pss_config.max_period; + *ioss_min_period = telm_conf->ioss_config.min_period; + *ioss_max_period = telm_conf->ioss_config.max_period; + + return 0; +} + + +static int telemetry_plt_reset_events(void) +{ + struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; + int ret; + + pss_evtconfig.evtmap = NULL; + pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; + pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; + + ioss_evtconfig.evtmap = NULL; + ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; + ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; + + ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, + TELEM_RESET); + if (ret) + pr_err("TELEMETRY Reset Failed\n"); + + return ret; +} + + +static int telemetry_plt_get_eventconfig(struct telemetry_evtconfig *pss_config, + struct telemetry_evtconfig *ioss_config, + int pss_len, int ioss_len) +{ + u32 *pss_evtmap, *ioss_evtmap; + u32 index; + + pss_evtmap = pss_config->evtmap; + ioss_evtmap = ioss_config->evtmap; + + mutex_lock(&(telm_conf->telem_lock)); + pss_config->num_evts = telm_conf->pss_config.ssram_evts_used; + ioss_config->num_evts = telm_conf->ioss_config.ssram_evts_used; + + pss_config->period = telm_conf->pss_config.curr_period; + ioss_config->period = telm_conf->ioss_config.curr_period; + + if ((pss_len < telm_conf->pss_config.ssram_evts_used) || + (ioss_len < telm_conf->ioss_config.ssram_evts_used)) { + mutex_unlock(&(telm_conf->telem_lock)); + return -EINVAL; + } + + for (index = 0; index < telm_conf->pss_config.ssram_evts_used; + index++) { + pss_evtmap[index] = + telm_conf->pss_config.telem_evts[index].evt_id; + } + + for (index = 0; index < telm_conf->ioss_config.ssram_evts_used; + index++) { + ioss_evtmap[index] = + telm_conf->ioss_config.telem_evts[index].evt_id; + } + + mutex_unlock(&(telm_conf->telem_lock)); + return 0; +} + + +static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts, + u32 *pss_evtmap, u32 *ioss_evtmap) +{ + struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; + int ret; + + pss_evtconfig.evtmap = pss_evtmap; + pss_evtconfig.num_evts = num_pss_evts; + pss_evtconfig.period = telm_conf->pss_config.curr_period; + + ioss_evtconfig.evtmap = ioss_evtmap; + ioss_evtconfig.num_evts = num_ioss_evts; + ioss_evtconfig.period = telm_conf->ioss_config.curr_period; + + ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, + TELEM_ADD); + if (ret) + pr_err("TELEMETRY ADD Failed\n"); + + return ret; +} + +static int telem_evtlog_read(enum telemetry_unit telem_unit, + struct telem_ssram_region *ssram_region, u8 len) +{ + struct telemetry_unit_config *unit_config; + u64 timestamp_prev, timestamp_next; + int ret, index, timeout = 0; + + ret = telem_get_unitconfig(telem_unit, &unit_config); + if (ret < 0) + return ret; + + if (len > unit_config->ssram_evts_used) + len = unit_config->ssram_evts_used; + + do { + timestamp_prev = readq(unit_config->regmap); + if (!timestamp_prev) { + pr_err("Ssram under update. Please Try Later\n"); + return -EBUSY; + } + + ssram_region->start_time = readq(unit_config->regmap + + TELEM_SSRAM_STARTTIME_OFFSET); + + for (index = 0; index < len; index++) { + ssram_region->events[index] = + readq(unit_config->regmap + TELEM_SSRAM_EVTLOG_OFFSET + + BYTES_PER_LONG*index); + } + + timestamp_next = readq(unit_config->regmap); + if (!timestamp_next) { + pr_err("Ssram under update. Please Try Later\n"); + return -EBUSY; + } + + if (timeout++ > TELEM_SSRAM_READ_TIMEOUT) { + pr_err("Timeout while reading Events\n"); + return -EBUSY; + } + + } while (timestamp_prev != timestamp_next); + + ssram_region->timestamp = timestamp_next; + + return len; +} + +static int telemetry_plt_raw_read_eventlog(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, + int len, int log_all_evts) +{ + int index, idx1, ret, readlen = len; + struct telem_ssram_region ssram_region; + struct telemetry_evtmap *evtmap; + + switch (telem_unit) { + case TELEM_PSS: + evtmap = telm_conf->pss_config.telem_evts; + break; + + case TELEM_IOSS: + evtmap = telm_conf->ioss_config.telem_evts; + break; + + default: + pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit); + return -EINVAL; + } + + if (!log_all_evts) + readlen = TELEM_MAX_EVENTS_SRAM; + + ret = telem_evtlog_read(telem_unit, &ssram_region, readlen); + if (ret < 0) + return ret; + + /* Invalid evt-id array specified via length mismatch */ + if ((!log_all_evts) && (len > ret)) + return -EINVAL; + + if (log_all_evts) + for (index = 0; index < ret; index++) { + evtlog[index].telem_evtlog = ssram_region.events[index]; + evtlog[index].telem_evtid = evtmap[index].evt_id; + } + else + for (index = 0, readlen = 0; (index < ret) && (readlen < len); + index++) { + for (idx1 = 0; idx1 < len; idx1++) { + /* Elements matched */ + if (evtmap[index].evt_id == + evtlog[idx1].telem_evtid) { + evtlog[idx1].telem_evtlog = + ssram_region.events[index]; + readlen++; + + break; + } + } + } + + return readlen; +} + +static int telemetry_plt_read_eventlog(enum telemetry_unit telem_unit, + struct telemetry_evtlog *evtlog, int len, int log_all_evts) +{ + int ret; + + mutex_lock(&(telm_conf->telem_lock)); + ret = telemetry_plt_raw_read_eventlog(telem_unit, evtlog, + len, log_all_evts); + mutex_unlock(&(telm_conf->telem_lock)); + + return ret; +} + +static int telemetry_plt_get_trace_verbosity(enum telemetry_unit telem_unit, + u32 *verbosity) +{ + u32 temp = 0; + int ret; + + if (verbosity == NULL) + return -EINVAL; + + mutex_lock(&(telm_conf->telem_trace_lock)); + switch (telem_unit) { + case TELEM_PSS: + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL, + 0, 0, NULL, &temp); + if (ret) { + pr_err("PSS TRACE_CTRL Read Failed\n"); + goto out; + } + + break; + + case TELEM_IOSS: + ret = intel_scu_ipc_dev_command(telm_conf->scu, + IOSS_TELEM, IOSS_TELEM_TRACE_CTL_READ, + NULL, 0, &temp, sizeof(temp)); + if (ret) { + pr_err("IOSS TRACE_CTL Read Failed\n"); + goto out; + } + + break; + + default: + pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit); + ret = -EINVAL; + break; + } + TELEM_EXTRACT_VERBOSITY(temp, *verbosity); + +out: + mutex_unlock(&(telm_conf->telem_trace_lock)); + return ret; +} + +static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit, + u32 verbosity) +{ + u32 temp = 0; + int ret; + + verbosity &= TELEM_TRC_VERBOSITY_MASK; + + mutex_lock(&(telm_conf->telem_trace_lock)); + switch (telem_unit) { + case TELEM_PSS: + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL, + 0, 0, NULL, &temp); + if (ret) { + pr_err("PSS TRACE_CTRL Read Failed\n"); + goto out; + } + + TELEM_CLEAR_VERBOSITY_BITS(temp); + TELEM_SET_VERBOSITY_BITS(temp, verbosity); + + ret = intel_punit_ipc_command( + IPC_PUNIT_BIOS_WRITE_TELE_TRACE_CTRL, + 0, 0, &temp, NULL); + if (ret) { + pr_err("PSS TRACE_CTRL Verbosity Set Failed\n"); + goto out; + } + break; + + case TELEM_IOSS: + ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, + IOSS_TELEM_TRACE_CTL_READ, + NULL, 0, &temp, sizeof(temp)); + if (ret) { + pr_err("IOSS TRACE_CTL Read Failed\n"); + goto out; + } + + TELEM_CLEAR_VERBOSITY_BITS(temp); + TELEM_SET_VERBOSITY_BITS(temp, verbosity); + + ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, + IOSS_TELEM_TRACE_CTL_WRITE, + &temp, sizeof(temp), NULL, 0); + if (ret) { + pr_err("IOSS TRACE_CTL Verbosity Set Failed\n"); + goto out; + } + break; + + default: + pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit); + ret = -EINVAL; + break; + } + +out: + mutex_unlock(&(telm_conf->telem_trace_lock)); + return ret; +} + +static const struct telemetry_core_ops telm_pltops = { + .get_trace_verbosity = telemetry_plt_get_trace_verbosity, + .set_trace_verbosity = telemetry_plt_set_trace_verbosity, + .set_sampling_period = telemetry_plt_set_sampling_period, + .get_sampling_period = telemetry_plt_get_sampling_period, + .raw_read_eventlog = telemetry_plt_raw_read_eventlog, + .get_eventconfig = telemetry_plt_get_eventconfig, + .update_events = telemetry_plt_update_events, + .read_eventlog = telemetry_plt_read_eventlog, + .reset_events = telemetry_plt_reset_events, + .add_events = telemetry_plt_add_events, +}; + +static int telemetry_pltdrv_probe(struct platform_device *pdev) +{ + const struct x86_cpu_id *id; + void __iomem *mem; + int ret; + + id = x86_match_cpu(telemetry_cpu_ids); + if (!id) + return -ENODEV; + + telm_conf = (struct telemetry_plt_config *)id->driver_data; + + telm_conf->pmc = dev_get_drvdata(pdev->dev.parent); + + mem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + telm_conf->pss_config.regmap = mem; + + mem = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + telm_conf->ioss_config.regmap = mem; + + telm_conf->scu = devm_intel_scu_ipc_dev_get(&pdev->dev); + if (!telm_conf->scu) { + ret = -EPROBE_DEFER; + goto out; + } + + mutex_init(&telm_conf->telem_lock); + mutex_init(&telm_conf->telem_trace_lock); + + ret = telemetry_setup(pdev); + if (ret) + goto out; + + ret = telemetry_set_pltdata(&telm_pltops, telm_conf); + if (ret) { + dev_err(&pdev->dev, "TELEMETRY Set Pltops Failed.\n"); + goto out; + } + + return 0; + +out: + dev_err(&pdev->dev, "TELEMETRY Setup Failed.\n"); + + return ret; +} + +static int telemetry_pltdrv_remove(struct platform_device *pdev) +{ + telemetry_clear_pltdata(); + return 0; +} + +static struct platform_driver telemetry_soc_driver = { + .probe = telemetry_pltdrv_probe, + .remove = telemetry_pltdrv_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init telemetry_module_init(void) +{ + return platform_driver_register(&telemetry_soc_driver); +} + +static void __exit telemetry_module_exit(void) +{ + platform_driver_unregister(&telemetry_soc_driver); +} + +device_initcall(telemetry_module_init); +module_exit(telemetry_module_exit); + +MODULE_AUTHOR("Souvik Kumar Chakravarty "); +MODULE_DESCRIPTION("Intel SoC Telemetry Platform Driver"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_telemetry_core.c b/drivers/platform/x86/intel_telemetry_core.c deleted file mode 100644 index fdf55b5d6948..000000000000 --- a/drivers/platform/x86/intel_telemetry_core.c +++ /dev/null @@ -1,450 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel SoC Core Telemetry Driver - * Copyright (C) 2015, Intel Corporation. - * All Rights Reserved. - * - * Telemetry Framework provides platform related PM and performance statistics. - * This file provides the core telemetry API implementation. - */ -#include -#include - -#include - -#define DRIVER_NAME "intel_telemetry_core" - -struct telemetry_core_config { - struct telemetry_plt_config *plt_config; - const struct telemetry_core_ops *telem_ops; -}; - -static struct telemetry_core_config telm_core_conf; - -static int telemetry_def_update_events(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig) -{ - return 0; -} - -static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period) -{ - return 0; -} - -static int telemetry_def_get_sampling_period(u8 *pss_min_period, - u8 *pss_max_period, - u8 *ioss_min_period, - u8 *ioss_max_period) -{ - return 0; -} - -static int telemetry_def_get_eventconfig( - struct telemetry_evtconfig *pss_evtconfig, - struct telemetry_evtconfig *ioss_evtconfig, - int pss_len, int ioss_len) -{ - return 0; -} - -static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit, - u32 *verbosity) -{ - return 0; -} - - -static int telemetry_def_set_trace_verbosity(enum telemetry_unit telem_unit, - u32 verbosity) -{ - return 0; -} - -static int telemetry_def_raw_read_eventlog(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, - int len, int log_all_evts) -{ - return 0; -} - -static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, - int len, int log_all_evts) -{ - return 0; -} - -static int telemetry_def_add_events(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap) -{ - return 0; -} - -static int telemetry_def_reset_events(void) -{ - return 0; -} - -static const struct telemetry_core_ops telm_defpltops = { - .set_sampling_period = telemetry_def_set_sampling_period, - .get_sampling_period = telemetry_def_get_sampling_period, - .get_trace_verbosity = telemetry_def_get_trace_verbosity, - .set_trace_verbosity = telemetry_def_set_trace_verbosity, - .raw_read_eventlog = telemetry_def_raw_read_eventlog, - .get_eventconfig = telemetry_def_get_eventconfig, - .read_eventlog = telemetry_def_read_eventlog, - .update_events = telemetry_def_update_events, - .reset_events = telemetry_def_reset_events, - .add_events = telemetry_def_add_events, -}; - -/** - * telemetry_update_events() - Update telemetry Configuration - * @pss_evtconfig: PSS related config. No change if num_evts = 0. - * @pss_evtconfig: IOSS related config. No change if num_evts = 0. - * - * This API updates the IOSS & PSS Telemetry configuration. Old config - * is overwritten. Call telemetry_reset_events when logging is over - * All sample period values should be in the form of: - * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) - * - * Return: 0 success, < 0 for failure - */ -int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig) -{ - return telm_core_conf.telem_ops->update_events(pss_evtconfig, - ioss_evtconfig); -} -EXPORT_SYMBOL_GPL(telemetry_update_events); - - -/** - * telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period - * @pss_period: placeholder for PSS Period to be set. - * Set to 0 if not required to be updated - * @ioss_period: placeholder for IOSS Period to be set - * Set to 0 if not required to be updated - * - * All values should be in the form of: - * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) - * - * Return: 0 success, < 0 for failure - */ -int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period) -{ - return telm_core_conf.telem_ops->set_sampling_period(pss_period, - ioss_period); -} -EXPORT_SYMBOL_GPL(telemetry_set_sampling_period); - -/** - * telemetry_get_sampling_period() - Get IOSS & PSS min & max sampling period - * @pss_min_period: placeholder for PSS Min Period supported - * @pss_max_period: placeholder for PSS Max Period supported - * @ioss_min_period: placeholder for IOSS Min Period supported - * @ioss_max_period: placeholder for IOSS Max Period supported - * - * All values should be in the form of: - * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) - * - * Return: 0 success, < 0 for failure - */ -int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period, - u8 *ioss_min_period, u8 *ioss_max_period) -{ - return telm_core_conf.telem_ops->get_sampling_period(pss_min_period, - pss_max_period, - ioss_min_period, - ioss_max_period); -} -EXPORT_SYMBOL_GPL(telemetry_get_sampling_period); - - -/** - * telemetry_reset_events() - Restore the IOSS & PSS configuration to default - * - * Return: 0 success, < 0 for failure - */ -int telemetry_reset_events(void) -{ - return telm_core_conf.telem_ops->reset_events(); -} -EXPORT_SYMBOL_GPL(telemetry_reset_events); - -/** - * telemetry_get_eventconfig() - Returns the pss and ioss events enabled - * @pss_evtconfig: Pointer to PSS related configuration. - * @pss_evtconfig: Pointer to IOSS related configuration. - * @pss_len: Number of u32 elements allocated for pss_evtconfig array - * @ioss_len: Number of u32 elements allocated for ioss_evtconfig array - * - * Return: 0 success, < 0 for failure - */ -int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_evtconfig, - struct telemetry_evtconfig *ioss_evtconfig, - int pss_len, int ioss_len) -{ - return telm_core_conf.telem_ops->get_eventconfig(pss_evtconfig, - ioss_evtconfig, - pss_len, ioss_len); -} -EXPORT_SYMBOL_GPL(telemetry_get_eventconfig); - -/** - * telemetry_add_events() - Add IOSS & PSS configuration to existing settings. - * @num_pss_evts: Number of PSS Events (<29) in pss_evtmap. Can be 0. - * @num_ioss_evts: Number of IOSS Events (<29) in ioss_evtmap. Can be 0. - * @pss_evtmap: Array of PSS Event-IDs to Enable - * @ioss_evtmap: Array of PSS Event-IDs to Enable - * - * Events are appended to Old Configuration. In case of total events > 28, it - * returns error. Call telemetry_reset_events to reset after eventlog done - * - * Return: 0 success, < 0 for failure - */ -int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap) -{ - return telm_core_conf.telem_ops->add_events(num_pss_evts, - num_ioss_evts, pss_evtmap, - ioss_evtmap); -} -EXPORT_SYMBOL_GPL(telemetry_add_events); - -/** - * telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id - * @telem_unit: Specify whether IOSS or PSS Read - * @evtlog: Array of telemetry_evtlog structs to fill data - * evtlog.telem_evt_id specifies the ids to read - * @len: Length of array of evtlog - * - * Return: number of eventlogs read for success, < 0 for failure - */ -int telemetry_read_events(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, int len) -{ - return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog, - len, 0); -} -EXPORT_SYMBOL_GPL(telemetry_read_events); - -/** - * telemetry_raw_read_events() - Fetch samples specified by evtlog.telem_evt_id - * @telem_unit: Specify whether IOSS or PSS Read - * @evtlog: Array of telemetry_evtlog structs to fill data - * evtlog.telem_evt_id specifies the ids to read - * @len: Length of array of evtlog - * - * The caller must take care of locking in this case. - * - * Return: number of eventlogs read for success, < 0 for failure - */ -int telemetry_raw_read_events(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, int len) -{ - return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog, - len, 0); -} -EXPORT_SYMBOL_GPL(telemetry_raw_read_events); - -/** - * telemetry_read_eventlog() - Fetch the Telemetry log from PSS or IOSS - * @telem_unit: Specify whether IOSS or PSS Read - * @evtlog: Array of telemetry_evtlog structs to fill data - * @len: Length of array of evtlog - * - * Return: number of eventlogs read for success, < 0 for failure - */ -int telemetry_read_eventlog(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, int len) -{ - return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog, - len, 1); -} -EXPORT_SYMBOL_GPL(telemetry_read_eventlog); - -/** - * telemetry_raw_read_eventlog() - Fetch the Telemetry log from PSS or IOSS - * @telem_unit: Specify whether IOSS or PSS Read - * @evtlog: Array of telemetry_evtlog structs to fill data - * @len: Length of array of evtlog - * - * The caller must take care of locking in this case. - * - * Return: number of eventlogs read for success, < 0 for failure - */ -int telemetry_raw_read_eventlog(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, int len) -{ - return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog, - len, 1); -} -EXPORT_SYMBOL_GPL(telemetry_raw_read_eventlog); - - -/** - * telemetry_get_trace_verbosity() - Get the IOSS & PSS Trace verbosity - * @telem_unit: Specify whether IOSS or PSS Read - * @verbosity: Pointer to return Verbosity - * - * Return: 0 success, < 0 for failure - */ -int telemetry_get_trace_verbosity(enum telemetry_unit telem_unit, - u32 *verbosity) -{ - return telm_core_conf.telem_ops->get_trace_verbosity(telem_unit, - verbosity); -} -EXPORT_SYMBOL_GPL(telemetry_get_trace_verbosity); - - -/** - * telemetry_set_trace_verbosity() - Update the IOSS & PSS Trace verbosity - * @telem_unit: Specify whether IOSS or PSS Read - * @verbosity: Verbosity to set - * - * Return: 0 success, < 0 for failure - */ -int telemetry_set_trace_verbosity(enum telemetry_unit telem_unit, u32 verbosity) -{ - return telm_core_conf.telem_ops->set_trace_verbosity(telem_unit, - verbosity); -} -EXPORT_SYMBOL_GPL(telemetry_set_trace_verbosity); - -/** - * telemetry_set_pltdata() - Set the platform specific Data - * @ops: Pointer to ops structure - * @pltconfig: Platform config data - * - * Usage by other than telemetry pltdrv module is invalid - * - * Return: 0 success, < 0 for failure - */ -int telemetry_set_pltdata(const struct telemetry_core_ops *ops, - struct telemetry_plt_config *pltconfig) -{ - if (ops) - telm_core_conf.telem_ops = ops; - - if (pltconfig) - telm_core_conf.plt_config = pltconfig; - - return 0; -} -EXPORT_SYMBOL_GPL(telemetry_set_pltdata); - -/** - * telemetry_clear_pltdata() - Clear the platform specific Data - * - * Usage by other than telemetry pltdrv module is invalid - * - * Return: 0 success, < 0 for failure - */ -int telemetry_clear_pltdata(void) -{ - telm_core_conf.telem_ops = &telm_defpltops; - telm_core_conf.plt_config = NULL; - - return 0; -} -EXPORT_SYMBOL_GPL(telemetry_clear_pltdata); - -/** - * telemetry_get_pltdata() - Return telemetry platform config - * - * May be used by other telemetry modules to get platform specific - * configuration. - */ -struct telemetry_plt_config *telemetry_get_pltdata(void) -{ - return telm_core_conf.plt_config; -} -EXPORT_SYMBOL_GPL(telemetry_get_pltdata); - -static inline int telemetry_get_pssevtname(enum telemetry_unit telem_unit, - const char **name, int len) -{ - struct telemetry_unit_config psscfg; - int i; - - if (!telm_core_conf.plt_config) - return -EINVAL; - - psscfg = telm_core_conf.plt_config->pss_config; - - if (len > psscfg.ssram_evts_used) - len = psscfg.ssram_evts_used; - - for (i = 0; i < len; i++) - name[i] = psscfg.telem_evts[i].name; - - return 0; -} - -static inline int telemetry_get_iossevtname(enum telemetry_unit telem_unit, - const char **name, int len) -{ - struct telemetry_unit_config iosscfg; - int i; - - if (!(telm_core_conf.plt_config)) - return -EINVAL; - - iosscfg = telm_core_conf.plt_config->ioss_config; - - if (len > iosscfg.ssram_evts_used) - len = iosscfg.ssram_evts_used; - - for (i = 0; i < len; i++) - name[i] = iosscfg.telem_evts[i].name; - - return 0; - -} - -/** - * telemetry_get_evtname() - Checkif platform config is valid - * @telem_unit: Telemetry Unit to check - * @name: Array of character pointers to contain name - * @len: length of array name provided by user - * - * Usage by other than telemetry debugfs module is invalid - * - * Return: 0 success, < 0 for failure - */ -int telemetry_get_evtname(enum telemetry_unit telem_unit, - const char **name, int len) -{ - int ret = -EINVAL; - - if (telem_unit == TELEM_PSS) - ret = telemetry_get_pssevtname(telem_unit, name, len); - - else if (telem_unit == TELEM_IOSS) - ret = telemetry_get_iossevtname(telem_unit, name, len); - - return ret; -} -EXPORT_SYMBOL_GPL(telemetry_get_evtname); - -static int __init telemetry_module_init(void) -{ - pr_info(pr_fmt(DRIVER_NAME) " Init\n"); - - telm_core_conf.telem_ops = &telm_defpltops; - return 0; -} - -static void __exit telemetry_module_exit(void) -{ -} - -module_init(telemetry_module_init); -module_exit(telemetry_module_exit); - -MODULE_AUTHOR("Souvik Kumar Chakravarty "); -MODULE_DESCRIPTION("Intel SoC Telemetry Interface"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c deleted file mode 100644 index 1d4d0fbfd63c..000000000000 --- a/drivers/platform/x86/intel_telemetry_debugfs.c +++ /dev/null @@ -1,961 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel SOC Telemetry debugfs Driver: Currently supports APL - * Copyright (c) 2015, Intel Corporation. - * All Rights Reserved. - * - * This file provides the debugfs interfaces for telemetry. - * /sys/kernel/debug/telemetry/pss_info: Shows Primary Control Sub-Sys Counters - * /sys/kernel/debug/telemetry/ioss_info: Shows IO Sub-System Counters - * /sys/kernel/debug/telemetry/soc_states: Shows SoC State - * /sys/kernel/debug/telemetry/pss_trace_verbosity: Read and Change Tracing - * Verbosity via firmware - * /sys/kernel/debug/telemetry/ioss_race_verbosity: Write and Change Tracing - * Verbosity via firmware - */ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define DRIVER_NAME "telemetry_soc_debugfs" -#define DRIVER_VERSION "1.0.0" - -/* ApolloLake SoC Event-IDs */ -#define TELEM_APL_PSS_PSTATES_ID 0x2802 -#define TELEM_APL_PSS_IDLE_ID 0x2806 -#define TELEM_APL_PCS_IDLE_BLOCKED_ID 0x2C00 -#define TELEM_APL_PCS_S0IX_BLOCKED_ID 0x2C01 -#define TELEM_APL_PSS_WAKEUP_ID 0x2C02 -#define TELEM_APL_PSS_LTR_BLOCKING_ID 0x2C03 - -#define TELEM_APL_S0IX_TOTAL_OCC_ID 0x4000 -#define TELEM_APL_S0IX_SHLW_OCC_ID 0x4001 -#define TELEM_APL_S0IX_DEEP_OCC_ID 0x4002 -#define TELEM_APL_S0IX_TOTAL_RES_ID 0x4800 -#define TELEM_APL_S0IX_SHLW_RES_ID 0x4801 -#define TELEM_APL_S0IX_DEEP_RES_ID 0x4802 -#define TELEM_APL_D0IX_ID 0x581A -#define TELEM_APL_D3_ID 0x5819 -#define TELEM_APL_PG_ID 0x5818 - -#define TELEM_INFO_SRAMEVTS_MASK 0xFF00 -#define TELEM_INFO_SRAMEVTS_SHIFT 0x8 -#define TELEM_SSRAM_READ_TIMEOUT 10 - -#define TELEM_MASK_BIT 1 -#define TELEM_MASK_BYTE 0xFF -#define BYTES_PER_LONG 8 -#define TELEM_APL_MASK_PCS_STATE 0xF - -/* Max events in bitmap to check for */ -#define TELEM_PSS_IDLE_EVTS 25 -#define TELEM_PSS_IDLE_BLOCKED_EVTS 20 -#define TELEM_PSS_S0IX_BLOCKED_EVTS 20 -#define TELEM_PSS_S0IX_WAKEUP_EVTS 20 -#define TELEM_PSS_LTR_BLOCKING_EVTS 20 -#define TELEM_IOSS_DX_D0IX_EVTS 25 -#define TELEM_IOSS_PG_EVTS 30 - -#define TELEM_CHECK_AND_PARSE_EVTS(EVTID, EVTNUM, BUF, EVTLOG, EVTDAT, MASK) { \ - if (evtlog[index].telem_evtid == (EVTID)) { \ - for (idx = 0; idx < (EVTNUM); idx++) \ - (BUF)[idx] = ((EVTLOG) >> (EVTDAT)[idx].bit_pos) & \ - (MASK); \ - continue; \ - } \ -} - -#define TELEM_CHECK_AND_PARSE_CTRS(EVTID, CTR) { \ - if (evtlog[index].telem_evtid == (EVTID)) { \ - (CTR) = evtlog[index].telem_evtlog; \ - continue; \ - } \ -} - -static u8 suspend_prep_ok; -static u32 suspend_shlw_ctr_temp, suspend_deep_ctr_temp; -static u64 suspend_shlw_res_temp, suspend_deep_res_temp; - -struct telemetry_susp_stats { - u32 shlw_ctr; - u32 deep_ctr; - u64 shlw_res; - u64 deep_res; -}; - -/* Bitmap definitions for default counters in APL */ -struct telem_pss_idle_stateinfo { - const char *name; - u32 bit_pos; -}; - -static struct telem_pss_idle_stateinfo telem_apl_pss_idle_data[] = { - {"IA_CORE0_C1E", 0}, - {"IA_CORE1_C1E", 1}, - {"IA_CORE2_C1E", 2}, - {"IA_CORE3_C1E", 3}, - {"IA_CORE0_C6", 16}, - {"IA_CORE1_C6", 17}, - {"IA_CORE2_C6", 18}, - {"IA_CORE3_C6", 19}, - {"IA_MODULE0_C7", 32}, - {"IA_MODULE1_C7", 33}, - {"GT_RC6", 40}, - {"IUNIT_PROCESSING_IDLE", 41}, - {"FAR_MEM_IDLE", 43}, - {"DISPLAY_IDLE", 44}, - {"IUNIT_INPUT_SYSTEM_IDLE", 45}, - {"PCS_STATUS", 60}, -}; - -struct telem_pcs_blkd_info { - const char *name; - u32 bit_pos; -}; - -static struct telem_pcs_blkd_info telem_apl_pcs_idle_blkd_data[] = { - {"COMPUTE", 0}, - {"MISC", 8}, - {"MODULE_ACTIONS_PENDING", 16}, - {"LTR", 24}, - {"DISPLAY_WAKE", 32}, - {"ISP_WAKE", 40}, - {"PSF0_ACTIVE", 48}, -}; - -static struct telem_pcs_blkd_info telem_apl_pcs_s0ix_blkd_data[] = { - {"LTR", 0}, - {"IRTL", 8}, - {"WAKE_DEADLINE_PENDING", 16}, - {"DISPLAY", 24}, - {"ISP", 32}, - {"CORE", 40}, - {"PMC", 48}, - {"MISC", 56}, -}; - -struct telem_pss_ltr_info { - const char *name; - u32 bit_pos; -}; - -static struct telem_pss_ltr_info telem_apl_pss_ltr_data[] = { - {"CORE_ACTIVE", 0}, - {"MEM_UP", 8}, - {"DFX", 16}, - {"DFX_FORCE_LTR", 24}, - {"DISPLAY", 32}, - {"ISP", 40}, - {"SOUTH", 48}, -}; - -struct telem_pss_wakeup_info { - const char *name; - u32 bit_pos; -}; - -static struct telem_pss_wakeup_info telem_apl_pss_wakeup[] = { - {"IP_IDLE", 0}, - {"DISPLAY_WAKE", 8}, - {"VOLTAGE_REG_INT", 16}, - {"DROWSY_TIMER (HOTPLUG)", 24}, - {"CORE_WAKE", 32}, - {"MISC_S0IX", 40}, - {"MISC_ABORT", 56}, -}; - -struct telem_ioss_d0ix_stateinfo { - const char *name; - u32 bit_pos; -}; - -static struct telem_ioss_d0ix_stateinfo telem_apl_ioss_d0ix_data[] = { - {"CSE", 0}, - {"SCC2", 1}, - {"GMM", 2}, - {"XDCI", 3}, - {"XHCI", 4}, - {"ISH", 5}, - {"AVS", 6}, - {"PCIE0P1", 7}, - {"PECI0P0", 8}, - {"LPSS", 9}, - {"SCC", 10}, - {"PWM", 11}, - {"PCIE1_P3", 12}, - {"PCIE1_P2", 13}, - {"PCIE1_P1", 14}, - {"PCIE1_P0", 15}, - {"CNV", 16}, - {"SATA", 17}, - {"PRTC", 18}, -}; - -struct telem_ioss_pg_info { - const char *name; - u32 bit_pos; -}; - -static struct telem_ioss_pg_info telem_apl_ioss_pg_data[] = { - {"LPSS", 0}, - {"SCC", 1}, - {"P2SB", 2}, - {"SCC2", 3}, - {"GMM", 4}, - {"PCIE0", 5}, - {"XDCI", 6}, - {"xHCI", 7}, - {"CSE", 8}, - {"SPI", 9}, - {"AVSPGD4", 10}, - {"AVSPGD3", 11}, - {"AVSPGD2", 12}, - {"AVSPGD1", 13}, - {"ISH", 14}, - {"EXI", 15}, - {"NPKVRC", 16}, - {"NPKVNN", 17}, - {"CUNIT", 18}, - {"FUSE_CTRL", 19}, - {"PCIE1", 20}, - {"CNV", 21}, - {"LPC", 22}, - {"SATA", 23}, - {"SMB", 24}, - {"PRTC", 25}, -}; - -struct telemetry_debugfs_conf { - struct telemetry_susp_stats suspend_stats; - struct dentry *telemetry_dbg_dir; - - /* Bitmap Data */ - struct telem_ioss_d0ix_stateinfo *ioss_d0ix_data; - struct telem_pss_idle_stateinfo *pss_idle_data; - struct telem_pcs_blkd_info *pcs_idle_blkd_data; - struct telem_pcs_blkd_info *pcs_s0ix_blkd_data; - struct telem_pss_wakeup_info *pss_wakeup; - struct telem_pss_ltr_info *pss_ltr_data; - struct telem_ioss_pg_info *ioss_pg_data; - u8 pcs_idle_blkd_evts; - u8 pcs_s0ix_blkd_evts; - u8 pss_wakeup_evts; - u8 pss_idle_evts; - u8 pss_ltr_evts; - u8 ioss_d0ix_evts; - u8 ioss_pg_evts; - - /* IDs */ - u16 pss_ltr_blocking_id; - u16 pcs_idle_blkd_id; - u16 pcs_s0ix_blkd_id; - u16 s0ix_total_occ_id; - u16 s0ix_shlw_occ_id; - u16 s0ix_deep_occ_id; - u16 s0ix_total_res_id; - u16 s0ix_shlw_res_id; - u16 s0ix_deep_res_id; - u16 pss_wakeup_id; - u16 ioss_d0ix_id; - u16 pstates_id; - u16 pss_idle_id; - u16 ioss_d3_id; - u16 ioss_pg_id; -}; - -static struct telemetry_debugfs_conf *debugfs_conf; - -static struct telemetry_debugfs_conf telem_apl_debugfs_conf = { - .pss_idle_data = telem_apl_pss_idle_data, - .pcs_idle_blkd_data = telem_apl_pcs_idle_blkd_data, - .pcs_s0ix_blkd_data = telem_apl_pcs_s0ix_blkd_data, - .pss_ltr_data = telem_apl_pss_ltr_data, - .pss_wakeup = telem_apl_pss_wakeup, - .ioss_d0ix_data = telem_apl_ioss_d0ix_data, - .ioss_pg_data = telem_apl_ioss_pg_data, - - .pss_idle_evts = ARRAY_SIZE(telem_apl_pss_idle_data), - .pcs_idle_blkd_evts = ARRAY_SIZE(telem_apl_pcs_idle_blkd_data), - .pcs_s0ix_blkd_evts = ARRAY_SIZE(telem_apl_pcs_s0ix_blkd_data), - .pss_ltr_evts = ARRAY_SIZE(telem_apl_pss_ltr_data), - .pss_wakeup_evts = ARRAY_SIZE(telem_apl_pss_wakeup), - .ioss_d0ix_evts = ARRAY_SIZE(telem_apl_ioss_d0ix_data), - .ioss_pg_evts = ARRAY_SIZE(telem_apl_ioss_pg_data), - - .pstates_id = TELEM_APL_PSS_PSTATES_ID, - .pss_idle_id = TELEM_APL_PSS_IDLE_ID, - .pcs_idle_blkd_id = TELEM_APL_PCS_IDLE_BLOCKED_ID, - .pcs_s0ix_blkd_id = TELEM_APL_PCS_S0IX_BLOCKED_ID, - .pss_wakeup_id = TELEM_APL_PSS_WAKEUP_ID, - .pss_ltr_blocking_id = TELEM_APL_PSS_LTR_BLOCKING_ID, - .s0ix_total_occ_id = TELEM_APL_S0IX_TOTAL_OCC_ID, - .s0ix_shlw_occ_id = TELEM_APL_S0IX_SHLW_OCC_ID, - .s0ix_deep_occ_id = TELEM_APL_S0IX_DEEP_OCC_ID, - .s0ix_total_res_id = TELEM_APL_S0IX_TOTAL_RES_ID, - .s0ix_shlw_res_id = TELEM_APL_S0IX_SHLW_RES_ID, - .s0ix_deep_res_id = TELEM_APL_S0IX_DEEP_RES_ID, - .ioss_d0ix_id = TELEM_APL_D0IX_ID, - .ioss_d3_id = TELEM_APL_D3_ID, - .ioss_pg_id = TELEM_APL_PG_ID, -}; - -static const struct x86_cpu_id telemetry_debugfs_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, &telem_apl_debugfs_conf), - X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, &telem_apl_debugfs_conf), - {} -}; -MODULE_DEVICE_TABLE(x86cpu, telemetry_debugfs_cpu_ids); - -static int telemetry_debugfs_check_evts(void) -{ - if ((debugfs_conf->pss_idle_evts > TELEM_PSS_IDLE_EVTS) || - (debugfs_conf->pcs_idle_blkd_evts > TELEM_PSS_IDLE_BLOCKED_EVTS) || - (debugfs_conf->pcs_s0ix_blkd_evts > TELEM_PSS_S0IX_BLOCKED_EVTS) || - (debugfs_conf->pss_ltr_evts > TELEM_PSS_LTR_BLOCKING_EVTS) || - (debugfs_conf->pss_wakeup_evts > TELEM_PSS_S0IX_WAKEUP_EVTS) || - (debugfs_conf->ioss_d0ix_evts > TELEM_IOSS_DX_D0IX_EVTS) || - (debugfs_conf->ioss_pg_evts > TELEM_IOSS_PG_EVTS)) - return -EINVAL; - - return 0; -} - -static int telem_pss_states_show(struct seq_file *s, void *unused) -{ - struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; - struct telemetry_debugfs_conf *conf = debugfs_conf; - const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS]; - u32 pcs_idle_blkd[TELEM_PSS_IDLE_BLOCKED_EVTS], - pcs_s0ix_blkd[TELEM_PSS_S0IX_BLOCKED_EVTS], - pss_s0ix_wakeup[TELEM_PSS_S0IX_WAKEUP_EVTS], - pss_ltr_blkd[TELEM_PSS_LTR_BLOCKING_EVTS], - pss_idle[TELEM_PSS_IDLE_EVTS]; - int index, idx, ret, err = 0; - u64 pstates = 0; - - ret = telemetry_read_eventlog(TELEM_PSS, evtlog, - TELEM_MAX_OS_ALLOCATED_EVENTS); - if (ret < 0) - return ret; - - err = telemetry_get_evtname(TELEM_PSS, name, - TELEM_MAX_OS_ALLOCATED_EVENTS); - if (err < 0) - return err; - - seq_puts(s, "\n----------------------------------------------------\n"); - seq_puts(s, "\tPSS TELEM EVENTLOG (Residency = field/19.2 us\n"); - seq_puts(s, "----------------------------------------------------\n"); - for (index = 0; index < ret; index++) { - seq_printf(s, "%-32s %llu\n", - name[index], evtlog[index].telem_evtlog); - - /* Fetch PSS IDLE State */ - if (evtlog[index].telem_evtid == conf->pss_idle_id) { - pss_idle[conf->pss_idle_evts - 1] = - (evtlog[index].telem_evtlog >> - conf->pss_idle_data[conf->pss_idle_evts - 1].bit_pos) & - TELEM_APL_MASK_PCS_STATE; - } - - TELEM_CHECK_AND_PARSE_EVTS(conf->pss_idle_id, - conf->pss_idle_evts - 1, - pss_idle, evtlog[index].telem_evtlog, - conf->pss_idle_data, TELEM_MASK_BIT); - - TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_idle_blkd_id, - conf->pcs_idle_blkd_evts, - pcs_idle_blkd, - evtlog[index].telem_evtlog, - conf->pcs_idle_blkd_data, - TELEM_MASK_BYTE); - - TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_s0ix_blkd_id, - conf->pcs_s0ix_blkd_evts, - pcs_s0ix_blkd, - evtlog[index].telem_evtlog, - conf->pcs_s0ix_blkd_data, - TELEM_MASK_BYTE); - - TELEM_CHECK_AND_PARSE_EVTS(conf->pss_wakeup_id, - conf->pss_wakeup_evts, - pss_s0ix_wakeup, - evtlog[index].telem_evtlog, - conf->pss_wakeup, TELEM_MASK_BYTE); - - TELEM_CHECK_AND_PARSE_EVTS(conf->pss_ltr_blocking_id, - conf->pss_ltr_evts, pss_ltr_blkd, - evtlog[index].telem_evtlog, - conf->pss_ltr_data, TELEM_MASK_BYTE); - - if (evtlog[index].telem_evtid == debugfs_conf->pstates_id) - pstates = evtlog[index].telem_evtlog; - } - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "PStates\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Domain\t\t\t\tFreq(Mhz)\n"); - seq_printf(s, " IA\t\t\t\t %llu\n GT\t\t\t\t %llu\n", - (pstates & TELEM_MASK_BYTE)*100, - ((pstates >> 8) & TELEM_MASK_BYTE)*50/3); - - seq_printf(s, " IUNIT\t\t\t\t %llu\n SA\t\t\t\t %llu\n", - ((pstates >> 16) & TELEM_MASK_BYTE)*25, - ((pstates >> 24) & TELEM_MASK_BYTE)*50/3); - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "PSS IDLE Status\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Device\t\t\t\t\tIDLE\n"); - for (index = 0; index < debugfs_conf->pss_idle_evts; index++) { - seq_printf(s, "%-32s\t%u\n", - debugfs_conf->pss_idle_data[index].name, - pss_idle[index]); - } - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "PSS Idle blkd Status (~1ms saturating bucket)\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Blocker\t\t\t\t\tCount\n"); - for (index = 0; index < debugfs_conf->pcs_idle_blkd_evts; index++) { - seq_printf(s, "%-32s\t%u\n", - debugfs_conf->pcs_idle_blkd_data[index].name, - pcs_idle_blkd[index]); - } - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "PSS S0ix blkd Status (~1ms saturating bucket)\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Blocker\t\t\t\t\tCount\n"); - for (index = 0; index < debugfs_conf->pcs_s0ix_blkd_evts; index++) { - seq_printf(s, "%-32s\t%u\n", - debugfs_conf->pcs_s0ix_blkd_data[index].name, - pcs_s0ix_blkd[index]); - } - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "LTR Blocking Status (~1ms saturating bucket)\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Blocker\t\t\t\t\tCount\n"); - for (index = 0; index < debugfs_conf->pss_ltr_evts; index++) { - seq_printf(s, "%-32s\t%u\n", - debugfs_conf->pss_ltr_data[index].name, - pss_s0ix_wakeup[index]); - } - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "Wakes Status (~1ms saturating bucket)\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Wakes\t\t\t\t\tCount\n"); - for (index = 0; index < debugfs_conf->pss_wakeup_evts; index++) { - seq_printf(s, "%-32s\t%u\n", - debugfs_conf->pss_wakeup[index].name, - pss_ltr_blkd[index]); - } - - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(telem_pss_states); - -static int telem_ioss_states_show(struct seq_file *s, void *unused) -{ - struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; - const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS]; - int index, ret, err; - - ret = telemetry_read_eventlog(TELEM_IOSS, evtlog, - TELEM_MAX_OS_ALLOCATED_EVENTS); - if (ret < 0) - return ret; - - err = telemetry_get_evtname(TELEM_IOSS, name, - TELEM_MAX_OS_ALLOCATED_EVENTS); - if (err < 0) - return err; - - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "\tI0SS TELEMETRY EVENTLOG\n"); - seq_puts(s, "--------------------------------------\n"); - for (index = 0; index < ret; index++) { - seq_printf(s, "%-32s 0x%llx\n", - name[index], evtlog[index].telem_evtlog); - } - - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(telem_ioss_states); - -static int telem_soc_states_show(struct seq_file *s, void *unused) -{ - u32 d3_sts[TELEM_IOSS_DX_D0IX_EVTS], d0ix_sts[TELEM_IOSS_DX_D0IX_EVTS]; - u32 pg_sts[TELEM_IOSS_PG_EVTS], pss_idle[TELEM_PSS_IDLE_EVTS]; - struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; - u32 s0ix_total_ctr = 0, s0ix_shlw_ctr = 0, s0ix_deep_ctr = 0; - u64 s0ix_total_res = 0, s0ix_shlw_res = 0, s0ix_deep_res = 0; - struct telemetry_debugfs_conf *conf = debugfs_conf; - struct pci_dev *dev = NULL; - int index, idx, ret; - u32 d3_state; - u16 pmcsr; - - ret = telemetry_read_eventlog(TELEM_IOSS, evtlog, - TELEM_MAX_OS_ALLOCATED_EVENTS); - if (ret < 0) - return ret; - - for (index = 0; index < ret; index++) { - TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d3_id, - conf->ioss_d0ix_evts, - d3_sts, evtlog[index].telem_evtlog, - conf->ioss_d0ix_data, - TELEM_MASK_BIT); - - TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_pg_id, conf->ioss_pg_evts, - pg_sts, evtlog[index].telem_evtlog, - conf->ioss_pg_data, TELEM_MASK_BIT); - - TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d0ix_id, - conf->ioss_d0ix_evts, - d0ix_sts, evtlog[index].telem_evtlog, - conf->ioss_d0ix_data, - TELEM_MASK_BIT); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_occ_id, - s0ix_total_ctr); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id, - s0ix_shlw_ctr); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id, - s0ix_deep_ctr); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_res_id, - s0ix_total_res); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id, - s0ix_shlw_res); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id, - s0ix_deep_res); - } - - seq_puts(s, "\n---------------------------------------------------\n"); - seq_puts(s, "S0IX Type\t\t\t Occurrence\t\t Residency(us)\n"); - seq_puts(s, "---------------------------------------------------\n"); - - seq_printf(s, "S0IX Shallow\t\t\t %10u\t %10llu\n", - s0ix_shlw_ctr - - conf->suspend_stats.shlw_ctr, - (u64)((s0ix_shlw_res - - conf->suspend_stats.shlw_res)*10/192)); - - seq_printf(s, "S0IX Deep\t\t\t %10u\t %10llu\n", - s0ix_deep_ctr - - conf->suspend_stats.deep_ctr, - (u64)((s0ix_deep_res - - conf->suspend_stats.deep_res)*10/192)); - - seq_printf(s, "Suspend(With S0ixShallow)\t %10u\t %10llu\n", - conf->suspend_stats.shlw_ctr, - (u64)(conf->suspend_stats.shlw_res*10)/192); - - seq_printf(s, "Suspend(With S0ixDeep)\t\t %10u\t %10llu\n", - conf->suspend_stats.deep_ctr, - (u64)(conf->suspend_stats.deep_res*10)/192); - - seq_printf(s, "TOTAL S0IX\t\t\t %10u\t %10llu\n", s0ix_total_ctr, - (u64)(s0ix_total_res*10/192)); - seq_puts(s, "\n-------------------------------------------------\n"); - seq_puts(s, "\t\tDEVICE STATES\n"); - seq_puts(s, "-------------------------------------------------\n"); - - for_each_pci_dev(dev) { - pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); - d3_state = ((pmcsr & PCI_PM_CTRL_STATE_MASK) == - (__force int)PCI_D3hot) ? 1 : 0; - - seq_printf(s, "pci %04x %04X %s %20.20s: ", - dev->vendor, dev->device, dev_name(&dev->dev), - dev_driver_string(&dev->dev)); - seq_printf(s, " d3:%x\n", d3_state); - } - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "D3/D0i3 Status\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Block\t\t D3\t D0i3\n"); - for (index = 0; index < conf->ioss_d0ix_evts; index++) { - seq_printf(s, "%-10s\t %u\t %u\n", - conf->ioss_d0ix_data[index].name, - d3_sts[index], d0ix_sts[index]); - } - - seq_puts(s, "\n--------------------------------------\n"); - seq_puts(s, "South Complex PowerGate Status\n"); - seq_puts(s, "--------------------------------------\n"); - seq_puts(s, "Device\t\t PG\n"); - for (index = 0; index < conf->ioss_pg_evts; index++) { - seq_printf(s, "%-10s\t %u\n", - conf->ioss_pg_data[index].name, - pg_sts[index]); - } - - evtlog->telem_evtid = conf->pss_idle_id; - ret = telemetry_read_events(TELEM_PSS, evtlog, 1); - if (ret < 0) - return ret; - - seq_puts(s, "\n-----------------------------------------\n"); - seq_puts(s, "North Idle Status\n"); - seq_puts(s, "-----------------------------------------\n"); - for (idx = 0; idx < conf->pss_idle_evts - 1; idx++) { - pss_idle[idx] = (evtlog->telem_evtlog >> - conf->pss_idle_data[idx].bit_pos) & - TELEM_MASK_BIT; - } - - pss_idle[idx] = (evtlog->telem_evtlog >> - conf->pss_idle_data[idx].bit_pos) & - TELEM_APL_MASK_PCS_STATE; - - for (index = 0; index < conf->pss_idle_evts; index++) { - seq_printf(s, "%-30s %u\n", - conf->pss_idle_data[index].name, - pss_idle[index]); - } - - seq_puts(s, "\nPCS_STATUS Code\n"); - seq_puts(s, "0:C0 1:C1 2:C1_DN_WT_DEV 3:C2 4:C2_WT_DE_MEM_UP\n"); - seq_puts(s, "5:C2_WT_DE_MEM_DOWN 6:C2_UP_WT_DEV 7:C2_DN 8:C2_VOA\n"); - seq_puts(s, "9:C2_VOA_UP 10:S0IX_PRE 11:S0IX\n"); - - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(telem_soc_states); - -static int telem_s0ix_res_get(void *data, u64 *val) -{ - struct telemetry_plt_config *plt_config = telemetry_get_pltdata(); - u64 s0ix_total_res; - int ret; - - ret = intel_pmc_s0ix_counter_read(plt_config->pmc, &s0ix_total_res); - if (ret) { - pr_err("Failed to read S0ix residency"); - return ret; - } - - *val = s0ix_total_res; - - return 0; -} - -DEFINE_DEBUGFS_ATTRIBUTE(telem_s0ix_fops, telem_s0ix_res_get, NULL, "%llu\n"); - -static int telem_pss_trc_verb_show(struct seq_file *s, void *unused) -{ - u32 verbosity; - int err; - - err = telemetry_get_trace_verbosity(TELEM_PSS, &verbosity); - if (err) { - pr_err("Get PSS Trace Verbosity Failed with Error %d\n", err); - return -EFAULT; - } - - seq_printf(s, "PSS Trace Verbosity %u\n", verbosity); - return 0; -} - -static ssize_t telem_pss_trc_verb_write(struct file *file, - const char __user *userbuf, - size_t count, loff_t *ppos) -{ - u32 verbosity; - int err; - - err = kstrtou32_from_user(userbuf, count, 0, &verbosity); - if (err) - return err; - - err = telemetry_set_trace_verbosity(TELEM_PSS, verbosity); - if (err) { - pr_err("Changing PSS Trace Verbosity Failed. Error %d\n", err); - return err; - } - - return count; -} - -static int telem_pss_trc_verb_open(struct inode *inode, struct file *file) -{ - return single_open(file, telem_pss_trc_verb_show, inode->i_private); -} - -static const struct file_operations telem_pss_trc_verb_ops = { - .open = telem_pss_trc_verb_open, - .read = seq_read, - .write = telem_pss_trc_verb_write, - .llseek = seq_lseek, - .release = single_release, -}; - -static int telem_ioss_trc_verb_show(struct seq_file *s, void *unused) -{ - u32 verbosity; - int err; - - err = telemetry_get_trace_verbosity(TELEM_IOSS, &verbosity); - if (err) { - pr_err("Get IOSS Trace Verbosity Failed with Error %d\n", err); - return -EFAULT; - } - - seq_printf(s, "IOSS Trace Verbosity %u\n", verbosity); - return 0; -} - -static ssize_t telem_ioss_trc_verb_write(struct file *file, - const char __user *userbuf, - size_t count, loff_t *ppos) -{ - u32 verbosity; - int err; - - err = kstrtou32_from_user(userbuf, count, 0, &verbosity); - if (err) - return err; - - err = telemetry_set_trace_verbosity(TELEM_IOSS, verbosity); - if (err) { - pr_err("Changing IOSS Trace Verbosity Failed. Error %d\n", err); - return err; - } - - return count; -} - -static int telem_ioss_trc_verb_open(struct inode *inode, struct file *file) -{ - return single_open(file, telem_ioss_trc_verb_show, inode->i_private); -} - -static const struct file_operations telem_ioss_trc_verb_ops = { - .open = telem_ioss_trc_verb_open, - .read = seq_read, - .write = telem_ioss_trc_verb_write, - .llseek = seq_lseek, - .release = single_release, -}; - -static int pm_suspend_prep_cb(void) -{ - struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; - struct telemetry_debugfs_conf *conf = debugfs_conf; - int ret, index; - - ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog, - TELEM_MAX_OS_ALLOCATED_EVENTS); - if (ret < 0) { - suspend_prep_ok = 0; - goto out; - } - - for (index = 0; index < ret; index++) { - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id, - suspend_shlw_ctr_temp); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id, - suspend_deep_ctr_temp); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id, - suspend_shlw_res_temp); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id, - suspend_deep_res_temp); - } - suspend_prep_ok = 1; -out: - return NOTIFY_OK; -} - -static int pm_suspend_exit_cb(void) -{ - struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; - static u32 suspend_shlw_ctr_exit, suspend_deep_ctr_exit; - static u64 suspend_shlw_res_exit, suspend_deep_res_exit; - struct telemetry_debugfs_conf *conf = debugfs_conf; - int ret, index; - - if (!suspend_prep_ok) - goto out; - - ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog, - TELEM_MAX_OS_ALLOCATED_EVENTS); - if (ret < 0) - goto out; - - for (index = 0; index < ret; index++) { - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id, - suspend_shlw_ctr_exit); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id, - suspend_deep_ctr_exit); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id, - suspend_shlw_res_exit); - - TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id, - suspend_deep_res_exit); - } - - if ((suspend_shlw_ctr_exit < suspend_shlw_ctr_temp) || - (suspend_deep_ctr_exit < suspend_deep_ctr_temp) || - (suspend_shlw_res_exit < suspend_shlw_res_temp) || - (suspend_deep_res_exit < suspend_deep_res_temp)) { - pr_err("Wrong s0ix counters detected\n"); - goto out; - } - - /* - * Due to some design limitations in the firmware, sometimes the - * counters do not get updated by the time we reach here. As a - * workaround, we try to see if this was a genuine case of sleep - * failure or not by cross-checking from PMC GCR registers directly. - */ - if (suspend_shlw_ctr_exit == suspend_shlw_ctr_temp && - suspend_deep_ctr_exit == suspend_deep_ctr_temp) { - struct telemetry_plt_config *plt_config = telemetry_get_pltdata(); - struct intel_pmc_dev *pmc = plt_config->pmc; - - ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_SHLW_S0IX_REG, - &suspend_shlw_res_exit); - if (ret < 0) - goto out; - - ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_DEEP_S0IX_REG, - &suspend_deep_res_exit); - if (ret < 0) - goto out; - - if (suspend_shlw_res_exit > suspend_shlw_res_temp) - suspend_shlw_ctr_exit++; - - if (suspend_deep_res_exit > suspend_deep_res_temp) - suspend_deep_ctr_exit++; - } - - suspend_shlw_ctr_exit -= suspend_shlw_ctr_temp; - suspend_deep_ctr_exit -= suspend_deep_ctr_temp; - suspend_shlw_res_exit -= suspend_shlw_res_temp; - suspend_deep_res_exit -= suspend_deep_res_temp; - - if (suspend_shlw_ctr_exit != 0) { - conf->suspend_stats.shlw_ctr += - suspend_shlw_ctr_exit; - - conf->suspend_stats.shlw_res += - suspend_shlw_res_exit; - } - - if (suspend_deep_ctr_exit != 0) { - conf->suspend_stats.deep_ctr += - suspend_deep_ctr_exit; - - conf->suspend_stats.deep_res += - suspend_deep_res_exit; - } - -out: - suspend_prep_ok = 0; - return NOTIFY_OK; -} - -static int pm_notification(struct notifier_block *this, - unsigned long event, void *ptr) -{ - switch (event) { - case PM_SUSPEND_PREPARE: - return pm_suspend_prep_cb(); - case PM_POST_SUSPEND: - return pm_suspend_exit_cb(); - } - - return NOTIFY_DONE; -} - -static struct notifier_block pm_notifier = { - .notifier_call = pm_notification, -}; - -static int __init telemetry_debugfs_init(void) -{ - const struct x86_cpu_id *id; - int err; - struct dentry *dir; - - /* Only APL supported for now */ - id = x86_match_cpu(telemetry_debugfs_cpu_ids); - if (!id) - return -ENODEV; - - debugfs_conf = (struct telemetry_debugfs_conf *)id->driver_data; - - if (!telemetry_get_pltdata()) { - pr_info("Invalid pltconfig, ensure IPC1 device is enabled in BIOS\n"); - return -ENODEV; - } - - err = telemetry_debugfs_check_evts(); - if (err < 0) { - pr_info("telemetry_debugfs_check_evts failed\n"); - return -EINVAL; - } - - register_pm_notifier(&pm_notifier); - - dir = debugfs_create_dir("telemetry", NULL); - debugfs_conf->telemetry_dbg_dir = dir; - - debugfs_create_file("pss_info", S_IFREG | S_IRUGO, dir, NULL, - &telem_pss_states_fops); - debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, dir, NULL, - &telem_ioss_states_fops); - debugfs_create_file("soc_states", S_IFREG | S_IRUGO, dir, NULL, - &telem_soc_states_fops); - debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, dir, NULL, - &telem_s0ix_fops); - debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, dir, NULL, - &telem_pss_trc_verb_ops); - debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, dir, - NULL, &telem_ioss_trc_verb_ops); - return 0; -} - -static void __exit telemetry_debugfs_exit(void) -{ - debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir); - debugfs_conf->telemetry_dbg_dir = NULL; - unregister_pm_notifier(&pm_notifier); -} - -late_initcall(telemetry_debugfs_init); -module_exit(telemetry_debugfs_exit); - -MODULE_AUTHOR("Souvik Kumar Chakravarty "); -MODULE_DESCRIPTION("Intel SoC Telemetry debugfs Interface"); -MODULE_VERSION(DRIVER_VERSION); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c deleted file mode 100644 index 405dea87de6b..000000000000 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ /dev/null @@ -1,1189 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel SOC Telemetry Platform Driver: Currently supports APL - * Copyright (c) 2015, Intel Corporation. - * All Rights Reserved. - * - * This file provides the platform specific telemetry implementation for APL. - * It used the PUNIT and PMC IPC interfaces for configuring the counters. - * The accumulated results are fetched from SRAM. - */ - -#include -#include -#include - -#include -#include -#include -#include - -#define DRIVER_NAME "intel_telemetry" -#define DRIVER_VERSION "1.0.0" - -#define TELEM_TRC_VERBOSITY_MASK 0x3 - -#define TELEM_MIN_PERIOD(x) ((x) & 0x7F0000) -#define TELEM_MAX_PERIOD(x) ((x) & 0x7F000000) -#define TELEM_SAMPLE_PERIOD_INVALID(x) ((x) & (BIT(7))) -#define TELEM_CLEAR_SAMPLE_PERIOD(x) ((x) &= ~0x7F) - -#define TELEM_SAMPLING_DEFAULT_PERIOD 0xD - -#define TELEM_MAX_EVENTS_SRAM 28 -#define TELEM_SSRAM_STARTTIME_OFFSET 8 -#define TELEM_SSRAM_EVTLOG_OFFSET 16 - -#define IOSS_TELEM 0xeb -#define IOSS_TELEM_EVENT_READ 0x0 -#define IOSS_TELEM_EVENT_WRITE 0x1 -#define IOSS_TELEM_INFO_READ 0x2 -#define IOSS_TELEM_TRACE_CTL_READ 0x5 -#define IOSS_TELEM_TRACE_CTL_WRITE 0x6 -#define IOSS_TELEM_EVENT_CTL_READ 0x7 -#define IOSS_TELEM_EVENT_CTL_WRITE 0x8 -#define IOSS_TELEM_EVT_WRITE_SIZE 0x3 - -#define TELEM_INFO_SRAMEVTS_MASK 0xFF00 -#define TELEM_INFO_SRAMEVTS_SHIFT 0x8 -#define TELEM_SSRAM_READ_TIMEOUT 10 - -#define TELEM_INFO_NENABLES_MASK 0xFF -#define TELEM_EVENT_ENABLE 0x8000 - -#define TELEM_MASK_BIT 1 -#define TELEM_MASK_BYTE 0xFF -#define BYTES_PER_LONG 8 -#define TELEM_MASK_PCS_STATE 0xF - -#define TELEM_DISABLE(x) ((x) &= ~(BIT(31))) -#define TELEM_CLEAR_EVENTS(x) ((x) |= (BIT(30))) -#define TELEM_ENABLE_SRAM_EVT_TRACE(x) ((x) &= ~(BIT(30) | BIT(24))) -#define TELEM_ENABLE_PERIODIC(x) ((x) |= (BIT(23) | BIT(31) | BIT(7))) -#define TELEM_EXTRACT_VERBOSITY(x, y) ((y) = (((x) >> 27) & 0x3)) -#define TELEM_CLEAR_VERBOSITY_BITS(x) ((x) &= ~(BIT(27) | BIT(28))) -#define TELEM_SET_VERBOSITY_BITS(x, y) ((x) |= ((y) << 27)) - -enum telemetry_action { - TELEM_UPDATE = 0, - TELEM_ADD, - TELEM_RESET, - TELEM_ACTION_NONE -}; - -struct telem_ssram_region { - u64 timestamp; - u64 start_time; - u64 events[TELEM_MAX_EVENTS_SRAM]; -}; - -static struct telemetry_plt_config *telm_conf; - -/* - * The following counters are programmed by default during setup. - * Only 20 allocated to kernel driver - */ -static struct telemetry_evtmap - telemetry_apl_ioss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = { - {"SOC_S0IX_TOTAL_RES", 0x4800}, - {"SOC_S0IX_TOTAL_OCC", 0x4000}, - {"SOC_S0IX_SHALLOW_RES", 0x4801}, - {"SOC_S0IX_SHALLOW_OCC", 0x4001}, - {"SOC_S0IX_DEEP_RES", 0x4802}, - {"SOC_S0IX_DEEP_OCC", 0x4002}, - {"PMC_POWER_GATE", 0x5818}, - {"PMC_D3_STATES", 0x5819}, - {"PMC_D0I3_STATES", 0x581A}, - {"PMC_S0IX_WAKE_REASON_GPIO", 0x6000}, - {"PMC_S0IX_WAKE_REASON_TIMER", 0x6001}, - {"PMC_S0IX_WAKE_REASON_VNNREQ", 0x6002}, - {"PMC_S0IX_WAKE_REASON_LOWPOWER", 0x6003}, - {"PMC_S0IX_WAKE_REASON_EXTERNAL", 0x6004}, - {"PMC_S0IX_WAKE_REASON_MISC", 0x6005}, - {"PMC_S0IX_BLOCKING_IPS_D3_D0I3", 0x6006}, - {"PMC_S0IX_BLOCKING_IPS_PG", 0x6007}, - {"PMC_S0IX_BLOCKING_MISC_IPS_PG", 0x6008}, - {"PMC_S0IX_BLOCK_IPS_VNN_REQ", 0x6009}, - {"PMC_S0IX_BLOCK_IPS_CLOCKS", 0x600B}, -}; - - -static struct telemetry_evtmap - telemetry_apl_pss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = { - {"IA_CORE0_C6_RES", 0x0400}, - {"IA_CORE0_C6_CTR", 0x0000}, - {"IA_MODULE0_C7_RES", 0x0410}, - {"IA_MODULE0_C7_CTR", 0x000E}, - {"IA_C0_RES", 0x0805}, - {"PCS_LTR", 0x2801}, - {"PSTATES", 0x2802}, - {"SOC_S0I3_RES", 0x0409}, - {"SOC_S0I3_CTR", 0x000A}, - {"PCS_S0I3_CTR", 0x0009}, - {"PCS_C1E_RES", 0x041A}, - {"PCS_IDLE_STATUS", 0x2806}, - {"IA_PERF_LIMITS", 0x280B}, - {"GT_PERF_LIMITS", 0x280C}, - {"PCS_WAKEUP_S0IX_CTR", 0x0030}, - {"PCS_IDLE_BLOCKED", 0x2C00}, - {"PCS_S0IX_BLOCKED", 0x2C01}, - {"PCS_S0IX_WAKE_REASONS", 0x2C02}, - {"PCS_LTR_BLOCKING", 0x2C03}, - {"PC2_AND_MEM_SHALLOW_IDLE_RES", 0x1D40}, -}; - -static struct telemetry_evtmap - telemetry_glk_pss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = { - {"IA_CORE0_C6_RES", 0x0400}, - {"IA_CORE0_C6_CTR", 0x0000}, - {"IA_MODULE0_C7_RES", 0x0410}, - {"IA_MODULE0_C7_CTR", 0x000C}, - {"IA_C0_RES", 0x0805}, - {"PCS_LTR", 0x2801}, - {"PSTATES", 0x2802}, - {"SOC_S0I3_RES", 0x0407}, - {"SOC_S0I3_CTR", 0x0008}, - {"PCS_S0I3_CTR", 0x0007}, - {"PCS_C1E_RES", 0x0414}, - {"PCS_IDLE_STATUS", 0x2806}, - {"IA_PERF_LIMITS", 0x280B}, - {"GT_PERF_LIMITS", 0x280C}, - {"PCS_WAKEUP_S0IX_CTR", 0x0025}, - {"PCS_IDLE_BLOCKED", 0x2C00}, - {"PCS_S0IX_BLOCKED", 0x2C01}, - {"PCS_S0IX_WAKE_REASONS", 0x2C02}, - {"PCS_LTR_BLOCKING", 0x2C03}, - {"PC2_AND_MEM_SHALLOW_IDLE_RES", 0x1D40}, -}; - -/* APL specific Data */ -static struct telemetry_plt_config telem_apl_config = { - .pss_config = { - .telem_evts = telemetry_apl_pss_default_events, - }, - .ioss_config = { - .telem_evts = telemetry_apl_ioss_default_events, - }, -}; - -/* GLK specific Data */ -static struct telemetry_plt_config telem_glk_config = { - .pss_config = { - .telem_evts = telemetry_glk_pss_default_events, - }, - .ioss_config = { - .telem_evts = telemetry_apl_ioss_default_events, - }, -}; - -static const struct x86_cpu_id telemetry_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, &telem_apl_config), - X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, &telem_glk_config), - {} -}; - -MODULE_DEVICE_TABLE(x86cpu, telemetry_cpu_ids); - -static inline int telem_get_unitconfig(enum telemetry_unit telem_unit, - struct telemetry_unit_config **unit_config) -{ - if (telem_unit == TELEM_PSS) - *unit_config = &(telm_conf->pss_config); - else if (telem_unit == TELEM_IOSS) - *unit_config = &(telm_conf->ioss_config); - else - return -EINVAL; - - return 0; - -} - -static int telemetry_check_evtid(enum telemetry_unit telem_unit, - u32 *evtmap, u8 len, - enum telemetry_action action) -{ - struct telemetry_unit_config *unit_config; - int ret; - - ret = telem_get_unitconfig(telem_unit, &unit_config); - if (ret < 0) - return ret; - - switch (action) { - case TELEM_RESET: - if (len > TELEM_MAX_EVENTS_SRAM) - return -EINVAL; - - break; - - case TELEM_UPDATE: - if (len > TELEM_MAX_EVENTS_SRAM) - return -EINVAL; - - if ((len > 0) && (evtmap == NULL)) - return -EINVAL; - - break; - - case TELEM_ADD: - if ((len + unit_config->ssram_evts_used) > - TELEM_MAX_EVENTS_SRAM) - return -EINVAL; - - if ((len > 0) && (evtmap == NULL)) - return -EINVAL; - - break; - - default: - pr_err("Unknown Telemetry action specified %d\n", action); - return -EINVAL; - } - - return 0; -} - - -static inline int telemetry_plt_config_ioss_event(u32 evt_id, int index) -{ - u32 write_buf; - - write_buf = evt_id | TELEM_EVENT_ENABLE; - write_buf <<= BITS_PER_BYTE; - write_buf |= index; - - return intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, - IOSS_TELEM_EVENT_WRITE, &write_buf, - IOSS_TELEM_EVT_WRITE_SIZE, NULL, 0); -} - -static inline int telemetry_plt_config_pss_event(u32 evt_id, int index) -{ - u32 write_buf; - int ret; - - write_buf = evt_id | TELEM_EVENT_ENABLE; - ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT, - index, 0, &write_buf, NULL); - - return ret; -} - -static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig, - enum telemetry_action action) -{ - struct intel_scu_ipc_dev *scu = telm_conf->scu; - u8 num_ioss_evts, ioss_period; - int ret, index, idx; - u32 *ioss_evtmap; - u32 telem_ctrl; - - num_ioss_evts = evtconfig.num_evts; - ioss_period = evtconfig.period; - ioss_evtmap = evtconfig.evtmap; - - /* Get telemetry EVENT CTL */ - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_READ, NULL, 0, - &telem_ctrl, sizeof(telem_ctrl)); - if (ret) { - pr_err("IOSS TELEM_CTRL Read Failed\n"); - return ret; - } - - /* Disable Telemetry */ - TELEM_DISABLE(telem_ctrl); - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, &telem_ctrl, - sizeof(telem_ctrl), NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); - return ret; - } - - - /* Reset Everything */ - if (action == TELEM_RESET) { - /* Clear All Events */ - TELEM_CLEAR_EVENTS(telem_ctrl); - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, - &telem_ctrl, sizeof(telem_ctrl), - NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); - return ret; - } - telm_conf->ioss_config.ssram_evts_used = 0; - - /* Configure Events */ - for (idx = 0; idx < num_ioss_evts; idx++) { - if (telemetry_plt_config_ioss_event( - telm_conf->ioss_config.telem_evts[idx].evt_id, - idx)) { - pr_err("IOSS TELEM_RESET Fail for data: %x\n", - telm_conf->ioss_config.telem_evts[idx].evt_id); - continue; - } - telm_conf->ioss_config.ssram_evts_used++; - } - } - - /* Re-Configure Everything */ - if (action == TELEM_UPDATE) { - /* Clear All Events */ - TELEM_CLEAR_EVENTS(telem_ctrl); - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, - &telem_ctrl, sizeof(telem_ctrl), - NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); - return ret; - } - telm_conf->ioss_config.ssram_evts_used = 0; - - /* Configure Events */ - for (index = 0; index < num_ioss_evts; index++) { - telm_conf->ioss_config.telem_evts[index].evt_id = - ioss_evtmap[index]; - - if (telemetry_plt_config_ioss_event( - telm_conf->ioss_config.telem_evts[index].evt_id, - index)) { - pr_err("IOSS TELEM_UPDATE Fail for Evt%x\n", - ioss_evtmap[index]); - continue; - } - telm_conf->ioss_config.ssram_evts_used++; - } - } - - /* Add some Events */ - if (action == TELEM_ADD) { - /* Configure Events */ - for (index = telm_conf->ioss_config.ssram_evts_used, idx = 0; - idx < num_ioss_evts; index++, idx++) { - telm_conf->ioss_config.telem_evts[index].evt_id = - ioss_evtmap[idx]; - - if (telemetry_plt_config_ioss_event( - telm_conf->ioss_config.telem_evts[index].evt_id, - index)) { - pr_err("IOSS TELEM_ADD Fail for Event %x\n", - ioss_evtmap[idx]); - continue; - } - telm_conf->ioss_config.ssram_evts_used++; - } - } - - /* Enable Periodic Telemetry Events and enable SRAM trace */ - TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); - TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); - TELEM_ENABLE_PERIODIC(telem_ctrl); - telem_ctrl |= ioss_period; - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, - &telem_ctrl, sizeof(telem_ctrl), NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n"); - return ret; - } - - telm_conf->ioss_config.curr_period = ioss_period; - - return 0; -} - - -static int telemetry_setup_pssevtconfig(struct telemetry_evtconfig evtconfig, - enum telemetry_action action) -{ - u8 num_pss_evts, pss_period; - int ret, index, idx; - u32 *pss_evtmap; - u32 telem_ctrl; - - num_pss_evts = evtconfig.num_evts; - pss_period = evtconfig.period; - pss_evtmap = evtconfig.evtmap; - - /* PSS Config */ - /* Get telemetry EVENT CTL */ - ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL, - 0, 0, NULL, &telem_ctrl); - if (ret) { - pr_err("PSS TELEM_CTRL Read Failed\n"); - return ret; - } - - /* Disable Telemetry */ - TELEM_DISABLE(telem_ctrl); - ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); - return ret; - } - - /* Reset Everything */ - if (action == TELEM_RESET) { - /* Clear All Events */ - TELEM_CLEAR_EVENTS(telem_ctrl); - - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); - return ret; - } - telm_conf->pss_config.ssram_evts_used = 0; - /* Configure Events */ - for (idx = 0; idx < num_pss_evts; idx++) { - if (telemetry_plt_config_pss_event( - telm_conf->pss_config.telem_evts[idx].evt_id, - idx)) { - pr_err("PSS TELEM_RESET Fail for Event %x\n", - telm_conf->pss_config.telem_evts[idx].evt_id); - continue; - } - telm_conf->pss_config.ssram_evts_used++; - } - } - - /* Re-Configure Everything */ - if (action == TELEM_UPDATE) { - /* Clear All Events */ - TELEM_CLEAR_EVENTS(telem_ctrl); - - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); - return ret; - } - telm_conf->pss_config.ssram_evts_used = 0; - - /* Configure Events */ - for (index = 0; index < num_pss_evts; index++) { - telm_conf->pss_config.telem_evts[index].evt_id = - pss_evtmap[index]; - - if (telemetry_plt_config_pss_event( - telm_conf->pss_config.telem_evts[index].evt_id, - index)) { - pr_err("PSS TELEM_UPDATE Fail for Event %x\n", - pss_evtmap[index]); - continue; - } - telm_conf->pss_config.ssram_evts_used++; - } - } - - /* Add some Events */ - if (action == TELEM_ADD) { - /* Configure Events */ - for (index = telm_conf->pss_config.ssram_evts_used, idx = 0; - idx < num_pss_evts; index++, idx++) { - - telm_conf->pss_config.telem_evts[index].evt_id = - pss_evtmap[idx]; - - if (telemetry_plt_config_pss_event( - telm_conf->pss_config.telem_evts[index].evt_id, - index)) { - pr_err("PSS TELEM_ADD Fail for Event %x\n", - pss_evtmap[idx]); - continue; - } - telm_conf->pss_config.ssram_evts_used++; - } - } - - /* Enable Periodic Telemetry Events and enable SRAM trace */ - TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); - TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); - TELEM_ENABLE_PERIODIC(telem_ctrl); - telem_ctrl |= pss_period; - - ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Enable Write Failed\n"); - return ret; - } - - telm_conf->pss_config.curr_period = pss_period; - - return 0; -} - -static int telemetry_setup_evtconfig(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig, - enum telemetry_action action) -{ - int ret; - - mutex_lock(&(telm_conf->telem_lock)); - - if ((action == TELEM_UPDATE) && (telm_conf->telem_in_use)) { - ret = -EBUSY; - goto out; - } - - ret = telemetry_check_evtid(TELEM_PSS, pss_evtconfig.evtmap, - pss_evtconfig.num_evts, action); - if (ret) - goto out; - - ret = telemetry_check_evtid(TELEM_IOSS, ioss_evtconfig.evtmap, - ioss_evtconfig.num_evts, action); - if (ret) - goto out; - - if (ioss_evtconfig.num_evts) { - ret = telemetry_setup_iossevtconfig(ioss_evtconfig, action); - if (ret) - goto out; - } - - if (pss_evtconfig.num_evts) { - ret = telemetry_setup_pssevtconfig(pss_evtconfig, action); - if (ret) - goto out; - } - - if ((action == TELEM_UPDATE) || (action == TELEM_ADD)) - telm_conf->telem_in_use = true; - else - telm_conf->telem_in_use = false; - -out: - mutex_unlock(&(telm_conf->telem_lock)); - return ret; -} - -static int telemetry_setup(struct platform_device *pdev) -{ - struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; - u32 read_buf, events, event_regs; - int ret; - - ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, - IOSS_TELEM_INFO_READ, NULL, 0, - &read_buf, sizeof(read_buf)); - if (ret) { - dev_err(&pdev->dev, "IOSS TELEM_INFO Read Failed\n"); - return ret; - } - - /* Get telemetry Info */ - events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >> - TELEM_INFO_SRAMEVTS_SHIFT; - event_regs = read_buf & TELEM_INFO_NENABLES_MASK; - if ((events < TELEM_MAX_EVENTS_SRAM) || - (event_regs < TELEM_MAX_EVENTS_SRAM)) { - dev_err(&pdev->dev, "IOSS:Insufficient Space for SRAM Trace\n"); - dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n", - events, event_regs); - return -ENOMEM; - } - - telm_conf->ioss_config.min_period = TELEM_MIN_PERIOD(read_buf); - telm_conf->ioss_config.max_period = TELEM_MAX_PERIOD(read_buf); - - /* PUNIT Mailbox Setup */ - ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_INFO, 0, 0, - NULL, &read_buf); - if (ret) { - dev_err(&pdev->dev, "PSS TELEM_INFO Read Failed\n"); - return ret; - } - - /* Get telemetry Info */ - events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >> - TELEM_INFO_SRAMEVTS_SHIFT; - event_regs = read_buf & TELEM_INFO_SRAMEVTS_MASK; - if ((events < TELEM_MAX_EVENTS_SRAM) || - (event_regs < TELEM_MAX_EVENTS_SRAM)) { - dev_err(&pdev->dev, "PSS:Insufficient Space for SRAM Trace\n"); - dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n", - events, event_regs); - return -ENOMEM; - } - - telm_conf->pss_config.min_period = TELEM_MIN_PERIOD(read_buf); - telm_conf->pss_config.max_period = TELEM_MAX_PERIOD(read_buf); - - pss_evtconfig.evtmap = NULL; - pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; - pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; - - ioss_evtconfig.evtmap = NULL; - ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; - ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; - - ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, - TELEM_RESET); - if (ret) { - dev_err(&pdev->dev, "TELEMETRY Setup Failed\n"); - return ret; - } - return 0; -} - -static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig) -{ - int ret; - - if ((pss_evtconfig.num_evts > 0) && - (TELEM_SAMPLE_PERIOD_INVALID(pss_evtconfig.period))) { - pr_err("PSS Sampling Period Out of Range\n"); - return -EINVAL; - } - - if ((ioss_evtconfig.num_evts > 0) && - (TELEM_SAMPLE_PERIOD_INVALID(ioss_evtconfig.period))) { - pr_err("IOSS Sampling Period Out of Range\n"); - return -EINVAL; - } - - ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, - TELEM_UPDATE); - if (ret) - pr_err("TELEMETRY Config Failed\n"); - - return ret; -} - - -static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period) -{ - u32 telem_ctrl = 0; - int ret = 0; - - mutex_lock(&(telm_conf->telem_lock)); - if (ioss_period) { - struct intel_scu_ipc_dev *scu = telm_conf->scu; - - if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) { - pr_err("IOSS Sampling Period Out of Range\n"); - ret = -EINVAL; - goto out; - } - - /* Get telemetry EVENT CTL */ - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_READ, NULL, 0, - &telem_ctrl, sizeof(telem_ctrl)); - if (ret) { - pr_err("IOSS TELEM_CTRL Read Failed\n"); - goto out; - } - - /* Disable Telemetry */ - TELEM_DISABLE(telem_ctrl); - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, - &telem_ctrl, sizeof(telem_ctrl), - NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); - goto out; - } - - /* Enable Periodic Telemetry Events and enable SRAM trace */ - TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); - TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); - TELEM_ENABLE_PERIODIC(telem_ctrl); - telem_ctrl |= ioss_period; - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, - &telem_ctrl, sizeof(telem_ctrl), - NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n"); - goto out; - } - telm_conf->ioss_config.curr_period = ioss_period; - } - - if (pss_period) { - if (TELEM_SAMPLE_PERIOD_INVALID(pss_period)) { - pr_err("PSS Sampling Period Out of Range\n"); - ret = -EINVAL; - goto out; - } - - /* Get telemetry EVENT CTL */ - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL, - 0, 0, NULL, &telem_ctrl); - if (ret) { - pr_err("PSS TELEM_CTRL Read Failed\n"); - goto out; - } - - /* Disable Telemetry */ - TELEM_DISABLE(telem_ctrl); - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); - goto out; - } - - /* Enable Periodic Telemetry Events and enable SRAM trace */ - TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); - TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); - TELEM_ENABLE_PERIODIC(telem_ctrl); - telem_ctrl |= pss_period; - - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Enable Write Failed\n"); - goto out; - } - telm_conf->pss_config.curr_period = pss_period; - } - -out: - mutex_unlock(&(telm_conf->telem_lock)); - return ret; -} - - -static int telemetry_plt_get_sampling_period(u8 *pss_min_period, - u8 *pss_max_period, - u8 *ioss_min_period, - u8 *ioss_max_period) -{ - *pss_min_period = telm_conf->pss_config.min_period; - *pss_max_period = telm_conf->pss_config.max_period; - *ioss_min_period = telm_conf->ioss_config.min_period; - *ioss_max_period = telm_conf->ioss_config.max_period; - - return 0; -} - - -static int telemetry_plt_reset_events(void) -{ - struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; - int ret; - - pss_evtconfig.evtmap = NULL; - pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; - pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; - - ioss_evtconfig.evtmap = NULL; - ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; - ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; - - ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, - TELEM_RESET); - if (ret) - pr_err("TELEMETRY Reset Failed\n"); - - return ret; -} - - -static int telemetry_plt_get_eventconfig(struct telemetry_evtconfig *pss_config, - struct telemetry_evtconfig *ioss_config, - int pss_len, int ioss_len) -{ - u32 *pss_evtmap, *ioss_evtmap; - u32 index; - - pss_evtmap = pss_config->evtmap; - ioss_evtmap = ioss_config->evtmap; - - mutex_lock(&(telm_conf->telem_lock)); - pss_config->num_evts = telm_conf->pss_config.ssram_evts_used; - ioss_config->num_evts = telm_conf->ioss_config.ssram_evts_used; - - pss_config->period = telm_conf->pss_config.curr_period; - ioss_config->period = telm_conf->ioss_config.curr_period; - - if ((pss_len < telm_conf->pss_config.ssram_evts_used) || - (ioss_len < telm_conf->ioss_config.ssram_evts_used)) { - mutex_unlock(&(telm_conf->telem_lock)); - return -EINVAL; - } - - for (index = 0; index < telm_conf->pss_config.ssram_evts_used; - index++) { - pss_evtmap[index] = - telm_conf->pss_config.telem_evts[index].evt_id; - } - - for (index = 0; index < telm_conf->ioss_config.ssram_evts_used; - index++) { - ioss_evtmap[index] = - telm_conf->ioss_config.telem_evts[index].evt_id; - } - - mutex_unlock(&(telm_conf->telem_lock)); - return 0; -} - - -static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap) -{ - struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; - int ret; - - pss_evtconfig.evtmap = pss_evtmap; - pss_evtconfig.num_evts = num_pss_evts; - pss_evtconfig.period = telm_conf->pss_config.curr_period; - - ioss_evtconfig.evtmap = ioss_evtmap; - ioss_evtconfig.num_evts = num_ioss_evts; - ioss_evtconfig.period = telm_conf->ioss_config.curr_period; - - ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, - TELEM_ADD); - if (ret) - pr_err("TELEMETRY ADD Failed\n"); - - return ret; -} - -static int telem_evtlog_read(enum telemetry_unit telem_unit, - struct telem_ssram_region *ssram_region, u8 len) -{ - struct telemetry_unit_config *unit_config; - u64 timestamp_prev, timestamp_next; - int ret, index, timeout = 0; - - ret = telem_get_unitconfig(telem_unit, &unit_config); - if (ret < 0) - return ret; - - if (len > unit_config->ssram_evts_used) - len = unit_config->ssram_evts_used; - - do { - timestamp_prev = readq(unit_config->regmap); - if (!timestamp_prev) { - pr_err("Ssram under update. Please Try Later\n"); - return -EBUSY; - } - - ssram_region->start_time = readq(unit_config->regmap + - TELEM_SSRAM_STARTTIME_OFFSET); - - for (index = 0; index < len; index++) { - ssram_region->events[index] = - readq(unit_config->regmap + TELEM_SSRAM_EVTLOG_OFFSET + - BYTES_PER_LONG*index); - } - - timestamp_next = readq(unit_config->regmap); - if (!timestamp_next) { - pr_err("Ssram under update. Please Try Later\n"); - return -EBUSY; - } - - if (timeout++ > TELEM_SSRAM_READ_TIMEOUT) { - pr_err("Timeout while reading Events\n"); - return -EBUSY; - } - - } while (timestamp_prev != timestamp_next); - - ssram_region->timestamp = timestamp_next; - - return len; -} - -static int telemetry_plt_raw_read_eventlog(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, - int len, int log_all_evts) -{ - int index, idx1, ret, readlen = len; - struct telem_ssram_region ssram_region; - struct telemetry_evtmap *evtmap; - - switch (telem_unit) { - case TELEM_PSS: - evtmap = telm_conf->pss_config.telem_evts; - break; - - case TELEM_IOSS: - evtmap = telm_conf->ioss_config.telem_evts; - break; - - default: - pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit); - return -EINVAL; - } - - if (!log_all_evts) - readlen = TELEM_MAX_EVENTS_SRAM; - - ret = telem_evtlog_read(telem_unit, &ssram_region, readlen); - if (ret < 0) - return ret; - - /* Invalid evt-id array specified via length mismatch */ - if ((!log_all_evts) && (len > ret)) - return -EINVAL; - - if (log_all_evts) - for (index = 0; index < ret; index++) { - evtlog[index].telem_evtlog = ssram_region.events[index]; - evtlog[index].telem_evtid = evtmap[index].evt_id; - } - else - for (index = 0, readlen = 0; (index < ret) && (readlen < len); - index++) { - for (idx1 = 0; idx1 < len; idx1++) { - /* Elements matched */ - if (evtmap[index].evt_id == - evtlog[idx1].telem_evtid) { - evtlog[idx1].telem_evtlog = - ssram_region.events[index]; - readlen++; - - break; - } - } - } - - return readlen; -} - -static int telemetry_plt_read_eventlog(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, int len, int log_all_evts) -{ - int ret; - - mutex_lock(&(telm_conf->telem_lock)); - ret = telemetry_plt_raw_read_eventlog(telem_unit, evtlog, - len, log_all_evts); - mutex_unlock(&(telm_conf->telem_lock)); - - return ret; -} - -static int telemetry_plt_get_trace_verbosity(enum telemetry_unit telem_unit, - u32 *verbosity) -{ - u32 temp = 0; - int ret; - - if (verbosity == NULL) - return -EINVAL; - - mutex_lock(&(telm_conf->telem_trace_lock)); - switch (telem_unit) { - case TELEM_PSS: - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL, - 0, 0, NULL, &temp); - if (ret) { - pr_err("PSS TRACE_CTRL Read Failed\n"); - goto out; - } - - break; - - case TELEM_IOSS: - ret = intel_scu_ipc_dev_command(telm_conf->scu, - IOSS_TELEM, IOSS_TELEM_TRACE_CTL_READ, - NULL, 0, &temp, sizeof(temp)); - if (ret) { - pr_err("IOSS TRACE_CTL Read Failed\n"); - goto out; - } - - break; - - default: - pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit); - ret = -EINVAL; - break; - } - TELEM_EXTRACT_VERBOSITY(temp, *verbosity); - -out: - mutex_unlock(&(telm_conf->telem_trace_lock)); - return ret; -} - -static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit, - u32 verbosity) -{ - u32 temp = 0; - int ret; - - verbosity &= TELEM_TRC_VERBOSITY_MASK; - - mutex_lock(&(telm_conf->telem_trace_lock)); - switch (telem_unit) { - case TELEM_PSS: - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL, - 0, 0, NULL, &temp); - if (ret) { - pr_err("PSS TRACE_CTRL Read Failed\n"); - goto out; - } - - TELEM_CLEAR_VERBOSITY_BITS(temp); - TELEM_SET_VERBOSITY_BITS(temp, verbosity); - - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_WRITE_TELE_TRACE_CTRL, - 0, 0, &temp, NULL); - if (ret) { - pr_err("PSS TRACE_CTRL Verbosity Set Failed\n"); - goto out; - } - break; - - case TELEM_IOSS: - ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, - IOSS_TELEM_TRACE_CTL_READ, - NULL, 0, &temp, sizeof(temp)); - if (ret) { - pr_err("IOSS TRACE_CTL Read Failed\n"); - goto out; - } - - TELEM_CLEAR_VERBOSITY_BITS(temp); - TELEM_SET_VERBOSITY_BITS(temp, verbosity); - - ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM, - IOSS_TELEM_TRACE_CTL_WRITE, - &temp, sizeof(temp), NULL, 0); - if (ret) { - pr_err("IOSS TRACE_CTL Verbosity Set Failed\n"); - goto out; - } - break; - - default: - pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit); - ret = -EINVAL; - break; - } - -out: - mutex_unlock(&(telm_conf->telem_trace_lock)); - return ret; -} - -static const struct telemetry_core_ops telm_pltops = { - .get_trace_verbosity = telemetry_plt_get_trace_verbosity, - .set_trace_verbosity = telemetry_plt_set_trace_verbosity, - .set_sampling_period = telemetry_plt_set_sampling_period, - .get_sampling_period = telemetry_plt_get_sampling_period, - .raw_read_eventlog = telemetry_plt_raw_read_eventlog, - .get_eventconfig = telemetry_plt_get_eventconfig, - .update_events = telemetry_plt_update_events, - .read_eventlog = telemetry_plt_read_eventlog, - .reset_events = telemetry_plt_reset_events, - .add_events = telemetry_plt_add_events, -}; - -static int telemetry_pltdrv_probe(struct platform_device *pdev) -{ - const struct x86_cpu_id *id; - void __iomem *mem; - int ret; - - id = x86_match_cpu(telemetry_cpu_ids); - if (!id) - return -ENODEV; - - telm_conf = (struct telemetry_plt_config *)id->driver_data; - - telm_conf->pmc = dev_get_drvdata(pdev->dev.parent); - - mem = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(mem)) - return PTR_ERR(mem); - - telm_conf->pss_config.regmap = mem; - - mem = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(mem)) - return PTR_ERR(mem); - - telm_conf->ioss_config.regmap = mem; - - telm_conf->scu = devm_intel_scu_ipc_dev_get(&pdev->dev); - if (!telm_conf->scu) { - ret = -EPROBE_DEFER; - goto out; - } - - mutex_init(&telm_conf->telem_lock); - mutex_init(&telm_conf->telem_trace_lock); - - ret = telemetry_setup(pdev); - if (ret) - goto out; - - ret = telemetry_set_pltdata(&telm_pltops, telm_conf); - if (ret) { - dev_err(&pdev->dev, "TELEMETRY Set Pltops Failed.\n"); - goto out; - } - - return 0; - -out: - dev_err(&pdev->dev, "TELEMETRY Setup Failed.\n"); - - return ret; -} - -static int telemetry_pltdrv_remove(struct platform_device *pdev) -{ - telemetry_clear_pltdata(); - return 0; -} - -static struct platform_driver telemetry_soc_driver = { - .probe = telemetry_pltdrv_probe, - .remove = telemetry_pltdrv_remove, - .driver = { - .name = DRIVER_NAME, - }, -}; - -static int __init telemetry_module_init(void) -{ - return platform_driver_register(&telemetry_soc_driver); -} - -static void __exit telemetry_module_exit(void) -{ - platform_driver_unregister(&telemetry_soc_driver); -} - -device_initcall(telemetry_module_init); -module_exit(telemetry_module_exit); - -MODULE_AUTHOR("Souvik Kumar Chakravarty "); -MODULE_DESCRIPTION("Intel SoC Telemetry Platform Driver"); -MODULE_VERSION(DRIVER_VERSION); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 075b559829d2b2f06d84d10f9712f6867b45c202 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:50 +0300 Subject: platform/x86: intel-uncore-frequency: Move to intel sub-directory Move Intel Uncore frequency driver to intel sub-directory to improve readability and rename it from intel-uncore-frequency.c to uncore-frequency.c. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-13-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 11 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-uncore-frequency.c | 451 -------------------------- drivers/platform/x86/intel/Kconfig | 12 + drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/uncore-frequency.c | 451 ++++++++++++++++++++++++++ 7 files changed, 466 insertions(+), 464 deletions(-) delete mode 100644 drivers/platform/x86/intel-uncore-frequency.c create mode 100644 drivers/platform/x86/intel/uncore-frequency.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index bb0fcf309cc8..5fe92c8e9214 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9565,7 +9565,7 @@ INTEL UNCORE FREQUENCY CONTROL M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel-uncore-frequency.c +F: drivers/platform/x86/intel/uncore-frequency.c INTEL VIRTUAL BUTTON DRIVER M: AceLan Kao diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a9acd44c2076..dc861ec59c3a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1112,17 +1112,6 @@ config INTEL_IPS source "drivers/platform/x86/intel_speed_select_if/Kconfig" -config INTEL_UNCORE_FREQ_CONTROL - tristate "Intel Uncore frequency control driver" - depends on X86_64 - help - This driver allows control of uncore frequency limits on - supported server platforms. - Uncore frequency controls RING/LLC (last-level cache) clocks. - - To compile this driver as a module, choose M here: the module - will be called intel-uncore-frequency. - config INTEL_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 15d0754363ea..dfc5cde88999 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -121,7 +121,6 @@ obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ -obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o diff --git a/drivers/platform/x86/intel-uncore-frequency.c b/drivers/platform/x86/intel-uncore-frequency.c deleted file mode 100644 index 3ee4c5c8a64f..000000000000 --- a/drivers/platform/x86/intel-uncore-frequency.c +++ /dev/null @@ -1,451 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Uncore Frequency Setting - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Provide interface to set MSR 620 at a granularity of per die. On CPU online, - * one control CPU is identified per die to read/write limit. This control CPU - * is changed, if the CPU state is changed to offline. When the last CPU is - * offline in a die then remove the sysfs object for that die. - * The majority of actual code is related to sysfs create and read/write - * attributes. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include -#include - -#define MSR_UNCORE_RATIO_LIMIT 0x620 -#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 - -/** - * struct uncore_data - Encapsulate all uncore data - * @stored_uncore_data: Last user changed MSR 620 value, which will be restored - * on system resume. - * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init - * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init - * @control_cpu: Designated CPU for a die to read/write - * @valid: Mark the data valid/invalid - * - * This structure is used to encapsulate all data related to uncore sysfs - * settings for a die/package. - */ -struct uncore_data { - struct kobject kobj; - struct completion kobj_unregister; - u64 stored_uncore_data; - u32 initial_min_freq_khz; - u32 initial_max_freq_khz; - int control_cpu; - bool valid; -}; - -#define to_uncore_data(a) container_of(a, struct uncore_data, kobj) - -/* Max instances for uncore data, one for each die */ -static int uncore_max_entries __read_mostly; -/* Storage for uncore data for all instances */ -static struct uncore_data *uncore_instances; -/* Root of the all uncore sysfs kobjs */ -static struct kobject *uncore_root_kobj; -/* Stores the CPU mask of the target CPUs to use during uncore read/write */ -static cpumask_t uncore_cpu_mask; -/* CPU online callback register instance */ -static enum cpuhp_state uncore_hp_state __read_mostly; -/* Mutex to control all mutual exclusions */ -static DEFINE_MUTEX(uncore_lock); - -struct uncore_attr { - struct attribute attr; - ssize_t (*show)(struct kobject *kobj, - struct attribute *attr, char *buf); - ssize_t (*store)(struct kobject *kobj, - struct attribute *attr, const char *c, ssize_t count); -}; - -#define define_one_uncore_ro(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0444, show_##_name, NULL) - -#define define_one_uncore_rw(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0644, show_##_name, store_##_name) - -#define show_uncore_data(member_name) \ - static ssize_t show_##member_name(struct kobject *kobj, \ - struct attribute *attr, \ - char *buf) \ - { \ - struct uncore_data *data = to_uncore_data(kobj); \ - return scnprintf(buf, PAGE_SIZE, "%u\n", \ - data->member_name); \ - } \ - define_one_uncore_ro(member_name) - -show_uncore_data(initial_min_freq_khz); -show_uncore_data(initial_max_freq_khz); - -/* Common function to read MSR 0x620 and read min/max */ -static int uncore_read_ratio(struct uncore_data *data, unsigned int *min, - unsigned int *max) -{ - u64 cap; - int ret; - - if (data->control_cpu < 0) - return -ENXIO; - - ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); - if (ret) - return ret; - - *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; - *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER; - - return 0; -} - -/* Common function to set min/max ratios to be used by sysfs callbacks */ -static int uncore_write_ratio(struct uncore_data *data, unsigned int input, - int set_max) -{ - int ret; - u64 cap; - - mutex_lock(&uncore_lock); - - if (data->control_cpu < 0) { - ret = -ENXIO; - goto finish_write; - } - - input /= UNCORE_FREQ_KHZ_MULTIPLIER; - if (!input || input > 0x7F) { - ret = -EINVAL; - goto finish_write; - } - - ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); - if (ret) - goto finish_write; - - if (set_max) { - cap &= ~0x7F; - cap |= input; - } else { - cap &= ~GENMASK(14, 8); - cap |= (input << 8); - } - - ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap); - if (ret) - goto finish_write; - - data->stored_uncore_data = cap; - -finish_write: - mutex_unlock(&uncore_lock); - - return ret; -} - -static ssize_t store_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, - const char *buf, ssize_t count, - int min_max) -{ - struct uncore_data *data = to_uncore_data(kobj); - unsigned int input; - - if (kstrtouint(buf, 10, &input)) - return -EINVAL; - - uncore_write_ratio(data, input, min_max); - - return count; -} - -static ssize_t show_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, - char *buf, int min_max) -{ - struct uncore_data *data = to_uncore_data(kobj); - unsigned int min, max; - int ret; - - mutex_lock(&uncore_lock); - ret = uncore_read_ratio(data, &min, &max); - mutex_unlock(&uncore_lock); - if (ret) - return ret; - - if (min_max) - return sprintf(buf, "%u\n", max); - - return sprintf(buf, "%u\n", min); -} - -#define store_uncore_min_max(name, min_max) \ - static ssize_t store_##name(struct kobject *kobj, \ - struct attribute *attr, \ - const char *buf, ssize_t count) \ - { \ - \ - return store_min_max_freq_khz(kobj, attr, buf, count, \ - min_max); \ - } - -#define show_uncore_min_max(name, min_max) \ - static ssize_t show_##name(struct kobject *kobj, \ - struct attribute *attr, char *buf) \ - { \ - \ - return show_min_max_freq_khz(kobj, attr, buf, min_max); \ - } - -store_uncore_min_max(min_freq_khz, 0); -store_uncore_min_max(max_freq_khz, 1); - -show_uncore_min_max(min_freq_khz, 0); -show_uncore_min_max(max_freq_khz, 1); - -define_one_uncore_rw(min_freq_khz); -define_one_uncore_rw(max_freq_khz); - -static struct attribute *uncore_attrs[] = { - &initial_min_freq_khz.attr, - &initial_max_freq_khz.attr, - &max_freq_khz.attr, - &min_freq_khz.attr, - NULL -}; - -static void uncore_sysfs_entry_release(struct kobject *kobj) -{ - struct uncore_data *data = to_uncore_data(kobj); - - complete(&data->kobj_unregister); -} - -static struct kobj_type uncore_ktype = { - .release = uncore_sysfs_entry_release, - .sysfs_ops = &kobj_sysfs_ops, - .default_attrs = uncore_attrs, -}; - -/* Caller provides protection */ -static struct uncore_data *uncore_get_instance(unsigned int cpu) -{ - int id = topology_logical_die_id(cpu); - - if (id >= 0 && id < uncore_max_entries) - return &uncore_instances[id]; - - return NULL; -} - -static void uncore_add_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (!data) { - mutex_unlock(&uncore_lock); - return; - } - - if (data->valid) { - /* control cpu changed */ - data->control_cpu = cpu; - } else { - char str[64]; - int ret; - - memset(data, 0, sizeof(*data)); - sprintf(str, "package_%02d_die_%02d", - topology_physical_package_id(cpu), - topology_die_id(cpu)); - - uncore_read_ratio(data, &data->initial_min_freq_khz, - &data->initial_max_freq_khz); - - init_completion(&data->kobj_unregister); - - ret = kobject_init_and_add(&data->kobj, &uncore_ktype, - uncore_root_kobj, str); - if (!ret) { - data->control_cpu = cpu; - data->valid = true; - } - } - mutex_unlock(&uncore_lock); -} - -/* Last CPU in this die is offline, make control cpu invalid */ -static void uncore_remove_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (data) - data->control_cpu = -1; - mutex_unlock(&uncore_lock); -} - -static int uncore_event_cpu_online(unsigned int cpu) -{ - int target; - - /* Check if there is an online cpu in the package for uncore MSR */ - target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu)); - if (target < nr_cpu_ids) - return 0; - - /* Use this CPU on this die as a control CPU */ - cpumask_set_cpu(cpu, &uncore_cpu_mask); - uncore_add_die_entry(cpu); - - return 0; -} - -static int uncore_event_cpu_offline(unsigned int cpu) -{ - int target; - - /* Check if existing cpu is used for uncore MSRs */ - if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) - return 0; - - /* Find a new cpu to set uncore MSR */ - target = cpumask_any_but(topology_die_cpumask(cpu), cpu); - - if (target < nr_cpu_ids) { - cpumask_set_cpu(target, &uncore_cpu_mask); - uncore_add_die_entry(target); - } else { - uncore_remove_die_entry(cpu); - } - - return 0; -} - -static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, - void *_unused) -{ - int cpu; - - switch (mode) { - case PM_POST_HIBERNATION: - case PM_POST_RESTORE: - case PM_POST_SUSPEND: - for_each_cpu(cpu, &uncore_cpu_mask) { - struct uncore_data *data; - int ret; - - data = uncore_get_instance(cpu); - if (!data || !data->valid || !data->stored_uncore_data) - continue; - - ret = wrmsrl_on_cpu(cpu, MSR_UNCORE_RATIO_LIMIT, - data->stored_uncore_data); - if (ret) - return ret; - } - break; - default: - break; - } - return 0; -} - -static struct notifier_block uncore_pm_nb = { - .notifier_call = uncore_pm_notify, -}; - -static const struct x86_cpu_id intel_uncore_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), - X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), - {} -}; - -static int __init intel_uncore_init(void) -{ - const struct x86_cpu_id *id; - int ret; - - id = x86_match_cpu(intel_uncore_cpu_ids); - if (!id) - return -ENODEV; - - uncore_max_entries = topology_max_packages() * - topology_max_die_per_package(); - uncore_instances = kcalloc(uncore_max_entries, - sizeof(*uncore_instances), GFP_KERNEL); - if (!uncore_instances) - return -ENOMEM; - - uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", - &cpu_subsys.dev_root->kobj); - if (!uncore_root_kobj) { - ret = -ENOMEM; - goto err_free; - } - - ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, - "platform/x86/uncore-freq:online", - uncore_event_cpu_online, - uncore_event_cpu_offline); - if (ret < 0) - goto err_rem_kobj; - - uncore_hp_state = ret; - - ret = register_pm_notifier(&uncore_pm_nb); - if (ret) - goto err_rem_state; - - return 0; - -err_rem_state: - cpuhp_remove_state(uncore_hp_state); -err_rem_kobj: - kobject_put(uncore_root_kobj); -err_free: - kfree(uncore_instances); - - return ret; -} -module_init(intel_uncore_init) - -static void __exit intel_uncore_exit(void) -{ - int i; - - unregister_pm_notifier(&uncore_pm_nb); - cpuhp_remove_state(uncore_hp_state); - for (i = 0; i < uncore_max_entries; ++i) { - if (uncore_instances[i].valid) { - kobject_put(&uncore_instances[i].kobj); - wait_for_completion(&uncore_instances[i].kobj_unregister); - } - } - kobject_put(uncore_root_kobj); - kfree(uncore_instances); -} -module_exit(intel_uncore_exit) - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver"); diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 701479386305..7e3eb4404f7b 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -98,4 +98,16 @@ config INTEL_TURBO_MAX_3 This driver is only required when the system is not using Hardware P-States (HWP). In HWP mode, priority can be read from ACPI tables. +config INTEL_UNCORE_FREQ_CONTROL + tristate "Intel Uncore frequency control driver" + depends on X86_64 + help + This driver allows control of Uncore frequency limits on + supported server platforms. + + Uncore frequency controls RING/LLC (last-level cache) clocks. + + To compile this driver as a module, choose M here: the module + will be called intel-uncore-frequency. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 1ecdf774e490..8080e850c1fb 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -28,3 +28,5 @@ intel-smartconnect-y := smartconnect.o obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o intel_turbo_max_3-y := turbo_max_3.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o +intel-uncore-frequency-y := uncore-frequency.o +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o diff --git a/drivers/platform/x86/intel/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency.c new file mode 100644 index 000000000000..3ee4c5c8a64f --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Uncore Frequency Setting + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Provide interface to set MSR 620 at a granularity of per die. On CPU online, + * one control CPU is identified per die to read/write limit. This control CPU + * is changed, if the CPU state is changed to offline. When the last CPU is + * offline in a die then remove the sysfs object for that die. + * The majority of actual code is related to sysfs create and read/write + * attributes. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include +#include + +#define MSR_UNCORE_RATIO_LIMIT 0x620 +#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 + +/** + * struct uncore_data - Encapsulate all uncore data + * @stored_uncore_data: Last user changed MSR 620 value, which will be restored + * on system resume. + * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init + * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init + * @control_cpu: Designated CPU for a die to read/write + * @valid: Mark the data valid/invalid + * + * This structure is used to encapsulate all data related to uncore sysfs + * settings for a die/package. + */ +struct uncore_data { + struct kobject kobj; + struct completion kobj_unregister; + u64 stored_uncore_data; + u32 initial_min_freq_khz; + u32 initial_max_freq_khz; + int control_cpu; + bool valid; +}; + +#define to_uncore_data(a) container_of(a, struct uncore_data, kobj) + +/* Max instances for uncore data, one for each die */ +static int uncore_max_entries __read_mostly; +/* Storage for uncore data for all instances */ +static struct uncore_data *uncore_instances; +/* Root of the all uncore sysfs kobjs */ +static struct kobject *uncore_root_kobj; +/* Stores the CPU mask of the target CPUs to use during uncore read/write */ +static cpumask_t uncore_cpu_mask; +/* CPU online callback register instance */ +static enum cpuhp_state uncore_hp_state __read_mostly; +/* Mutex to control all mutual exclusions */ +static DEFINE_MUTEX(uncore_lock); + +struct uncore_attr { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, + struct attribute *attr, char *buf); + ssize_t (*store)(struct kobject *kobj, + struct attribute *attr, const char *c, ssize_t count); +}; + +#define define_one_uncore_ro(_name) \ +static struct uncore_attr _name = \ +__ATTR(_name, 0444, show_##_name, NULL) + +#define define_one_uncore_rw(_name) \ +static struct uncore_attr _name = \ +__ATTR(_name, 0644, show_##_name, store_##_name) + +#define show_uncore_data(member_name) \ + static ssize_t show_##member_name(struct kobject *kobj, \ + struct attribute *attr, \ + char *buf) \ + { \ + struct uncore_data *data = to_uncore_data(kobj); \ + return scnprintf(buf, PAGE_SIZE, "%u\n", \ + data->member_name); \ + } \ + define_one_uncore_ro(member_name) + +show_uncore_data(initial_min_freq_khz); +show_uncore_data(initial_max_freq_khz); + +/* Common function to read MSR 0x620 and read min/max */ +static int uncore_read_ratio(struct uncore_data *data, unsigned int *min, + unsigned int *max) +{ + u64 cap; + int ret; + + if (data->control_cpu < 0) + return -ENXIO; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); + if (ret) + return ret; + + *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; + *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER; + + return 0; +} + +/* Common function to set min/max ratios to be used by sysfs callbacks */ +static int uncore_write_ratio(struct uncore_data *data, unsigned int input, + int set_max) +{ + int ret; + u64 cap; + + mutex_lock(&uncore_lock); + + if (data->control_cpu < 0) { + ret = -ENXIO; + goto finish_write; + } + + input /= UNCORE_FREQ_KHZ_MULTIPLIER; + if (!input || input > 0x7F) { + ret = -EINVAL; + goto finish_write; + } + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); + if (ret) + goto finish_write; + + if (set_max) { + cap &= ~0x7F; + cap |= input; + } else { + cap &= ~GENMASK(14, 8); + cap |= (input << 8); + } + + ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap); + if (ret) + goto finish_write; + + data->stored_uncore_data = cap; + +finish_write: + mutex_unlock(&uncore_lock); + + return ret; +} + +static ssize_t store_min_max_freq_khz(struct kobject *kobj, + struct attribute *attr, + const char *buf, ssize_t count, + int min_max) +{ + struct uncore_data *data = to_uncore_data(kobj); + unsigned int input; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + uncore_write_ratio(data, input, min_max); + + return count; +} + +static ssize_t show_min_max_freq_khz(struct kobject *kobj, + struct attribute *attr, + char *buf, int min_max) +{ + struct uncore_data *data = to_uncore_data(kobj); + unsigned int min, max; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read_ratio(data, &min, &max); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + if (min_max) + return sprintf(buf, "%u\n", max); + + return sprintf(buf, "%u\n", min); +} + +#define store_uncore_min_max(name, min_max) \ + static ssize_t store_##name(struct kobject *kobj, \ + struct attribute *attr, \ + const char *buf, ssize_t count) \ + { \ + \ + return store_min_max_freq_khz(kobj, attr, buf, count, \ + min_max); \ + } + +#define show_uncore_min_max(name, min_max) \ + static ssize_t show_##name(struct kobject *kobj, \ + struct attribute *attr, char *buf) \ + { \ + \ + return show_min_max_freq_khz(kobj, attr, buf, min_max); \ + } + +store_uncore_min_max(min_freq_khz, 0); +store_uncore_min_max(max_freq_khz, 1); + +show_uncore_min_max(min_freq_khz, 0); +show_uncore_min_max(max_freq_khz, 1); + +define_one_uncore_rw(min_freq_khz); +define_one_uncore_rw(max_freq_khz); + +static struct attribute *uncore_attrs[] = { + &initial_min_freq_khz.attr, + &initial_max_freq_khz.attr, + &max_freq_khz.attr, + &min_freq_khz.attr, + NULL +}; + +static void uncore_sysfs_entry_release(struct kobject *kobj) +{ + struct uncore_data *data = to_uncore_data(kobj); + + complete(&data->kobj_unregister); +} + +static struct kobj_type uncore_ktype = { + .release = uncore_sysfs_entry_release, + .sysfs_ops = &kobj_sysfs_ops, + .default_attrs = uncore_attrs, +}; + +/* Caller provides protection */ +static struct uncore_data *uncore_get_instance(unsigned int cpu) +{ + int id = topology_logical_die_id(cpu); + + if (id >= 0 && id < uncore_max_entries) + return &uncore_instances[id]; + + return NULL; +} + +static void uncore_add_die_entry(int cpu) +{ + struct uncore_data *data; + + mutex_lock(&uncore_lock); + data = uncore_get_instance(cpu); + if (!data) { + mutex_unlock(&uncore_lock); + return; + } + + if (data->valid) { + /* control cpu changed */ + data->control_cpu = cpu; + } else { + char str[64]; + int ret; + + memset(data, 0, sizeof(*data)); + sprintf(str, "package_%02d_die_%02d", + topology_physical_package_id(cpu), + topology_die_id(cpu)); + + uncore_read_ratio(data, &data->initial_min_freq_khz, + &data->initial_max_freq_khz); + + init_completion(&data->kobj_unregister); + + ret = kobject_init_and_add(&data->kobj, &uncore_ktype, + uncore_root_kobj, str); + if (!ret) { + data->control_cpu = cpu; + data->valid = true; + } + } + mutex_unlock(&uncore_lock); +} + +/* Last CPU in this die is offline, make control cpu invalid */ +static void uncore_remove_die_entry(int cpu) +{ + struct uncore_data *data; + + mutex_lock(&uncore_lock); + data = uncore_get_instance(cpu); + if (data) + data->control_cpu = -1; + mutex_unlock(&uncore_lock); +} + +static int uncore_event_cpu_online(unsigned int cpu) +{ + int target; + + /* Check if there is an online cpu in the package for uncore MSR */ + target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu)); + if (target < nr_cpu_ids) + return 0; + + /* Use this CPU on this die as a control CPU */ + cpumask_set_cpu(cpu, &uncore_cpu_mask); + uncore_add_die_entry(cpu); + + return 0; +} + +static int uncore_event_cpu_offline(unsigned int cpu) +{ + int target; + + /* Check if existing cpu is used for uncore MSRs */ + if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) + return 0; + + /* Find a new cpu to set uncore MSR */ + target = cpumask_any_but(topology_die_cpumask(cpu), cpu); + + if (target < nr_cpu_ids) { + cpumask_set_cpu(target, &uncore_cpu_mask); + uncore_add_die_entry(target); + } else { + uncore_remove_die_entry(cpu); + } + + return 0; +} + +static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, + void *_unused) +{ + int cpu; + + switch (mode) { + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + for_each_cpu(cpu, &uncore_cpu_mask) { + struct uncore_data *data; + int ret; + + data = uncore_get_instance(cpu); + if (!data || !data->valid || !data->stored_uncore_data) + continue; + + ret = wrmsrl_on_cpu(cpu, MSR_UNCORE_RATIO_LIMIT, + data->stored_uncore_data); + if (ret) + return ret; + } + break; + default: + break; + } + return 0; +} + +static struct notifier_block uncore_pm_nb = { + .notifier_call = uncore_pm_notify, +}; + +static const struct x86_cpu_id intel_uncore_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), + {} +}; + +static int __init intel_uncore_init(void) +{ + const struct x86_cpu_id *id; + int ret; + + id = x86_match_cpu(intel_uncore_cpu_ids); + if (!id) + return -ENODEV; + + uncore_max_entries = topology_max_packages() * + topology_max_die_per_package(); + uncore_instances = kcalloc(uncore_max_entries, + sizeof(*uncore_instances), GFP_KERNEL); + if (!uncore_instances) + return -ENOMEM; + + uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", + &cpu_subsys.dev_root->kobj); + if (!uncore_root_kobj) { + ret = -ENOMEM; + goto err_free; + } + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "platform/x86/uncore-freq:online", + uncore_event_cpu_online, + uncore_event_cpu_offline); + if (ret < 0) + goto err_rem_kobj; + + uncore_hp_state = ret; + + ret = register_pm_notifier(&uncore_pm_nb); + if (ret) + goto err_rem_state; + + return 0; + +err_rem_state: + cpuhp_remove_state(uncore_hp_state); +err_rem_kobj: + kobject_put(uncore_root_kobj); +err_free: + kfree(uncore_instances); + + return ret; +} +module_init(intel_uncore_init) + +static void __exit intel_uncore_exit(void) +{ + int i; + + unregister_pm_notifier(&uncore_pm_nb); + cpuhp_remove_state(uncore_hp_state); + for (i = 0; i < uncore_max_entries; ++i) { + if (uncore_instances[i].valid) { + kobject_put(&uncore_instances[i].kobj); + wait_for_completion(&uncore_instances[i].kobj_unregister); + } + } + kobject_put(uncore_root_kobj); + kfree(uncore_instances); +} +module_exit(intel_uncore_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver"); -- cgit v1.2.3-59-g8ed1b From 6b1e482898e841b577d1d22b97fb65ac8ffacd9a Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:51 +0300 Subject: platform/x86: intel_speed_select_if: Move to intel sub-directory Move Intel Speed Select interface driver to intel sub-directory to improve readability and rename it from intel_speed_select_if to speed_select_if. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-14-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 2 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/speed_select_if/Kconfig | 17 + .../platform/x86/intel/speed_select_if/Makefile | 10 + .../x86/intel/speed_select_if/isst_if_common.c | 741 +++++++++++++++++++++ .../x86/intel/speed_select_if/isst_if_common.h | 72 ++ .../x86/intel/speed_select_if/isst_if_mbox_msr.c | 214 ++++++ .../x86/intel/speed_select_if/isst_if_mbox_pci.c | 227 +++++++ .../x86/intel/speed_select_if/isst_if_mmio.c | 200 ++++++ drivers/platform/x86/intel_speed_select_if/Kconfig | 17 - .../platform/x86/intel_speed_select_if/Makefile | 10 - .../x86/intel_speed_select_if/isst_if_common.c | 741 --------------------- .../x86/intel_speed_select_if/isst_if_common.h | 72 -- .../x86/intel_speed_select_if/isst_if_mbox_msr.c | 214 ------ .../x86/intel_speed_select_if/isst_if_mbox_pci.c | 227 ------- .../x86/intel_speed_select_if/isst_if_mmio.c | 200 ------ 19 files changed, 1484 insertions(+), 1485 deletions(-) create mode 100644 drivers/platform/x86/intel/speed_select_if/Kconfig create mode 100644 drivers/platform/x86/intel/speed_select_if/Makefile create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_common.c create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_common.h create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/Kconfig delete mode 100644 drivers/platform/x86/intel_speed_select_if/Makefile delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_common.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_common.h delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 5fe92c8e9214..cf29f7154889 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9538,7 +9538,7 @@ INTEL SPEED SELECT TECHNOLOGY M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel_speed_select_if/ +F: drivers/platform/x86/intel/speed_select_if/ F: include/uapi/linux/isst_if.h F: tools/power/x86/intel-speed-select/ diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index dc861ec59c3a..935116ef2df9 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1110,8 +1110,6 @@ config INTEL_IPS functionality. If in doubt, say Y here; it will only load on supported platforms. -source "drivers/platform/x86/intel_speed_select_if/Kconfig" - config INTEL_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dfc5cde88999..dd3f1e683f0d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -120,7 +120,6 @@ obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o -obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 7e3eb4404f7b..2622653af37a 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -21,6 +21,7 @@ source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" source "drivers/platform/x86/intel/pmc/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" +source "drivers/platform/x86/intel/speed_select_if/Kconfig" source "drivers/platform/x86/intel/telemetry/Kconfig" config INTEL_BXTWC_PMIC_TMU diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 8080e850c1fb..f5ac4a5f953f 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMC_CORE) += pmc/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ # Intel PMIC / PMC / P-Unit drivers diff --git a/drivers/platform/x86/intel/speed_select_if/Kconfig b/drivers/platform/x86/intel/speed_select_if/Kconfig new file mode 100644 index 000000000000..ce3e3dc076d2 --- /dev/null +++ b/drivers/platform/x86/intel/speed_select_if/Kconfig @@ -0,0 +1,17 @@ +menu "Intel Speed Select Technology interface support" + depends on PCI + depends on X86_64 || COMPILE_TEST + +config INTEL_SPEED_SELECT_INTERFACE + tristate "Intel(R) Speed Select Technology interface drivers" + help + This config enables the Intel(R) Speed Select Technology interface + drivers. The Intel(R) speed select technology features are non + architectural and only supported on specific Xeon(R) servers. + These drivers provide interface to directly communicate with hardware + via MMIO and Mail boxes to enumerate and control all the speed select + features. + + Enable this config, if there is a need to enable and control the + Intel(R) Speed Select Technology features from the user space. +endmenu diff --git a/drivers/platform/x86/intel/speed_select_if/Makefile b/drivers/platform/x86/intel/speed_select_if/Makefile new file mode 100644 index 000000000000..856076206f35 --- /dev/null +++ b/drivers/platform/x86/intel/speed_select_if/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile - Intel Speed Select Interface drivers +# Copyright (c) 2019, Intel Corporation. +# + +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c new file mode 100644 index 000000000000..8a4d52a9028d --- /dev/null +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c @@ -0,0 +1,741 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Common functions + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +#define MSR_THREAD_ID_INFO 0x53 +#define MSR_CPU_BUS_NUMBER 0x128 + +static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX]; + +static int punit_msr_white_list[] = { + MSR_TURBO_RATIO_LIMIT, + MSR_CONFIG_TDP_CONTROL, + MSR_TURBO_RATIO_LIMIT1, + MSR_TURBO_RATIO_LIMIT2, +}; + +struct isst_valid_cmd_ranges { + u16 cmd; + u16 sub_cmd_beg; + u16 sub_cmd_end; +}; + +struct isst_cmd_set_req_type { + u16 cmd; + u16 sub_cmd; + u16 param; +}; + +static const struct isst_valid_cmd_ranges isst_valid_cmds[] = { + {0xD0, 0x00, 0x03}, + {0x7F, 0x00, 0x0B}, + {0x7F, 0x10, 0x12}, + {0x7F, 0x20, 0x23}, + {0x94, 0x03, 0x03}, + {0x95, 0x03, 0x03}, +}; + +static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = { + {0xD0, 0x00, 0x08}, + {0xD0, 0x01, 0x08}, + {0xD0, 0x02, 0x08}, + {0xD0, 0x03, 0x08}, + {0x7F, 0x02, 0x00}, + {0x7F, 0x08, 0x00}, + {0x95, 0x03, 0x03}, +}; + +struct isst_cmd { + struct hlist_node hnode; + u64 data; + u32 cmd; + int cpu; + int mbox_cmd_type; + u32 param; +}; + +static DECLARE_HASHTABLE(isst_hash, 8); +static DEFINE_MUTEX(isst_hash_lock); + +static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param, + u32 data) +{ + struct isst_cmd *sst_cmd; + + sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL); + if (!sst_cmd) + return -ENOMEM; + + sst_cmd->cpu = cpu; + sst_cmd->cmd = cmd; + sst_cmd->mbox_cmd_type = mbox_cmd_type; + sst_cmd->param = param; + sst_cmd->data = data; + + hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd); + + return 0; +} + +static void isst_delete_hash(void) +{ + struct isst_cmd *sst_cmd; + struct hlist_node *tmp; + int i; + + hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) { + hash_del(&sst_cmd->hnode); + kfree(sst_cmd); + } +} + +/** + * isst_store_cmd() - Store command to a hash table + * @cmd: Mailbox command. + * @sub_cmd: Mailbox sub-command or MSR id. + * @mbox_cmd_type: Mailbox or MSR command. + * @param: Mailbox parameter. + * @data: Mailbox request data or MSR data. + * + * Stores the command to a hash table if there is no such command already + * stored. If already stored update the latest parameter and data for the + * command. + * + * Return: Return result of store to hash table, 0 for success, others for + * failure. + */ +int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type, + u32 param, u64 data) +{ + struct isst_cmd *sst_cmd; + int full_cmd, ret; + + full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16; + full_cmd |= (sub_cmd & GENMASK_ULL(15, 0)); + mutex_lock(&isst_hash_lock); + hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) { + if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu && + sst_cmd->mbox_cmd_type == mbox_cmd_type) { + sst_cmd->param = param; + sst_cmd->data = data; + mutex_unlock(&isst_hash_lock); + return 0; + } + } + + ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data); + mutex_unlock(&isst_hash_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(isst_store_cmd); + +static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb, + struct isst_cmd *sst_cmd) +{ + struct isst_if_mbox_cmd mbox_cmd; + int wr_only; + + mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16; + mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0); + mbox_cmd.parameter = sst_cmd->param; + mbox_cmd.req_data = sst_cmd->data; + mbox_cmd.logical_cpu = sst_cmd->cpu; + (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1); +} + +/** + * isst_resume_common() - Process Resume request + * + * On resume replay all mailbox commands and MSRs. + * + * Return: None. + */ +void isst_resume_common(void) +{ + struct isst_cmd *sst_cmd; + int i; + + hash_for_each(isst_hash, i, sst_cmd, hnode) { + struct isst_if_cmd_cb *cb; + + if (sst_cmd->mbox_cmd_type) { + cb = &punit_callbacks[ISST_IF_DEV_MBOX]; + if (cb->registered) + isst_mbox_resume_command(cb, sst_cmd); + } else { + wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd, + sst_cmd->data); + } + } +} +EXPORT_SYMBOL_GPL(isst_resume_common); + +static void isst_restore_msr_local(int cpu) +{ + struct isst_cmd *sst_cmd; + int i; + + mutex_lock(&isst_hash_lock); + for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { + if (!punit_msr_white_list[i]) + break; + + hash_for_each_possible(isst_hash, sst_cmd, hnode, + punit_msr_white_list[i]) { + if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu) + wrmsrl_safe(sst_cmd->cmd, sst_cmd->data); + } + } + mutex_unlock(&isst_hash_lock); +} + +/** + * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands + * @cmd: Pointer to the command structure to verify. + * + * Invalid command to PUNIT to may result in instability of the platform. + * This function has a whitelist of commands, which are allowed. + * + * Return: Return true if the command is invalid, else false. + */ +bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd) +{ + int i; + + if (cmd->logical_cpu >= nr_cpu_ids) + return true; + + for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) { + if (cmd->command == isst_valid_cmds[i].cmd && + (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg && + cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) { + return false; + } + } + + return true; +} +EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid); + +/** + * isst_if_mbox_cmd_set_req() - Check mailbox command is a set request + * @cmd: Pointer to the command structure to verify. + * + * Check if the given mail box level is set request and not a get request. + * + * Return: Return true if the command is set_req, else false. + */ +bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) { + if (cmd->command == isst_cmd_set_reqs[i].cmd && + cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd && + cmd->parameter == isst_cmd_set_reqs[i].param) { + return true; + } + } + + return false; +} +EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req); + +static int isst_if_get_platform_info(void __user *argp) +{ + struct isst_if_platform_info info; + + info.api_version = ISST_IF_API_VERSION, + info.driver_version = ISST_IF_DRIVER_VERSION, + info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT, + info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered; + info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered; + + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + + +struct isst_if_cpu_info { + /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ + int bus_info[2]; + struct pci_dev *pci_dev[2]; + int punit_cpu_id; + int numa_node; +}; + +static struct isst_if_cpu_info *isst_cpu_info; +#define ISST_MAX_PCI_DOMAINS 8 + +static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) +{ + struct pci_dev *matched_pci_dev = NULL; + struct pci_dev *pci_dev = NULL; + int no_matches = 0; + int i, bus_number; + + if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || + cpu >= num_possible_cpus()) + return NULL; + + bus_number = isst_cpu_info[cpu].bus_info[bus_no]; + if (bus_number < 0) + return NULL; + + for (i = 0; i < ISST_MAX_PCI_DOMAINS; ++i) { + struct pci_dev *_pci_dev; + int node; + + _pci_dev = pci_get_domain_bus_and_slot(i, bus_number, PCI_DEVFN(dev, fn)); + if (!_pci_dev) + continue; + + ++no_matches; + if (!matched_pci_dev) + matched_pci_dev = _pci_dev; + + node = dev_to_node(&_pci_dev->dev); + if (node == NUMA_NO_NODE) { + pr_info("Fail to get numa node for CPU:%d bus:%d dev:%d fn:%d\n", + cpu, bus_no, dev, fn); + continue; + } + + if (node == isst_cpu_info[cpu].numa_node) { + pci_dev = _pci_dev; + break; + } + } + + /* + * If there is no numa matched pci_dev, then there can be following cases: + * 1. CONFIG_NUMA is not defined: In this case if there is only single device + * match, then we don't need numa information. Simply return last match. + * Othewise return NULL. + * 2. NUMA information is not exposed via _SEG method. In this case it is similar + * to case 1. + * 3. Numa information doesn't match with CPU numa node and more than one match + * return NULL. + */ + if (!pci_dev && no_matches == 1) + pci_dev = matched_pci_dev; + + return pci_dev; +} + +/** + * isst_if_get_pci_dev() - Get the PCI device instance for a CPU + * @cpu: Logical CPU number. + * @bus_number: The bus number assigned by the hardware. + * @dev: The device number assigned by the hardware. + * @fn: The function number assigned by the hardware. + * + * Using cached bus information, find out the PCI device for a bus number, + * device and function. + * + * Return: Return pci_dev pointer or NULL. + */ +struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) +{ + struct pci_dev *pci_dev; + + if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || + cpu >= num_possible_cpus()) + return NULL; + + pci_dev = isst_cpu_info[cpu].pci_dev[bus_no]; + + if (pci_dev && pci_dev->devfn == PCI_DEVFN(dev, fn)) + return pci_dev; + + return _isst_if_get_pci_dev(cpu, bus_no, dev, fn); +} +EXPORT_SYMBOL_GPL(isst_if_get_pci_dev); + +static int isst_if_cpu_online(unsigned int cpu) +{ + u64 data; + int ret; + + isst_cpu_info[cpu].numa_node = cpu_to_node(cpu); + + ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data); + if (ret) { + /* This is not a fatal error on MSR mailbox only I/F */ + isst_cpu_info[cpu].bus_info[0] = -1; + isst_cpu_info[cpu].bus_info[1] = -1; + } else { + isst_cpu_info[cpu].bus_info[0] = data & 0xff; + isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff; + isst_cpu_info[cpu].pci_dev[0] = _isst_if_get_pci_dev(cpu, 0, 0, 1); + isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1); + } + + ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data); + if (ret) { + isst_cpu_info[cpu].punit_cpu_id = -1; + return ret; + } + isst_cpu_info[cpu].punit_cpu_id = data; + + isst_restore_msr_local(cpu); + + return 0; +} + +static int isst_if_online_id; + +static int isst_if_cpu_info_init(void) +{ + int ret; + + isst_cpu_info = kcalloc(num_possible_cpus(), + sizeof(*isst_cpu_info), + GFP_KERNEL); + if (!isst_cpu_info) + return -ENOMEM; + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "platform/x86/isst-if:online", + isst_if_cpu_online, NULL); + if (ret < 0) { + kfree(isst_cpu_info); + return ret; + } + + isst_if_online_id = ret; + + return 0; +} + +static void isst_if_cpu_info_exit(void) +{ + cpuhp_remove_state(isst_if_online_id); + kfree(isst_cpu_info); +}; + +static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_cpu_map *cpu_map; + + cpu_map = (struct isst_if_cpu_map *)cmd_ptr; + if (cpu_map->logical_cpu >= nr_cpu_ids || + cpu_map->logical_cpu >= num_possible_cpus()) + return -EINVAL; + + *write_only = 0; + cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id; + + return 0; +} + +static bool match_punit_msr_white_list(int msr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { + if (punit_msr_white_list[i] == msr) + return true; + } + + return false; +} + +static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_msr_cmd *msr_cmd; + int ret; + + msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr; + + if (!match_punit_msr_white_list(msr_cmd->msr)) + return -EINVAL; + + if (msr_cmd->logical_cpu >= nr_cpu_ids) + return -EINVAL; + + if (msr_cmd->read_write) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu, + msr_cmd->msr, + msr_cmd->data); + *write_only = 1; + if (!ret && !resume) + ret = isst_store_cmd(0, msr_cmd->msr, + msr_cmd->logical_cpu, + 0, 0, msr_cmd->data); + } else { + u64 data; + + ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu, + msr_cmd->msr, &data); + if (!ret) { + msr_cmd->data = data; + *write_only = 0; + } + } + + + return ret; +} + +static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb) +{ + unsigned char __user *ptr; + u32 cmd_count; + u8 *cmd_ptr; + long ret; + int i; + + /* Each multi command has u32 command count as the first field */ + if (copy_from_user(&cmd_count, argp, sizeof(cmd_count))) + return -EFAULT; + + if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT) + return -EINVAL; + + cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL); + if (!cmd_ptr) + return -ENOMEM; + + /* cb->offset points to start of the command after the command count */ + ptr = argp + cb->offset; + + for (i = 0; i < cmd_count; ++i) { + int wr_only; + + if (signal_pending(current)) { + ret = -EINTR; + break; + } + + if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) { + ret = -EFAULT; + break; + } + + ret = cb->cmd_callback(cmd_ptr, &wr_only, 0); + if (ret) + break; + + if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) { + ret = -EFAULT; + break; + } + + ptr += cb->cmd_size; + } + + kfree(cmd_ptr); + + return i ? i : ret; +} + +static long isst_if_def_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct isst_if_cmd_cb cmd_cb; + struct isst_if_cmd_cb *cb; + long ret = -ENOTTY; + + switch (cmd) { + case ISST_IF_GET_PLATFORM_INFO: + ret = isst_if_get_platform_info(argp); + break; + case ISST_IF_GET_PHY_ID: + cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map); + cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map); + cmd_cb.cmd_callback = isst_if_proc_phyid_req; + ret = isst_if_exec_multi_cmd(argp, &cmd_cb); + break; + case ISST_IF_IO_CMD: + cb = &punit_callbacks[ISST_IF_DEV_MMIO]; + if (cb->registered) + ret = isst_if_exec_multi_cmd(argp, cb); + break; + case ISST_IF_MBOX_COMMAND: + cb = &punit_callbacks[ISST_IF_DEV_MBOX]; + if (cb->registered) + ret = isst_if_exec_multi_cmd(argp, cb); + break; + case ISST_IF_MSR_COMMAND: + cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd); + cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd); + cmd_cb.cmd_callback = isst_if_msr_cmd_req; + ret = isst_if_exec_multi_cmd(argp, &cmd_cb); + break; + default: + break; + } + + return ret; +} + +static DEFINE_MUTEX(punit_misc_dev_lock); +static int misc_usage_count; +static int misc_device_ret; +static int misc_device_open; + +static int isst_if_open(struct inode *inode, struct file *file) +{ + int i, ret = 0; + + /* Fail open, if a module is going away */ + mutex_lock(&punit_misc_dev_lock); + for (i = 0; i < ISST_IF_DEV_MAX; ++i) { + struct isst_if_cmd_cb *cb = &punit_callbacks[i]; + + if (cb->registered && !try_module_get(cb->owner)) { + ret = -ENODEV; + break; + } + } + if (ret) { + int j; + + for (j = 0; j < i; ++j) { + struct isst_if_cmd_cb *cb; + + cb = &punit_callbacks[j]; + if (cb->registered) + module_put(cb->owner); + } + } else { + misc_device_open++; + } + mutex_unlock(&punit_misc_dev_lock); + + return ret; +} + +static int isst_if_relase(struct inode *inode, struct file *f) +{ + int i; + + mutex_lock(&punit_misc_dev_lock); + misc_device_open--; + for (i = 0; i < ISST_IF_DEV_MAX; ++i) { + struct isst_if_cmd_cb *cb = &punit_callbacks[i]; + + if (cb->registered) + module_put(cb->owner); + } + mutex_unlock(&punit_misc_dev_lock); + + return 0; +} + +static const struct file_operations isst_if_char_driver_ops = { + .open = isst_if_open, + .unlocked_ioctl = isst_if_def_ioctl, + .release = isst_if_relase, +}; + +static struct miscdevice isst_if_char_driver = { + .minor = MISC_DYNAMIC_MINOR, + .name = "isst_interface", + .fops = &isst_if_char_driver_ops, +}; + +/** + * isst_if_cdev_register() - Register callback for IOCTL + * @device_type: The device type this callback handling. + * @cb: Callback structure. + * + * This function registers a callback to device type. On very first call + * it will register a misc device, which is used for user kernel interface. + * Other calls simply increment ref count. Registry will fail, if the user + * already opened misc device for operation. Also if the misc device + * creation failed, then it will not try again and all callers will get + * failure code. + * + * Return: Return the return value from the misc creation device or -EINVAL + * for unsupported device type. + */ +int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb) +{ + if (misc_device_ret) + return misc_device_ret; + + if (device_type >= ISST_IF_DEV_MAX) + return -EINVAL; + + mutex_lock(&punit_misc_dev_lock); + if (misc_device_open) { + mutex_unlock(&punit_misc_dev_lock); + return -EAGAIN; + } + if (!misc_usage_count) { + int ret; + + misc_device_ret = misc_register(&isst_if_char_driver); + if (misc_device_ret) + goto unlock_exit; + + ret = isst_if_cpu_info_init(); + if (ret) { + misc_deregister(&isst_if_char_driver); + misc_device_ret = ret; + goto unlock_exit; + } + } + memcpy(&punit_callbacks[device_type], cb, sizeof(*cb)); + punit_callbacks[device_type].registered = 1; + misc_usage_count++; +unlock_exit: + mutex_unlock(&punit_misc_dev_lock); + + return misc_device_ret; +} +EXPORT_SYMBOL_GPL(isst_if_cdev_register); + +/** + * isst_if_cdev_unregister() - Unregister callback for IOCTL + * @device_type: The device type to unregister. + * + * This function unregisters the previously registered callback. If this + * is the last callback unregistering, then misc device is removed. + * + * Return: None. + */ +void isst_if_cdev_unregister(int device_type) +{ + mutex_lock(&punit_misc_dev_lock); + misc_usage_count--; + punit_callbacks[device_type].registered = 0; + if (device_type == ISST_IF_DEV_MBOX) + isst_delete_hash(); + if (!misc_usage_count && !misc_device_ret) { + misc_deregister(&isst_if_char_driver); + isst_if_cpu_info_exit(); + } + mutex_unlock(&punit_misc_dev_lock); +} +EXPORT_SYMBOL_GPL(isst_if_cdev_unregister); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.h b/drivers/platform/x86/intel/speed_select_if/isst_if_common.h new file mode 100644 index 000000000000..fdecdae248d7 --- /dev/null +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Speed Select Interface: Drivers Internal defines + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#ifndef __ISST_IF_COMMON_H +#define __ISST_IF_COMMON_H + +#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_0 0x3451 +#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0 0x3459 + +#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_1 0x3251 +#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1 0x3259 + +/* + * Validate maximum commands in a single request. + * This is enough to handle command to every core in one ioctl, or all + * possible message id to one CPU. Limit is also helpful for resonse time + * per IOCTL request, as PUNIT may take different times to process each + * request and may hold for long for too many commands. + */ +#define ISST_IF_CMD_LIMIT 64 + +#define ISST_IF_API_VERSION 0x01 +#define ISST_IF_DRIVER_VERSION 0x01 + +#define ISST_IF_DEV_MBOX 0 +#define ISST_IF_DEV_MMIO 1 +#define ISST_IF_DEV_MAX 2 + +/** + * struct isst_if_cmd_cb - Used to register a IOCTL handler + * @registered: Used by the common code to store registry. Caller don't + * to touch this field + * @cmd_size: The command size of the individual command in IOCTL + * @offset: Offset to the first valid member in command structure. + * This will be the offset of the start of the command + * after command count field + * @cmd_callback: Callback function to handle IOCTL. The callback has the + * command pointer with data for command. There is a pointer + * called write_only, which when set, will not copy the + * response to user ioctl buffer. The "resume" argument + * can be used to avoid storing the command for replay + * during system resume + * + * This structure is used to register an handler for IOCTL. To avoid + * code duplication common code handles all the IOCTL command read/write + * including handling multiple command in single IOCTL. The caller just + * need to execute a command via the registered callback. + */ +struct isst_if_cmd_cb { + int registered; + int cmd_size; + int offset; + struct module *owner; + long (*cmd_callback)(u8 *ptr, int *write_only, int resume); +}; + +/* Internal interface functions */ +int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb); +void isst_if_cdev_unregister(int type); +struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn); +bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd); +bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd); +int isst_store_cmd(int cmd, int sub_command, u32 cpu, int mbox_cmd, + u32 param, u64 data); +void isst_resume_common(void); +#endif diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c new file mode 100644 index 000000000000..1b6eab071068 --- /dev/null +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Mbox via MSR Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +#define MSR_OS_MAILBOX_INTERFACE 0xB0 +#define MSR_OS_MAILBOX_DATA 0xB1 +#define MSR_OS_MAILBOX_BUSY_BIT 31 + +/* + * Based on experiments count is never more than 1, as the MSR overhead + * is enough to finish the command. So here this is the worst case number. + */ +#define OS_MAILBOX_RETRY_COUNT 3 + +static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter, + u32 command_data, u32 *response_data) +{ + u32 retries; + u64 data; + int ret; + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); + if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + ret = 0; + break; + } while (--retries); + + if (ret) + return ret; + + /* Write DATA register */ + wrmsrl(MSR_OS_MAILBOX_DATA, command_data); + + /* Write command register */ + data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) | + (parameter & GENMASK_ULL(13, 0)) << 16 | + (sub_command << 8) | + command; + wrmsrl(MSR_OS_MAILBOX_INTERFACE, data); + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); + if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + + if (data & 0xff) + return -ENXIO; + + if (response_data) { + rdmsrl(MSR_OS_MAILBOX_DATA, data); + *response_data = data; + } + ret = 0; + break; + } while (--retries); + + return ret; +} + +struct msrl_action { + int err; + struct isst_if_mbox_cmd *mbox_cmd; +}; + +/* revisit, smp_call_function_single should be enough for atomic mailbox! */ +static void msrl_update_func(void *info) +{ + struct msrl_action *act = info; + + act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command, + act->mbox_cmd->sub_command, + act->mbox_cmd->parameter, + act->mbox_cmd->req_data, + &act->mbox_cmd->resp_data); +} + +static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) +{ + struct msrl_action action; + int ret; + + action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; + + if (isst_if_mbox_cmd_invalid(action.mbox_cmd)) + return -EINVAL; + + if (isst_if_mbox_cmd_set_req(action.mbox_cmd) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + + /* + * To complete mailbox command, we need to access two MSRs. + * So we don't want race to complete a mailbox transcation. + * Here smp_call ensures that msrl_update_func() has no race + * and also with wait flag, wait for completion. + * smp_call_function_single is using get_cpu() and put_cpu(). + */ + ret = smp_call_function_single(action.mbox_cmd->logical_cpu, + msrl_update_func, &action, 1); + if (ret) + return ret; + + if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd)) + action.err = isst_store_cmd(action.mbox_cmd->command, + action.mbox_cmd->sub_command, + action.mbox_cmd->logical_cpu, 1, + action.mbox_cmd->parameter, + action.mbox_cmd->req_data); + *write_only = 0; + + return action.err; +} + + +static int isst_pm_notify(struct notifier_block *nb, + unsigned long mode, void *_unused) +{ + switch (mode) { + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + isst_resume_common(); + break; + default: + break; + } + return 0; +} + +static struct notifier_block isst_pm_nb = { + .notifier_call = isst_pm_notify, +}; + +static const struct x86_cpu_id isst_if_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids); + +static int __init isst_if_mbox_init(void) +{ + struct isst_if_cmd_cb cb; + const struct x86_cpu_id *id; + u64 data; + int ret; + + id = x86_match_cpu(isst_if_cpu_ids); + if (!id) + return -ENODEV; + + /* Check presence of mailbox MSRs */ + ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data); + if (ret) + return ret; + + ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data); + if (ret) + return ret; + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_mbox_cmd); + cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); + cb.cmd_callback = isst_if_mbox_proc_cmd; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); + if (ret) + return ret; + + ret = register_pm_notifier(&isst_pm_nb); + if (ret) + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + + return ret; +} +module_init(isst_if_mbox_init) + +static void __exit isst_if_mbox_exit(void) +{ + unregister_pm_notifier(&isst_pm_nb); + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); +} +module_exit(isst_if_mbox_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface mailbox driver"); diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c new file mode 100644 index 000000000000..df1fc6c719f3 --- /dev/null +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Mbox via PCI Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +#define PUNIT_MAILBOX_DATA 0xA0 +#define PUNIT_MAILBOX_INTERFACE 0xA4 +#define PUNIT_MAILBOX_BUSY_BIT 31 + +/* + * The average time to complete mailbox commands is less than 40us. Most of + * the commands complete in few micro seconds. But the same firmware handles + * requests from all power management features. + * We can create a scenario where we flood the firmware with requests then + * the mailbox response can be delayed for 100s of micro seconds. So define + * two timeouts. One for average case and one for long. + * If the firmware is taking more than average, just call cond_resched(). + */ +#define OS_MAILBOX_TIMEOUT_AVG_US 40 +#define OS_MAILBOX_TIMEOUT_MAX_US 1000 + +struct isst_if_device { + struct mutex mutex; +}; + +static int isst_if_mbox_cmd(struct pci_dev *pdev, + struct isst_if_mbox_cmd *mbox_cmd) +{ + s64 tm_delta = 0; + ktime_t tm; + u32 data; + int ret; + + /* Poll for rb bit == 0 */ + tm = ktime_get(); + do { + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, + &data); + if (ret) + return ret; + + if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + tm_delta = ktime_us_delta(ktime_get(), tm); + if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US) + cond_resched(); + continue; + } + ret = 0; + break; + } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US); + + if (ret) + return ret; + + /* Write DATA register */ + ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA, + mbox_cmd->req_data); + if (ret) + return ret; + + /* Write command register */ + data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) | + (mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 | + (mbox_cmd->sub_command << 8) | + mbox_cmd->command; + + ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data); + if (ret) + return ret; + + /* Poll for rb bit == 0 */ + tm_delta = 0; + tm = ktime_get(); + do { + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, + &data); + if (ret) + return ret; + + if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + tm_delta = ktime_us_delta(ktime_get(), tm); + if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US) + cond_resched(); + continue; + } + + if (data & 0xff) + return -ENXIO; + + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data); + if (ret) + return ret; + + mbox_cmd->resp_data = data; + ret = 0; + break; + } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US); + + return ret; +} + +static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_mbox_cmd *mbox_cmd; + struct isst_if_device *punit_dev; + struct pci_dev *pdev; + int ret; + + mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; + + if (isst_if_mbox_cmd_invalid(mbox_cmd)) + return -EINVAL; + + if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1); + if (!pdev) + return -EINVAL; + + punit_dev = pci_get_drvdata(pdev); + if (!punit_dev) + return -EINVAL; + + /* + * Basically we are allowing one complete mailbox transaction on + * a mapped PCI device at a time. + */ + mutex_lock(&punit_dev->mutex); + ret = isst_if_mbox_cmd(pdev, mbox_cmd); + if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd)) + ret = isst_store_cmd(mbox_cmd->command, + mbox_cmd->sub_command, + mbox_cmd->logical_cpu, 1, + mbox_cmd->parameter, + mbox_cmd->req_data); + mutex_unlock(&punit_dev->mutex); + if (ret) + return ret; + + *write_only = 0; + + return 0; +} + +static const struct pci_device_id isst_if_mbox_ids[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0)}, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1)}, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids); + +static int isst_if_mbox_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct isst_if_device *punit_dev; + struct isst_if_cmd_cb cb; + int ret; + + punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); + if (!punit_dev) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + mutex_init(&punit_dev->mutex); + pci_set_drvdata(pdev, punit_dev); + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_mbox_cmd); + cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); + cb.cmd_callback = isst_if_mbox_proc_cmd; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); + + if (ret) + mutex_destroy(&punit_dev->mutex); + + return ret; +} + +static void isst_if_mbox_remove(struct pci_dev *pdev) +{ + struct isst_if_device *punit_dev; + + punit_dev = pci_get_drvdata(pdev); + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + mutex_destroy(&punit_dev->mutex); +} + +static int __maybe_unused isst_if_resume(struct device *device) +{ + isst_resume_common(); + return 0; +} + +static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume); + +static struct pci_driver isst_if_pci_driver = { + .name = "isst_if_mbox_pci", + .id_table = isst_if_mbox_ids, + .probe = isst_if_mbox_probe, + .remove = isst_if_mbox_remove, + .driver.pm = &isst_if_pm_ops, +}; + +module_pci_driver(isst_if_pci_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver"); diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c new file mode 100644 index 000000000000..ff49025ec085 --- /dev/null +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: MMIO Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +struct isst_mmio_range { + int beg; + int end; +}; + +static struct isst_mmio_range mmio_range_devid_0[] = { + {0x04, 0x14}, + {0x20, 0xD0}, +}; + +static struct isst_mmio_range mmio_range_devid_1[] = { + {0x04, 0x14}, + {0x20, 0x11C}, +}; + +struct isst_if_device { + void __iomem *punit_mmio; + u32 range_0[5]; + u32 range_1[64]; + struct isst_mmio_range *mmio_range; + struct mutex mutex; +}; + +static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_device *punit_dev; + struct isst_if_io_reg *io_reg; + struct pci_dev *pdev; + + io_reg = (struct isst_if_io_reg *)cmd_ptr; + + if (io_reg->reg % 4) + return -EINVAL; + + if (io_reg->read_write && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + pdev = isst_if_get_pci_dev(io_reg->logical_cpu, 0, 0, 1); + if (!pdev) + return -EINVAL; + + punit_dev = pci_get_drvdata(pdev); + if (!punit_dev) + return -EINVAL; + + if (io_reg->reg < punit_dev->mmio_range[0].beg || + io_reg->reg > punit_dev->mmio_range[1].end) + return -EINVAL; + + /* + * Ensure that operation is complete on a PCI device to avoid read + * write race by using per PCI device mutex. + */ + mutex_lock(&punit_dev->mutex); + if (io_reg->read_write) { + writel(io_reg->value, punit_dev->punit_mmio+io_reg->reg); + *write_only = 1; + } else { + io_reg->value = readl(punit_dev->punit_mmio+io_reg->reg); + *write_only = 0; + } + mutex_unlock(&punit_dev->mutex); + + return 0; +} + +static const struct pci_device_id isst_if_ids[] = { + { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_0, &mmio_range_devid_0)}, + { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_1, &mmio_range_devid_1)}, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, isst_if_ids); + +static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct isst_if_device *punit_dev; + struct isst_if_cmd_cb cb; + u32 mmio_base, pcu_base; + u64 base_addr; + int ret; + + punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); + if (!punit_dev) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pci_read_config_dword(pdev, 0xD0, &mmio_base); + if (ret) + return ret; + + ret = pci_read_config_dword(pdev, 0xFC, &pcu_base); + if (ret) + return ret; + + pcu_base &= GENMASK(10, 0); + base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12; + punit_dev->punit_mmio = devm_ioremap(&pdev->dev, base_addr, 256); + if (!punit_dev->punit_mmio) + return -ENOMEM; + + mutex_init(&punit_dev->mutex); + pci_set_drvdata(pdev, punit_dev); + punit_dev->mmio_range = (struct isst_mmio_range *) ent->driver_data; + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_io_reg); + cb.offset = offsetof(struct isst_if_io_regs, io_reg); + cb.cmd_callback = isst_if_mmio_rd_wr; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MMIO, &cb); + if (ret) + mutex_destroy(&punit_dev->mutex); + + return ret; +} + +static void isst_if_remove(struct pci_dev *pdev) +{ + struct isst_if_device *punit_dev; + + punit_dev = pci_get_drvdata(pdev); + isst_if_cdev_unregister(ISST_IF_DEV_MMIO); + mutex_destroy(&punit_dev->mutex); +} + +static int __maybe_unused isst_if_suspend(struct device *device) +{ + struct isst_if_device *punit_dev = dev_get_drvdata(device); + int i; + + for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) + punit_dev->range_0[i] = readl(punit_dev->punit_mmio + + punit_dev->mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) { + u32 addr; + + addr = punit_dev->mmio_range[1].beg + 4 * i; + if (addr > punit_dev->mmio_range[1].end) + break; + punit_dev->range_1[i] = readl(punit_dev->punit_mmio + addr); + } + + return 0; +} + +static int __maybe_unused isst_if_resume(struct device *device) +{ + struct isst_if_device *punit_dev = dev_get_drvdata(device); + int i; + + for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) + writel(punit_dev->range_0[i], punit_dev->punit_mmio + + punit_dev->mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) { + u32 addr; + + addr = punit_dev->mmio_range[1].beg + 4 * i; + if (addr > punit_dev->mmio_range[1].end) + break; + + writel(punit_dev->range_1[i], punit_dev->punit_mmio + addr); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, isst_if_suspend, isst_if_resume); + +static struct pci_driver isst_if_pci_driver = { + .name = "isst_if_pci", + .id_table = isst_if_ids, + .probe = isst_if_probe, + .remove = isst_if_remove, + .driver.pm = &isst_if_pm_ops, +}; + +module_pci_driver(isst_if_pci_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface mmio driver"); diff --git a/drivers/platform/x86/intel_speed_select_if/Kconfig b/drivers/platform/x86/intel_speed_select_if/Kconfig deleted file mode 100644 index ce3e3dc076d2..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/Kconfig +++ /dev/null @@ -1,17 +0,0 @@ -menu "Intel Speed Select Technology interface support" - depends on PCI - depends on X86_64 || COMPILE_TEST - -config INTEL_SPEED_SELECT_INTERFACE - tristate "Intel(R) Speed Select Technology interface drivers" - help - This config enables the Intel(R) Speed Select Technology interface - drivers. The Intel(R) speed select technology features are non - architectural and only supported on specific Xeon(R) servers. - These drivers provide interface to directly communicate with hardware - via MMIO and Mail boxes to enumerate and control all the speed select - features. - - Enable this config, if there is a need to enable and control the - Intel(R) Speed Select Technology features from the user space. -endmenu diff --git a/drivers/platform/x86/intel_speed_select_if/Makefile b/drivers/platform/x86/intel_speed_select_if/Makefile deleted file mode 100644 index 856076206f35..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Makefile - Intel Speed Select Interface drivers -# Copyright (c) 2019, Intel Corporation. -# - -obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o -obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o -obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o -obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c deleted file mode 100644 index 8a4d52a9028d..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ /dev/null @@ -1,741 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Speed Select Interface: Common functions - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isst_if_common.h" - -#define MSR_THREAD_ID_INFO 0x53 -#define MSR_CPU_BUS_NUMBER 0x128 - -static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX]; - -static int punit_msr_white_list[] = { - MSR_TURBO_RATIO_LIMIT, - MSR_CONFIG_TDP_CONTROL, - MSR_TURBO_RATIO_LIMIT1, - MSR_TURBO_RATIO_LIMIT2, -}; - -struct isst_valid_cmd_ranges { - u16 cmd; - u16 sub_cmd_beg; - u16 sub_cmd_end; -}; - -struct isst_cmd_set_req_type { - u16 cmd; - u16 sub_cmd; - u16 param; -}; - -static const struct isst_valid_cmd_ranges isst_valid_cmds[] = { - {0xD0, 0x00, 0x03}, - {0x7F, 0x00, 0x0B}, - {0x7F, 0x10, 0x12}, - {0x7F, 0x20, 0x23}, - {0x94, 0x03, 0x03}, - {0x95, 0x03, 0x03}, -}; - -static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = { - {0xD0, 0x00, 0x08}, - {0xD0, 0x01, 0x08}, - {0xD0, 0x02, 0x08}, - {0xD0, 0x03, 0x08}, - {0x7F, 0x02, 0x00}, - {0x7F, 0x08, 0x00}, - {0x95, 0x03, 0x03}, -}; - -struct isst_cmd { - struct hlist_node hnode; - u64 data; - u32 cmd; - int cpu; - int mbox_cmd_type; - u32 param; -}; - -static DECLARE_HASHTABLE(isst_hash, 8); -static DEFINE_MUTEX(isst_hash_lock); - -static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param, - u32 data) -{ - struct isst_cmd *sst_cmd; - - sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL); - if (!sst_cmd) - return -ENOMEM; - - sst_cmd->cpu = cpu; - sst_cmd->cmd = cmd; - sst_cmd->mbox_cmd_type = mbox_cmd_type; - sst_cmd->param = param; - sst_cmd->data = data; - - hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd); - - return 0; -} - -static void isst_delete_hash(void) -{ - struct isst_cmd *sst_cmd; - struct hlist_node *tmp; - int i; - - hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) { - hash_del(&sst_cmd->hnode); - kfree(sst_cmd); - } -} - -/** - * isst_store_cmd() - Store command to a hash table - * @cmd: Mailbox command. - * @sub_cmd: Mailbox sub-command or MSR id. - * @mbox_cmd_type: Mailbox or MSR command. - * @param: Mailbox parameter. - * @data: Mailbox request data or MSR data. - * - * Stores the command to a hash table if there is no such command already - * stored. If already stored update the latest parameter and data for the - * command. - * - * Return: Return result of store to hash table, 0 for success, others for - * failure. - */ -int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type, - u32 param, u64 data) -{ - struct isst_cmd *sst_cmd; - int full_cmd, ret; - - full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16; - full_cmd |= (sub_cmd & GENMASK_ULL(15, 0)); - mutex_lock(&isst_hash_lock); - hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) { - if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu && - sst_cmd->mbox_cmd_type == mbox_cmd_type) { - sst_cmd->param = param; - sst_cmd->data = data; - mutex_unlock(&isst_hash_lock); - return 0; - } - } - - ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data); - mutex_unlock(&isst_hash_lock); - - return ret; -} -EXPORT_SYMBOL_GPL(isst_store_cmd); - -static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb, - struct isst_cmd *sst_cmd) -{ - struct isst_if_mbox_cmd mbox_cmd; - int wr_only; - - mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16; - mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0); - mbox_cmd.parameter = sst_cmd->param; - mbox_cmd.req_data = sst_cmd->data; - mbox_cmd.logical_cpu = sst_cmd->cpu; - (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1); -} - -/** - * isst_resume_common() - Process Resume request - * - * On resume replay all mailbox commands and MSRs. - * - * Return: None. - */ -void isst_resume_common(void) -{ - struct isst_cmd *sst_cmd; - int i; - - hash_for_each(isst_hash, i, sst_cmd, hnode) { - struct isst_if_cmd_cb *cb; - - if (sst_cmd->mbox_cmd_type) { - cb = &punit_callbacks[ISST_IF_DEV_MBOX]; - if (cb->registered) - isst_mbox_resume_command(cb, sst_cmd); - } else { - wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd, - sst_cmd->data); - } - } -} -EXPORT_SYMBOL_GPL(isst_resume_common); - -static void isst_restore_msr_local(int cpu) -{ - struct isst_cmd *sst_cmd; - int i; - - mutex_lock(&isst_hash_lock); - for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { - if (!punit_msr_white_list[i]) - break; - - hash_for_each_possible(isst_hash, sst_cmd, hnode, - punit_msr_white_list[i]) { - if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu) - wrmsrl_safe(sst_cmd->cmd, sst_cmd->data); - } - } - mutex_unlock(&isst_hash_lock); -} - -/** - * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands - * @cmd: Pointer to the command structure to verify. - * - * Invalid command to PUNIT to may result in instability of the platform. - * This function has a whitelist of commands, which are allowed. - * - * Return: Return true if the command is invalid, else false. - */ -bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd) -{ - int i; - - if (cmd->logical_cpu >= nr_cpu_ids) - return true; - - for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) { - if (cmd->command == isst_valid_cmds[i].cmd && - (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg && - cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) { - return false; - } - } - - return true; -} -EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid); - -/** - * isst_if_mbox_cmd_set_req() - Check mailbox command is a set request - * @cmd: Pointer to the command structure to verify. - * - * Check if the given mail box level is set request and not a get request. - * - * Return: Return true if the command is set_req, else false. - */ -bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) { - if (cmd->command == isst_cmd_set_reqs[i].cmd && - cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd && - cmd->parameter == isst_cmd_set_reqs[i].param) { - return true; - } - } - - return false; -} -EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req); - -static int isst_if_get_platform_info(void __user *argp) -{ - struct isst_if_platform_info info; - - info.api_version = ISST_IF_API_VERSION, - info.driver_version = ISST_IF_DRIVER_VERSION, - info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT, - info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered; - info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered; - - if (copy_to_user(argp, &info, sizeof(info))) - return -EFAULT; - - return 0; -} - - -struct isst_if_cpu_info { - /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ - int bus_info[2]; - struct pci_dev *pci_dev[2]; - int punit_cpu_id; - int numa_node; -}; - -static struct isst_if_cpu_info *isst_cpu_info; -#define ISST_MAX_PCI_DOMAINS 8 - -static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) -{ - struct pci_dev *matched_pci_dev = NULL; - struct pci_dev *pci_dev = NULL; - int no_matches = 0; - int i, bus_number; - - if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || - cpu >= num_possible_cpus()) - return NULL; - - bus_number = isst_cpu_info[cpu].bus_info[bus_no]; - if (bus_number < 0) - return NULL; - - for (i = 0; i < ISST_MAX_PCI_DOMAINS; ++i) { - struct pci_dev *_pci_dev; - int node; - - _pci_dev = pci_get_domain_bus_and_slot(i, bus_number, PCI_DEVFN(dev, fn)); - if (!_pci_dev) - continue; - - ++no_matches; - if (!matched_pci_dev) - matched_pci_dev = _pci_dev; - - node = dev_to_node(&_pci_dev->dev); - if (node == NUMA_NO_NODE) { - pr_info("Fail to get numa node for CPU:%d bus:%d dev:%d fn:%d\n", - cpu, bus_no, dev, fn); - continue; - } - - if (node == isst_cpu_info[cpu].numa_node) { - pci_dev = _pci_dev; - break; - } - } - - /* - * If there is no numa matched pci_dev, then there can be following cases: - * 1. CONFIG_NUMA is not defined: In this case if there is only single device - * match, then we don't need numa information. Simply return last match. - * Othewise return NULL. - * 2. NUMA information is not exposed via _SEG method. In this case it is similar - * to case 1. - * 3. Numa information doesn't match with CPU numa node and more than one match - * return NULL. - */ - if (!pci_dev && no_matches == 1) - pci_dev = matched_pci_dev; - - return pci_dev; -} - -/** - * isst_if_get_pci_dev() - Get the PCI device instance for a CPU - * @cpu: Logical CPU number. - * @bus_number: The bus number assigned by the hardware. - * @dev: The device number assigned by the hardware. - * @fn: The function number assigned by the hardware. - * - * Using cached bus information, find out the PCI device for a bus number, - * device and function. - * - * Return: Return pci_dev pointer or NULL. - */ -struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) -{ - struct pci_dev *pci_dev; - - if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || - cpu >= num_possible_cpus()) - return NULL; - - pci_dev = isst_cpu_info[cpu].pci_dev[bus_no]; - - if (pci_dev && pci_dev->devfn == PCI_DEVFN(dev, fn)) - return pci_dev; - - return _isst_if_get_pci_dev(cpu, bus_no, dev, fn); -} -EXPORT_SYMBOL_GPL(isst_if_get_pci_dev); - -static int isst_if_cpu_online(unsigned int cpu) -{ - u64 data; - int ret; - - isst_cpu_info[cpu].numa_node = cpu_to_node(cpu); - - ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data); - if (ret) { - /* This is not a fatal error on MSR mailbox only I/F */ - isst_cpu_info[cpu].bus_info[0] = -1; - isst_cpu_info[cpu].bus_info[1] = -1; - } else { - isst_cpu_info[cpu].bus_info[0] = data & 0xff; - isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff; - isst_cpu_info[cpu].pci_dev[0] = _isst_if_get_pci_dev(cpu, 0, 0, 1); - isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1); - } - - ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data); - if (ret) { - isst_cpu_info[cpu].punit_cpu_id = -1; - return ret; - } - isst_cpu_info[cpu].punit_cpu_id = data; - - isst_restore_msr_local(cpu); - - return 0; -} - -static int isst_if_online_id; - -static int isst_if_cpu_info_init(void) -{ - int ret; - - isst_cpu_info = kcalloc(num_possible_cpus(), - sizeof(*isst_cpu_info), - GFP_KERNEL); - if (!isst_cpu_info) - return -ENOMEM; - - ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, - "platform/x86/isst-if:online", - isst_if_cpu_online, NULL); - if (ret < 0) { - kfree(isst_cpu_info); - return ret; - } - - isst_if_online_id = ret; - - return 0; -} - -static void isst_if_cpu_info_exit(void) -{ - cpuhp_remove_state(isst_if_online_id); - kfree(isst_cpu_info); -}; - -static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume) -{ - struct isst_if_cpu_map *cpu_map; - - cpu_map = (struct isst_if_cpu_map *)cmd_ptr; - if (cpu_map->logical_cpu >= nr_cpu_ids || - cpu_map->logical_cpu >= num_possible_cpus()) - return -EINVAL; - - *write_only = 0; - cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id; - - return 0; -} - -static bool match_punit_msr_white_list(int msr) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { - if (punit_msr_white_list[i] == msr) - return true; - } - - return false; -} - -static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume) -{ - struct isst_if_msr_cmd *msr_cmd; - int ret; - - msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr; - - if (!match_punit_msr_white_list(msr_cmd->msr)) - return -EINVAL; - - if (msr_cmd->logical_cpu >= nr_cpu_ids) - return -EINVAL; - - if (msr_cmd->read_write) { - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu, - msr_cmd->msr, - msr_cmd->data); - *write_only = 1; - if (!ret && !resume) - ret = isst_store_cmd(0, msr_cmd->msr, - msr_cmd->logical_cpu, - 0, 0, msr_cmd->data); - } else { - u64 data; - - ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu, - msr_cmd->msr, &data); - if (!ret) { - msr_cmd->data = data; - *write_only = 0; - } - } - - - return ret; -} - -static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb) -{ - unsigned char __user *ptr; - u32 cmd_count; - u8 *cmd_ptr; - long ret; - int i; - - /* Each multi command has u32 command count as the first field */ - if (copy_from_user(&cmd_count, argp, sizeof(cmd_count))) - return -EFAULT; - - if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT) - return -EINVAL; - - cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL); - if (!cmd_ptr) - return -ENOMEM; - - /* cb->offset points to start of the command after the command count */ - ptr = argp + cb->offset; - - for (i = 0; i < cmd_count; ++i) { - int wr_only; - - if (signal_pending(current)) { - ret = -EINTR; - break; - } - - if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) { - ret = -EFAULT; - break; - } - - ret = cb->cmd_callback(cmd_ptr, &wr_only, 0); - if (ret) - break; - - if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) { - ret = -EFAULT; - break; - } - - ptr += cb->cmd_size; - } - - kfree(cmd_ptr); - - return i ? i : ret; -} - -static long isst_if_def_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - void __user *argp = (void __user *)arg; - struct isst_if_cmd_cb cmd_cb; - struct isst_if_cmd_cb *cb; - long ret = -ENOTTY; - - switch (cmd) { - case ISST_IF_GET_PLATFORM_INFO: - ret = isst_if_get_platform_info(argp); - break; - case ISST_IF_GET_PHY_ID: - cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map); - cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map); - cmd_cb.cmd_callback = isst_if_proc_phyid_req; - ret = isst_if_exec_multi_cmd(argp, &cmd_cb); - break; - case ISST_IF_IO_CMD: - cb = &punit_callbacks[ISST_IF_DEV_MMIO]; - if (cb->registered) - ret = isst_if_exec_multi_cmd(argp, cb); - break; - case ISST_IF_MBOX_COMMAND: - cb = &punit_callbacks[ISST_IF_DEV_MBOX]; - if (cb->registered) - ret = isst_if_exec_multi_cmd(argp, cb); - break; - case ISST_IF_MSR_COMMAND: - cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd); - cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd); - cmd_cb.cmd_callback = isst_if_msr_cmd_req; - ret = isst_if_exec_multi_cmd(argp, &cmd_cb); - break; - default: - break; - } - - return ret; -} - -static DEFINE_MUTEX(punit_misc_dev_lock); -static int misc_usage_count; -static int misc_device_ret; -static int misc_device_open; - -static int isst_if_open(struct inode *inode, struct file *file) -{ - int i, ret = 0; - - /* Fail open, if a module is going away */ - mutex_lock(&punit_misc_dev_lock); - for (i = 0; i < ISST_IF_DEV_MAX; ++i) { - struct isst_if_cmd_cb *cb = &punit_callbacks[i]; - - if (cb->registered && !try_module_get(cb->owner)) { - ret = -ENODEV; - break; - } - } - if (ret) { - int j; - - for (j = 0; j < i; ++j) { - struct isst_if_cmd_cb *cb; - - cb = &punit_callbacks[j]; - if (cb->registered) - module_put(cb->owner); - } - } else { - misc_device_open++; - } - mutex_unlock(&punit_misc_dev_lock); - - return ret; -} - -static int isst_if_relase(struct inode *inode, struct file *f) -{ - int i; - - mutex_lock(&punit_misc_dev_lock); - misc_device_open--; - for (i = 0; i < ISST_IF_DEV_MAX; ++i) { - struct isst_if_cmd_cb *cb = &punit_callbacks[i]; - - if (cb->registered) - module_put(cb->owner); - } - mutex_unlock(&punit_misc_dev_lock); - - return 0; -} - -static const struct file_operations isst_if_char_driver_ops = { - .open = isst_if_open, - .unlocked_ioctl = isst_if_def_ioctl, - .release = isst_if_relase, -}; - -static struct miscdevice isst_if_char_driver = { - .minor = MISC_DYNAMIC_MINOR, - .name = "isst_interface", - .fops = &isst_if_char_driver_ops, -}; - -/** - * isst_if_cdev_register() - Register callback for IOCTL - * @device_type: The device type this callback handling. - * @cb: Callback structure. - * - * This function registers a callback to device type. On very first call - * it will register a misc device, which is used for user kernel interface. - * Other calls simply increment ref count. Registry will fail, if the user - * already opened misc device for operation. Also if the misc device - * creation failed, then it will not try again and all callers will get - * failure code. - * - * Return: Return the return value from the misc creation device or -EINVAL - * for unsupported device type. - */ -int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb) -{ - if (misc_device_ret) - return misc_device_ret; - - if (device_type >= ISST_IF_DEV_MAX) - return -EINVAL; - - mutex_lock(&punit_misc_dev_lock); - if (misc_device_open) { - mutex_unlock(&punit_misc_dev_lock); - return -EAGAIN; - } - if (!misc_usage_count) { - int ret; - - misc_device_ret = misc_register(&isst_if_char_driver); - if (misc_device_ret) - goto unlock_exit; - - ret = isst_if_cpu_info_init(); - if (ret) { - misc_deregister(&isst_if_char_driver); - misc_device_ret = ret; - goto unlock_exit; - } - } - memcpy(&punit_callbacks[device_type], cb, sizeof(*cb)); - punit_callbacks[device_type].registered = 1; - misc_usage_count++; -unlock_exit: - mutex_unlock(&punit_misc_dev_lock); - - return misc_device_ret; -} -EXPORT_SYMBOL_GPL(isst_if_cdev_register); - -/** - * isst_if_cdev_unregister() - Unregister callback for IOCTL - * @device_type: The device type to unregister. - * - * This function unregisters the previously registered callback. If this - * is the last callback unregistering, then misc device is removed. - * - * Return: None. - */ -void isst_if_cdev_unregister(int device_type) -{ - mutex_lock(&punit_misc_dev_lock); - misc_usage_count--; - punit_callbacks[device_type].registered = 0; - if (device_type == ISST_IF_DEV_MBOX) - isst_delete_hash(); - if (!misc_usage_count && !misc_device_ret) { - misc_deregister(&isst_if_char_driver); - isst_if_cpu_info_exit(); - } - mutex_unlock(&punit_misc_dev_lock); -} -EXPORT_SYMBOL_GPL(isst_if_cdev_unregister); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h deleted file mode 100644 index fdecdae248d7..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Intel Speed Select Interface: Drivers Internal defines - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#ifndef __ISST_IF_COMMON_H -#define __ISST_IF_COMMON_H - -#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_0 0x3451 -#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0 0x3459 - -#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_1 0x3251 -#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1 0x3259 - -/* - * Validate maximum commands in a single request. - * This is enough to handle command to every core in one ioctl, or all - * possible message id to one CPU. Limit is also helpful for resonse time - * per IOCTL request, as PUNIT may take different times to process each - * request and may hold for long for too many commands. - */ -#define ISST_IF_CMD_LIMIT 64 - -#define ISST_IF_API_VERSION 0x01 -#define ISST_IF_DRIVER_VERSION 0x01 - -#define ISST_IF_DEV_MBOX 0 -#define ISST_IF_DEV_MMIO 1 -#define ISST_IF_DEV_MAX 2 - -/** - * struct isst_if_cmd_cb - Used to register a IOCTL handler - * @registered: Used by the common code to store registry. Caller don't - * to touch this field - * @cmd_size: The command size of the individual command in IOCTL - * @offset: Offset to the first valid member in command structure. - * This will be the offset of the start of the command - * after command count field - * @cmd_callback: Callback function to handle IOCTL. The callback has the - * command pointer with data for command. There is a pointer - * called write_only, which when set, will not copy the - * response to user ioctl buffer. The "resume" argument - * can be used to avoid storing the command for replay - * during system resume - * - * This structure is used to register an handler for IOCTL. To avoid - * code duplication common code handles all the IOCTL command read/write - * including handling multiple command in single IOCTL. The caller just - * need to execute a command via the registered callback. - */ -struct isst_if_cmd_cb { - int registered; - int cmd_size; - int offset; - struct module *owner; - long (*cmd_callback)(u8 *ptr, int *write_only, int resume); -}; - -/* Internal interface functions */ -int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb); -void isst_if_cdev_unregister(int type); -struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn); -bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd); -bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd); -int isst_store_cmd(int cmd, int sub_command, u32 cpu, int mbox_cmd, - u32 param, u64 data); -void isst_resume_common(void); -#endif diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c deleted file mode 100644 index 1b6eab071068..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c +++ /dev/null @@ -1,214 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Speed Select Interface: Mbox via MSR Interface - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isst_if_common.h" - -#define MSR_OS_MAILBOX_INTERFACE 0xB0 -#define MSR_OS_MAILBOX_DATA 0xB1 -#define MSR_OS_MAILBOX_BUSY_BIT 31 - -/* - * Based on experiments count is never more than 1, as the MSR overhead - * is enough to finish the command. So here this is the worst case number. - */ -#define OS_MAILBOX_RETRY_COUNT 3 - -static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter, - u32 command_data, u32 *response_data) -{ - u32 retries; - u64 data; - int ret; - - /* Poll for rb bit == 0 */ - retries = OS_MAILBOX_RETRY_COUNT; - do { - rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); - if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { - ret = -EBUSY; - continue; - } - ret = 0; - break; - } while (--retries); - - if (ret) - return ret; - - /* Write DATA register */ - wrmsrl(MSR_OS_MAILBOX_DATA, command_data); - - /* Write command register */ - data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) | - (parameter & GENMASK_ULL(13, 0)) << 16 | - (sub_command << 8) | - command; - wrmsrl(MSR_OS_MAILBOX_INTERFACE, data); - - /* Poll for rb bit == 0 */ - retries = OS_MAILBOX_RETRY_COUNT; - do { - rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); - if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { - ret = -EBUSY; - continue; - } - - if (data & 0xff) - return -ENXIO; - - if (response_data) { - rdmsrl(MSR_OS_MAILBOX_DATA, data); - *response_data = data; - } - ret = 0; - break; - } while (--retries); - - return ret; -} - -struct msrl_action { - int err; - struct isst_if_mbox_cmd *mbox_cmd; -}; - -/* revisit, smp_call_function_single should be enough for atomic mailbox! */ -static void msrl_update_func(void *info) -{ - struct msrl_action *act = info; - - act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command, - act->mbox_cmd->sub_command, - act->mbox_cmd->parameter, - act->mbox_cmd->req_data, - &act->mbox_cmd->resp_data); -} - -static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) -{ - struct msrl_action action; - int ret; - - action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; - - if (isst_if_mbox_cmd_invalid(action.mbox_cmd)) - return -EINVAL; - - if (isst_if_mbox_cmd_set_req(action.mbox_cmd) && - !capable(CAP_SYS_ADMIN)) - return -EPERM; - - /* - * To complete mailbox command, we need to access two MSRs. - * So we don't want race to complete a mailbox transcation. - * Here smp_call ensures that msrl_update_func() has no race - * and also with wait flag, wait for completion. - * smp_call_function_single is using get_cpu() and put_cpu(). - */ - ret = smp_call_function_single(action.mbox_cmd->logical_cpu, - msrl_update_func, &action, 1); - if (ret) - return ret; - - if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd)) - action.err = isst_store_cmd(action.mbox_cmd->command, - action.mbox_cmd->sub_command, - action.mbox_cmd->logical_cpu, 1, - action.mbox_cmd->parameter, - action.mbox_cmd->req_data); - *write_only = 0; - - return action.err; -} - - -static int isst_pm_notify(struct notifier_block *nb, - unsigned long mode, void *_unused) -{ - switch (mode) { - case PM_POST_HIBERNATION: - case PM_POST_RESTORE: - case PM_POST_SUSPEND: - isst_resume_common(); - break; - default: - break; - } - return 0; -} - -static struct notifier_block isst_pm_nb = { - .notifier_call = isst_pm_notify, -}; - -static const struct x86_cpu_id isst_if_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), - {} -}; -MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids); - -static int __init isst_if_mbox_init(void) -{ - struct isst_if_cmd_cb cb; - const struct x86_cpu_id *id; - u64 data; - int ret; - - id = x86_match_cpu(isst_if_cpu_ids); - if (!id) - return -ENODEV; - - /* Check presence of mailbox MSRs */ - ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data); - if (ret) - return ret; - - ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data); - if (ret) - return ret; - - memset(&cb, 0, sizeof(cb)); - cb.cmd_size = sizeof(struct isst_if_mbox_cmd); - cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); - cb.cmd_callback = isst_if_mbox_proc_cmd; - cb.owner = THIS_MODULE; - ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); - if (ret) - return ret; - - ret = register_pm_notifier(&isst_pm_nb); - if (ret) - isst_if_cdev_unregister(ISST_IF_DEV_MBOX); - - return ret; -} -module_init(isst_if_mbox_init) - -static void __exit isst_if_mbox_exit(void) -{ - unregister_pm_notifier(&isst_pm_nb); - isst_if_cdev_unregister(ISST_IF_DEV_MBOX); -} -module_exit(isst_if_mbox_exit) - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel speed select interface mailbox driver"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c deleted file mode 100644 index df1fc6c719f3..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Speed Select Interface: Mbox via PCI Interface - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include -#include - -#include "isst_if_common.h" - -#define PUNIT_MAILBOX_DATA 0xA0 -#define PUNIT_MAILBOX_INTERFACE 0xA4 -#define PUNIT_MAILBOX_BUSY_BIT 31 - -/* - * The average time to complete mailbox commands is less than 40us. Most of - * the commands complete in few micro seconds. But the same firmware handles - * requests from all power management features. - * We can create a scenario where we flood the firmware with requests then - * the mailbox response can be delayed for 100s of micro seconds. So define - * two timeouts. One for average case and one for long. - * If the firmware is taking more than average, just call cond_resched(). - */ -#define OS_MAILBOX_TIMEOUT_AVG_US 40 -#define OS_MAILBOX_TIMEOUT_MAX_US 1000 - -struct isst_if_device { - struct mutex mutex; -}; - -static int isst_if_mbox_cmd(struct pci_dev *pdev, - struct isst_if_mbox_cmd *mbox_cmd) -{ - s64 tm_delta = 0; - ktime_t tm; - u32 data; - int ret; - - /* Poll for rb bit == 0 */ - tm = ktime_get(); - do { - ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, - &data); - if (ret) - return ret; - - if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { - ret = -EBUSY; - tm_delta = ktime_us_delta(ktime_get(), tm); - if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US) - cond_resched(); - continue; - } - ret = 0; - break; - } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US); - - if (ret) - return ret; - - /* Write DATA register */ - ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA, - mbox_cmd->req_data); - if (ret) - return ret; - - /* Write command register */ - data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) | - (mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 | - (mbox_cmd->sub_command << 8) | - mbox_cmd->command; - - ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data); - if (ret) - return ret; - - /* Poll for rb bit == 0 */ - tm_delta = 0; - tm = ktime_get(); - do { - ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, - &data); - if (ret) - return ret; - - if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { - ret = -EBUSY; - tm_delta = ktime_us_delta(ktime_get(), tm); - if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US) - cond_resched(); - continue; - } - - if (data & 0xff) - return -ENXIO; - - ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data); - if (ret) - return ret; - - mbox_cmd->resp_data = data; - ret = 0; - break; - } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US); - - return ret; -} - -static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) -{ - struct isst_if_mbox_cmd *mbox_cmd; - struct isst_if_device *punit_dev; - struct pci_dev *pdev; - int ret; - - mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; - - if (isst_if_mbox_cmd_invalid(mbox_cmd)) - return -EINVAL; - - if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN)) - return -EPERM; - - pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1); - if (!pdev) - return -EINVAL; - - punit_dev = pci_get_drvdata(pdev); - if (!punit_dev) - return -EINVAL; - - /* - * Basically we are allowing one complete mailbox transaction on - * a mapped PCI device at a time. - */ - mutex_lock(&punit_dev->mutex); - ret = isst_if_mbox_cmd(pdev, mbox_cmd); - if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd)) - ret = isst_store_cmd(mbox_cmd->command, - mbox_cmd->sub_command, - mbox_cmd->logical_cpu, 1, - mbox_cmd->parameter, - mbox_cmd->req_data); - mutex_unlock(&punit_dev->mutex); - if (ret) - return ret; - - *write_only = 0; - - return 0; -} - -static const struct pci_device_id isst_if_mbox_ids[] = { - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0)}, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1)}, - { 0 }, -}; -MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids); - -static int isst_if_mbox_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) -{ - struct isst_if_device *punit_dev; - struct isst_if_cmd_cb cb; - int ret; - - punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); - if (!punit_dev) - return -ENOMEM; - - ret = pcim_enable_device(pdev); - if (ret) - return ret; - - mutex_init(&punit_dev->mutex); - pci_set_drvdata(pdev, punit_dev); - - memset(&cb, 0, sizeof(cb)); - cb.cmd_size = sizeof(struct isst_if_mbox_cmd); - cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); - cb.cmd_callback = isst_if_mbox_proc_cmd; - cb.owner = THIS_MODULE; - ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); - - if (ret) - mutex_destroy(&punit_dev->mutex); - - return ret; -} - -static void isst_if_mbox_remove(struct pci_dev *pdev) -{ - struct isst_if_device *punit_dev; - - punit_dev = pci_get_drvdata(pdev); - isst_if_cdev_unregister(ISST_IF_DEV_MBOX); - mutex_destroy(&punit_dev->mutex); -} - -static int __maybe_unused isst_if_resume(struct device *device) -{ - isst_resume_common(); - return 0; -} - -static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume); - -static struct pci_driver isst_if_pci_driver = { - .name = "isst_if_mbox_pci", - .id_table = isst_if_mbox_ids, - .probe = isst_if_mbox_probe, - .remove = isst_if_mbox_remove, - .driver.pm = &isst_if_pm_ops, -}; - -module_pci_driver(isst_if_pci_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c deleted file mode 100644 index ff49025ec085..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Speed Select Interface: MMIO Interface - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include - -#include "isst_if_common.h" - -struct isst_mmio_range { - int beg; - int end; -}; - -static struct isst_mmio_range mmio_range_devid_0[] = { - {0x04, 0x14}, - {0x20, 0xD0}, -}; - -static struct isst_mmio_range mmio_range_devid_1[] = { - {0x04, 0x14}, - {0x20, 0x11C}, -}; - -struct isst_if_device { - void __iomem *punit_mmio; - u32 range_0[5]; - u32 range_1[64]; - struct isst_mmio_range *mmio_range; - struct mutex mutex; -}; - -static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume) -{ - struct isst_if_device *punit_dev; - struct isst_if_io_reg *io_reg; - struct pci_dev *pdev; - - io_reg = (struct isst_if_io_reg *)cmd_ptr; - - if (io_reg->reg % 4) - return -EINVAL; - - if (io_reg->read_write && !capable(CAP_SYS_ADMIN)) - return -EPERM; - - pdev = isst_if_get_pci_dev(io_reg->logical_cpu, 0, 0, 1); - if (!pdev) - return -EINVAL; - - punit_dev = pci_get_drvdata(pdev); - if (!punit_dev) - return -EINVAL; - - if (io_reg->reg < punit_dev->mmio_range[0].beg || - io_reg->reg > punit_dev->mmio_range[1].end) - return -EINVAL; - - /* - * Ensure that operation is complete on a PCI device to avoid read - * write race by using per PCI device mutex. - */ - mutex_lock(&punit_dev->mutex); - if (io_reg->read_write) { - writel(io_reg->value, punit_dev->punit_mmio+io_reg->reg); - *write_only = 1; - } else { - io_reg->value = readl(punit_dev->punit_mmio+io_reg->reg); - *write_only = 0; - } - mutex_unlock(&punit_dev->mutex); - - return 0; -} - -static const struct pci_device_id isst_if_ids[] = { - { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_0, &mmio_range_devid_0)}, - { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_1, &mmio_range_devid_1)}, - { 0 }, -}; -MODULE_DEVICE_TABLE(pci, isst_if_ids); - -static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent) -{ - struct isst_if_device *punit_dev; - struct isst_if_cmd_cb cb; - u32 mmio_base, pcu_base; - u64 base_addr; - int ret; - - punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); - if (!punit_dev) - return -ENOMEM; - - ret = pcim_enable_device(pdev); - if (ret) - return ret; - - ret = pci_read_config_dword(pdev, 0xD0, &mmio_base); - if (ret) - return ret; - - ret = pci_read_config_dword(pdev, 0xFC, &pcu_base); - if (ret) - return ret; - - pcu_base &= GENMASK(10, 0); - base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12; - punit_dev->punit_mmio = devm_ioremap(&pdev->dev, base_addr, 256); - if (!punit_dev->punit_mmio) - return -ENOMEM; - - mutex_init(&punit_dev->mutex); - pci_set_drvdata(pdev, punit_dev); - punit_dev->mmio_range = (struct isst_mmio_range *) ent->driver_data; - - memset(&cb, 0, sizeof(cb)); - cb.cmd_size = sizeof(struct isst_if_io_reg); - cb.offset = offsetof(struct isst_if_io_regs, io_reg); - cb.cmd_callback = isst_if_mmio_rd_wr; - cb.owner = THIS_MODULE; - ret = isst_if_cdev_register(ISST_IF_DEV_MMIO, &cb); - if (ret) - mutex_destroy(&punit_dev->mutex); - - return ret; -} - -static void isst_if_remove(struct pci_dev *pdev) -{ - struct isst_if_device *punit_dev; - - punit_dev = pci_get_drvdata(pdev); - isst_if_cdev_unregister(ISST_IF_DEV_MMIO); - mutex_destroy(&punit_dev->mutex); -} - -static int __maybe_unused isst_if_suspend(struct device *device) -{ - struct isst_if_device *punit_dev = dev_get_drvdata(device); - int i; - - for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) - punit_dev->range_0[i] = readl(punit_dev->punit_mmio + - punit_dev->mmio_range[0].beg + 4 * i); - for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) { - u32 addr; - - addr = punit_dev->mmio_range[1].beg + 4 * i; - if (addr > punit_dev->mmio_range[1].end) - break; - punit_dev->range_1[i] = readl(punit_dev->punit_mmio + addr); - } - - return 0; -} - -static int __maybe_unused isst_if_resume(struct device *device) -{ - struct isst_if_device *punit_dev = dev_get_drvdata(device); - int i; - - for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) - writel(punit_dev->range_0[i], punit_dev->punit_mmio + - punit_dev->mmio_range[0].beg + 4 * i); - for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) { - u32 addr; - - addr = punit_dev->mmio_range[1].beg + 4 * i; - if (addr > punit_dev->mmio_range[1].end) - break; - - writel(punit_dev->range_1[i], punit_dev->punit_mmio + addr); - } - - return 0; -} - -static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, isst_if_suspend, isst_if_resume); - -static struct pci_driver isst_if_pci_driver = { - .name = "isst_if_pci", - .id_table = isst_if_ids, - .probe = isst_if_probe, - .remove = isst_if_remove, - .driver.pm = &isst_if_pm_ops, -}; - -module_pci_driver(isst_if_pci_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel speed select interface mmio driver"); -- cgit v1.2.3-59-g8ed1b From 76693f5705828aa63c5cc52e407761964c6a4280 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:52 +0300 Subject: platform/x86: intel_atomisp2: Move to intel sub-directory Move Intel AtomISP v2 drivers to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-15-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 4 +- drivers/platform/x86/Kconfig | 34 ------- drivers/platform/x86/Makefile | 2 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/atomisp2/Kconfig | 43 ++++++++ drivers/platform/x86/intel/atomisp2/Makefile | 9 ++ drivers/platform/x86/intel/atomisp2/led.c | 116 ++++++++++++++++++++++ drivers/platform/x86/intel/atomisp2/pm.c | 143 +++++++++++++++++++++++++++ drivers/platform/x86/intel_atomisp2_led.c | 116 ---------------------- drivers/platform/x86/intel_atomisp2_pm.c | 143 --------------------------- 11 files changed, 315 insertions(+), 297 deletions(-) create mode 100644 drivers/platform/x86/intel/atomisp2/Kconfig create mode 100644 drivers/platform/x86/intel/atomisp2/Makefile create mode 100644 drivers/platform/x86/intel/atomisp2/led.c create mode 100644 drivers/platform/x86/intel/atomisp2/pm.c delete mode 100644 drivers/platform/x86/intel_atomisp2_led.c delete mode 100644 drivers/platform/x86/intel_atomisp2_pm.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index cf29f7154889..429b8b5c5283 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9212,13 +9212,13 @@ INTEL ATOMISP2 DUMMY / POWER-MANAGEMENT DRIVER M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel_atomisp2_pm.c +F: drivers/platform/x86/intel/atomisp2/pm.c INTEL ATOMISP2 LED DRIVER M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel_atomisp2_led.c +F: drivers/platform/x86/intel/atomisp2/led.c INTEL BIOS SAR INT1092 DRIVER M: Shravan S diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 935116ef2df9..6de0d374ba16 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,40 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/Kconfig" -config INTEL_ATOMISP2_LED - tristate "Intel AtomISP2 camera LED driver" - depends on GPIOLIB && LEDS_GPIO - help - Many Bay Trail and Cherry Trail devices come with a camera attached - to Intel's Image Signal Processor. Linux currently does not have a - driver for these, so they do not work as a camera. Some of these - camera's have a LED which is controlled through a GPIO. - - Some of these devices have a firmware issue where the LED gets turned - on at boot. This driver will turn the LED off at boot and also allows - controlling the LED (repurposing it) through the sysfs LED interface. - - Which GPIO is attached to the LED is usually not described in the - ACPI tables, so this driver contains per-system info about the GPIO - inside the driver, this means that this driver only works on systems - the driver knows about. - - To compile this driver as a module, choose M here: the module - will be called intel_atomisp2_led. - -config INTEL_ATOMISP2_PM - tristate "Intel AtomISP2 dummy / power-management driver" - depends on PCI && IOSF_MBI && PM - depends on !INTEL_ATOMISP - help - Power-management driver for Intel's Image Signal Processor found on - Bay Trail and Cherry Trail devices. This dummy driver's sole purpose - is to turn the ISP off (put it in D3) to save power and to allow - entering of S0ix modes. - - To compile this driver as a module, choose M here: the module - will be called intel_atomisp2_pm. - config INTEL_HID_EVENT tristate "INTEL HID Event" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dd3f1e683f0d..dccd7ecaae6a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,8 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o -obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 2622653af37a..9d0bc76a8948 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -16,6 +16,7 @@ menuconfig X86_PLATFORM_DRIVERS_INTEL if X86_PLATFORM_DRIVERS_INTEL +source "drivers/platform/x86/intel/atomisp2/Kconfig" source "drivers/platform/x86/intel/int1092/Kconfig" source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index f5ac4a5f953f..080589b3082d 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -4,6 +4,7 @@ # Intel x86 Platform-Specific Drivers # +obj-$(CONFIG_INTEL_ATOMISP2_PDX86) += atomisp2/ obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ diff --git a/drivers/platform/x86/intel/atomisp2/Kconfig b/drivers/platform/x86/intel/atomisp2/Kconfig new file mode 100644 index 000000000000..35dd2be9d2a1 --- /dev/null +++ b/drivers/platform/x86/intel/atomisp2/Kconfig @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Intel x86 Platform Specific Drivers +# + +config INTEL_ATOMISP2_PDX86 + bool + +config INTEL_ATOMISP2_LED + tristate "Intel AtomISP v2 camera LED driver" + depends on GPIOLIB && LEDS_GPIO + select INTEL_ATOMISP2_PDX86 + help + Many Bay Trail and Cherry Trail devices come with a camera attached + to Intel's Image Signal Processor. Linux currently does not have a + driver for these, so they do not work as a camera. Some of these + camera's have a LED which is controlled through a GPIO. + + Some of these devices have a firmware issue where the LED gets turned + on at boot. This driver will turn the LED off at boot and also allows + controlling the LED (repurposing it) through the sysfs LED interface. + + Which GPIO is attached to the LED is usually not described in the + ACPI tables, so this driver contains per-system info about the GPIO + inside the driver, this means that this driver only works on systems + the driver knows about. + + To compile this driver as a module, choose M here: the module + will be called intel_atomisp2_led. + +config INTEL_ATOMISP2_PM + tristate "Intel AtomISP v2 dummy / power-management driver" + depends on PCI && IOSF_MBI && PM + depends on !INTEL_ATOMISP + select INTEL_ATOMISP2_PDX86 + help + Power-management driver for Intel's Image Signal Processor found on + Bay Trail and Cherry Trail devices. This dummy driver's sole purpose + is to turn the ISP off (put it in D3) to save power and to allow + entering of S0ix modes. + + To compile this driver as a module, choose M here: the module + will be called intel_atomisp2_pm. diff --git a/drivers/platform/x86/intel/atomisp2/Makefile b/drivers/platform/x86/intel/atomisp2/Makefile new file mode 100644 index 000000000000..96b1e877d1f1 --- /dev/null +++ b/drivers/platform/x86/intel/atomisp2/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Intel x86 Platform Specific Drivers +# + +intel_atomisp2_led-y := led.o +obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o +intel_atomisp2_pm-y += pm.o +obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o diff --git a/drivers/platform/x86/intel/atomisp2/led.c b/drivers/platform/x86/intel/atomisp2/led.c new file mode 100644 index 000000000000..5935dfca166f --- /dev/null +++ b/drivers/platform/x86/intel/atomisp2/led.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for controlling LEDs for cameras connected to the Intel atomisp2 + * The main purpose of this driver is to turn off LEDs which are on at boot. + * + * Copyright (C) 2020 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* This must be leds-gpio as the leds-gpio driver binds to the name */ +#define DEV_NAME "leds-gpio" + +static const struct gpio_led atomisp2_leds[] = { + { + .name = "atomisp2::camera", + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, +}; + +static const struct gpio_led_platform_data atomisp2_leds_pdata = { + .num_leds = ARRAY_SIZE(atomisp2_leds), + .leds = atomisp2_leds, +}; + +static struct gpiod_lookup_table asus_t100ta_lookup = { + .dev_id = DEV_NAME, + .table = { + GPIO_LOOKUP_IDX("INT33FC:02", 8, NULL, 0, GPIO_ACTIVE_HIGH), + { } + } +}; + +static struct gpiod_lookup_table asus_t100chi_lookup = { + .dev_id = DEV_NAME, + .table = { + GPIO_LOOKUP_IDX("INT33FC:01", 24, NULL, 0, GPIO_ACTIVE_HIGH), + { } + } +}; + +static const struct dmi_system_id atomisp2_led_systems[] __initconst = { + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TA"), + }, + .driver_data = &asus_t100ta_lookup, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T200TA"), + }, + .driver_data = &asus_t100ta_lookup, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100CHI"), + }, + .driver_data = &asus_t100chi_lookup, + }, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(dmi, atomisp2_led_systems); + +static struct gpiod_lookup_table *gpio_lookup; +static struct platform_device *pdev; + +static int __init atomisp2_led_init(void) +{ + const struct dmi_system_id *system; + + system = dmi_first_match(atomisp2_led_systems); + if (!system) + return -ENODEV; + + gpio_lookup = system->driver_data; + gpiod_add_lookup_table(gpio_lookup); + + pdev = platform_device_register_resndata(NULL, + DEV_NAME, PLATFORM_DEVID_NONE, + NULL, 0, &atomisp2_leds_pdata, + sizeof(atomisp2_leds_pdata)); + if (IS_ERR(pdev)) + gpiod_remove_lookup_table(gpio_lookup); + + return PTR_ERR_OR_ZERO(pdev); +} + +static void __exit atomisp2_led_cleanup(void) +{ + platform_device_unregister(pdev); + gpiod_remove_lookup_table(gpio_lookup); +} + +module_init(atomisp2_led_init); +module_exit(atomisp2_led_cleanup); + +/* + * The ACPI INIT method from Asus WMI's code on the T100TA and T200TA turns the + * LED on (without the WMI interface allowing further control over the LED). + * Ensure we are loaded after asus-nb-wmi so that we turn the LED off again. + */ +MODULE_SOFTDEP("pre: asus_nb_wmi"); +MODULE_AUTHOR("Hans de Goede + * + * Based on various non upstream patches for ISP support: + * Copyright (C) 2010-2017 Intel Corporation. All rights reserved. + * Copyright (c) 2010 Silicon Hive www.siliconhive.com. + */ + +#include +#include +#include +#include +#include +#include + +/* PCI configuration regs */ +#define PCI_INTERRUPT_CTRL 0x9c + +#define PCI_CSI_CONTROL 0xe8 +#define PCI_CSI_CONTROL_PORTS_OFF_MASK 0x7 + +/* IOSF BT_MBI_UNIT_PMC regs */ +#define ISPSSPM0 0x39 +#define ISPSSPM0_ISPSSC_OFFSET 0 +#define ISPSSPM0_ISPSSC_MASK 0x00000003 +#define ISPSSPM0_ISPSSS_OFFSET 24 +#define ISPSSPM0_ISPSSS_MASK 0x03000000 +#define ISPSSPM0_IUNIT_POWER_ON 0x0 +#define ISPSSPM0_IUNIT_POWER_OFF 0x3 + +static int isp_set_power(struct pci_dev *dev, bool enable) +{ + unsigned long timeout; + u32 val = enable ? ISPSSPM0_IUNIT_POWER_ON : ISPSSPM0_IUNIT_POWER_OFF; + + /* Write to ISPSSPM0 bit[1:0] to power on/off the IUNIT */ + iosf_mbi_modify(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0, + val, ISPSSPM0_ISPSSC_MASK); + + /* + * There should be no IUNIT access while power-down is + * in progress. HW sighting: 4567865. + * Wait up to 50 ms for the IUNIT to shut down. + * And we do the same for power on. + */ + timeout = jiffies + msecs_to_jiffies(50); + do { + u32 tmp; + + /* Wait until ISPSSPM0 bit[25:24] shows the right value */ + iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0, &tmp); + tmp = (tmp & ISPSSPM0_ISPSSS_MASK) >> ISPSSPM0_ISPSSS_OFFSET; + if (tmp == val) + return 0; + + usleep_range(1000, 2000); + } while (time_before(jiffies, timeout)); + + dev_err(&dev->dev, "IUNIT power-%s timeout.\n", enable ? "on" : "off"); + return -EBUSY; +} + +static int isp_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + pm_runtime_allow(&dev->dev); + pm_runtime_put_sync_suspend(&dev->dev); + + return 0; +} + +static void isp_remove(struct pci_dev *dev) +{ + pm_runtime_get_sync(&dev->dev); + pm_runtime_forbid(&dev->dev); +} + +static int isp_pci_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + u32 val; + + pci_write_config_dword(pdev, PCI_INTERRUPT_CTRL, 0); + + /* + * MRFLD IUNIT DPHY is located in an always-power-on island + * MRFLD HW design need all CSI ports are disabled before + * powering down the IUNIT. + */ + pci_read_config_dword(pdev, PCI_CSI_CONTROL, &val); + val |= PCI_CSI_CONTROL_PORTS_OFF_MASK; + pci_write_config_dword(pdev, PCI_CSI_CONTROL, val); + + /* + * We lose config space access when punit power gates + * the ISP. Can't use pci_set_power_state() because + * pmcsr won't actually change when we write to it. + */ + pci_save_state(pdev); + pdev->current_state = PCI_D3cold; + isp_set_power(pdev, false); + + return 0; +} + +static int isp_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + isp_set_power(pdev, true); + pdev->current_state = PCI_D0; + pci_restore_state(pdev); + + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(isp_pm_ops, isp_pci_suspend, + isp_pci_resume, NULL); + +static const struct pci_device_id isp_id_table[] = { + { PCI_VDEVICE(INTEL, 0x0f38), }, + { PCI_VDEVICE(INTEL, 0x22b8), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, isp_id_table); + +static struct pci_driver isp_pci_driver = { + .name = "intel_atomisp2_pm", + .id_table = isp_id_table, + .probe = isp_probe, + .remove = isp_remove, + .driver.pm = &isp_pm_ops, +}; + +module_pci_driver(isp_pci_driver); + +MODULE_DESCRIPTION("Intel AtomISP2 dummy / power-management drv (for suspend)"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_atomisp2_led.c b/drivers/platform/x86/intel_atomisp2_led.c deleted file mode 100644 index 5935dfca166f..000000000000 --- a/drivers/platform/x86/intel_atomisp2_led.c +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Driver for controlling LEDs for cameras connected to the Intel atomisp2 - * The main purpose of this driver is to turn off LEDs which are on at boot. - * - * Copyright (C) 2020 Hans de Goede - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* This must be leds-gpio as the leds-gpio driver binds to the name */ -#define DEV_NAME "leds-gpio" - -static const struct gpio_led atomisp2_leds[] = { - { - .name = "atomisp2::camera", - .default_state = LEDS_GPIO_DEFSTATE_OFF, - }, -}; - -static const struct gpio_led_platform_data atomisp2_leds_pdata = { - .num_leds = ARRAY_SIZE(atomisp2_leds), - .leds = atomisp2_leds, -}; - -static struct gpiod_lookup_table asus_t100ta_lookup = { - .dev_id = DEV_NAME, - .table = { - GPIO_LOOKUP_IDX("INT33FC:02", 8, NULL, 0, GPIO_ACTIVE_HIGH), - { } - } -}; - -static struct gpiod_lookup_table asus_t100chi_lookup = { - .dev_id = DEV_NAME, - .table = { - GPIO_LOOKUP_IDX("INT33FC:01", 24, NULL, 0, GPIO_ACTIVE_HIGH), - { } - } -}; - -static const struct dmi_system_id atomisp2_led_systems[] __initconst = { - { - .matches = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TA"), - }, - .driver_data = &asus_t100ta_lookup, - }, - { - .matches = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T200TA"), - }, - .driver_data = &asus_t100ta_lookup, - }, - { - .matches = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100CHI"), - }, - .driver_data = &asus_t100chi_lookup, - }, - {} /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(dmi, atomisp2_led_systems); - -static struct gpiod_lookup_table *gpio_lookup; -static struct platform_device *pdev; - -static int __init atomisp2_led_init(void) -{ - const struct dmi_system_id *system; - - system = dmi_first_match(atomisp2_led_systems); - if (!system) - return -ENODEV; - - gpio_lookup = system->driver_data; - gpiod_add_lookup_table(gpio_lookup); - - pdev = platform_device_register_resndata(NULL, - DEV_NAME, PLATFORM_DEVID_NONE, - NULL, 0, &atomisp2_leds_pdata, - sizeof(atomisp2_leds_pdata)); - if (IS_ERR(pdev)) - gpiod_remove_lookup_table(gpio_lookup); - - return PTR_ERR_OR_ZERO(pdev); -} - -static void __exit atomisp2_led_cleanup(void) -{ - platform_device_unregister(pdev); - gpiod_remove_lookup_table(gpio_lookup); -} - -module_init(atomisp2_led_init); -module_exit(atomisp2_led_cleanup); - -/* - * The ACPI INIT method from Asus WMI's code on the T100TA and T200TA turns the - * LED on (without the WMI interface allowing further control over the LED). - * Ensure we are loaded after asus-nb-wmi so that we turn the LED off again. - */ -MODULE_SOFTDEP("pre: asus_nb_wmi"); -MODULE_AUTHOR("Hans de Goede - * - * Based on various non upstream patches for ISP support: - * Copyright (C) 2010-2017 Intel Corporation. All rights reserved. - * Copyright (c) 2010 Silicon Hive www.siliconhive.com. - */ - -#include -#include -#include -#include -#include -#include - -/* PCI configuration regs */ -#define PCI_INTERRUPT_CTRL 0x9c - -#define PCI_CSI_CONTROL 0xe8 -#define PCI_CSI_CONTROL_PORTS_OFF_MASK 0x7 - -/* IOSF BT_MBI_UNIT_PMC regs */ -#define ISPSSPM0 0x39 -#define ISPSSPM0_ISPSSC_OFFSET 0 -#define ISPSSPM0_ISPSSC_MASK 0x00000003 -#define ISPSSPM0_ISPSSS_OFFSET 24 -#define ISPSSPM0_ISPSSS_MASK 0x03000000 -#define ISPSSPM0_IUNIT_POWER_ON 0x0 -#define ISPSSPM0_IUNIT_POWER_OFF 0x3 - -static int isp_set_power(struct pci_dev *dev, bool enable) -{ - unsigned long timeout; - u32 val = enable ? ISPSSPM0_IUNIT_POWER_ON : ISPSSPM0_IUNIT_POWER_OFF; - - /* Write to ISPSSPM0 bit[1:0] to power on/off the IUNIT */ - iosf_mbi_modify(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0, - val, ISPSSPM0_ISPSSC_MASK); - - /* - * There should be no IUNIT access while power-down is - * in progress. HW sighting: 4567865. - * Wait up to 50 ms for the IUNIT to shut down. - * And we do the same for power on. - */ - timeout = jiffies + msecs_to_jiffies(50); - do { - u32 tmp; - - /* Wait until ISPSSPM0 bit[25:24] shows the right value */ - iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0, &tmp); - tmp = (tmp & ISPSSPM0_ISPSSS_MASK) >> ISPSSPM0_ISPSSS_OFFSET; - if (tmp == val) - return 0; - - usleep_range(1000, 2000); - } while (time_before(jiffies, timeout)); - - dev_err(&dev->dev, "IUNIT power-%s timeout.\n", enable ? "on" : "off"); - return -EBUSY; -} - -static int isp_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ - pm_runtime_allow(&dev->dev); - pm_runtime_put_sync_suspend(&dev->dev); - - return 0; -} - -static void isp_remove(struct pci_dev *dev) -{ - pm_runtime_get_sync(&dev->dev); - pm_runtime_forbid(&dev->dev); -} - -static int isp_pci_suspend(struct device *dev) -{ - struct pci_dev *pdev = to_pci_dev(dev); - u32 val; - - pci_write_config_dword(pdev, PCI_INTERRUPT_CTRL, 0); - - /* - * MRFLD IUNIT DPHY is located in an always-power-on island - * MRFLD HW design need all CSI ports are disabled before - * powering down the IUNIT. - */ - pci_read_config_dword(pdev, PCI_CSI_CONTROL, &val); - val |= PCI_CSI_CONTROL_PORTS_OFF_MASK; - pci_write_config_dword(pdev, PCI_CSI_CONTROL, val); - - /* - * We lose config space access when punit power gates - * the ISP. Can't use pci_set_power_state() because - * pmcsr won't actually change when we write to it. - */ - pci_save_state(pdev); - pdev->current_state = PCI_D3cold; - isp_set_power(pdev, false); - - return 0; -} - -static int isp_pci_resume(struct device *dev) -{ - struct pci_dev *pdev = to_pci_dev(dev); - - isp_set_power(pdev, true); - pdev->current_state = PCI_D0; - pci_restore_state(pdev); - - return 0; -} - -static UNIVERSAL_DEV_PM_OPS(isp_pm_ops, isp_pci_suspend, - isp_pci_resume, NULL); - -static const struct pci_device_id isp_id_table[] = { - { PCI_VDEVICE(INTEL, 0x0f38), }, - { PCI_VDEVICE(INTEL, 0x22b8), }, - { 0, } -}; -MODULE_DEVICE_TABLE(pci, isp_id_table); - -static struct pci_driver isp_pci_driver = { - .name = "intel_atomisp2_pm", - .id_table = isp_id_table, - .probe = isp_probe, - .remove = isp_remove, - .driver.pm = &isp_pm_ops, -}; - -module_pci_driver(isp_pci_driver); - -MODULE_DESCRIPTION("Intel AtomISP2 dummy / power-management drv (for suspend)"); -MODULE_AUTHOR("Hans de Goede "); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From c3d3586d12b106dd96c80febd571760a0b4095a5 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:53 +0300 Subject: platform/x86: intel-hid: Move to intel sub-directory Move Intel HID driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-16-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 13 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-hid.c | 732 ------------------------------------ drivers/platform/x86/intel/Kconfig | 13 + drivers/platform/x86/intel/Makefile | 4 + drivers/platform/x86/intel/hid.c | 732 ++++++++++++++++++++++++++++++++++++ 7 files changed, 750 insertions(+), 747 deletions(-) delete mode 100644 drivers/platform/x86/intel-hid.c create mode 100644 drivers/platform/x86/intel/hid.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 429b8b5c5283..5339b210d6a5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9321,7 +9321,7 @@ INTEL HID EVENT DRIVER M: Alex Hung L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel-hid.c +F: drivers/platform/x86/intel/hid.c INTEL I/OAT DMA DRIVER M: Dave Jiang diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 6de0d374ba16..6659b1036945 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,19 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/Kconfig" -config INTEL_HID_EVENT - tristate "INTEL HID Event" - depends on ACPI - depends on INPUT - depends on I2C - select INPUT_SPARSEKMAP - help - This driver provides support for the Intel HID Event hotkey interface. - Some laptops require this driver for hotkey support. - - To compile this driver as a module, choose M here: the module will - be called intel_hid. - config INTEL_INT0002_VGPIO tristate "Intel ACPI INT0002 Virtual GPIO driver" depends on GPIOLIB && ACPI && PM_SLEEP diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dccd7ecaae6a..1062773e8f41 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,7 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c deleted file mode 100644 index 2e4e97a626a5..000000000000 --- a/drivers/platform/x86/intel-hid.c +++ /dev/null @@ -1,732 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Intel HID event & 5 button array driver - * - * Copyright (C) 2015 Alex Hung - * Copyright (C) 2015 Andrew Lutomirski - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "dual_accel_detect.h" - -/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ -#define TABLET_MODE_FLAG BIT(6) - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Alex Hung"); - -static const struct acpi_device_id intel_hid_ids[] = { - {"INT33D5", 0}, - {"INTC1051", 0}, - {"INTC1054", 0}, - {"INTC1070", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, intel_hid_ids); - -/* In theory, these are HID usages. */ -static const struct key_entry intel_hid_keymap[] = { - /* 1: LSuper (Page 0x07, usage 0xE3) -- unclear what to do */ - /* 2: Toggle SW_ROTATE_LOCK -- easy to implement if seen in wild */ - { KE_KEY, 3, { KEY_NUMLOCK } }, - { KE_KEY, 4, { KEY_HOME } }, - { KE_KEY, 5, { KEY_END } }, - { KE_KEY, 6, { KEY_PAGEUP } }, - { KE_KEY, 7, { KEY_PAGEDOWN } }, - { KE_KEY, 8, { KEY_RFKILL } }, - { KE_KEY, 9, { KEY_POWER } }, - { KE_KEY, 11, { KEY_SLEEP } }, - /* 13 has two different meanings in the spec -- ignore it. */ - { KE_KEY, 14, { KEY_STOPCD } }, - { KE_KEY, 15, { KEY_PLAYPAUSE } }, - { KE_KEY, 16, { KEY_MUTE } }, - { KE_KEY, 17, { KEY_VOLUMEUP } }, - { KE_KEY, 18, { KEY_VOLUMEDOWN } }, - { KE_KEY, 19, { KEY_BRIGHTNESSUP } }, - { KE_KEY, 20, { KEY_BRIGHTNESSDOWN } }, - /* 27: wake -- needs special handling */ - { KE_END }, -}; - -/* 5 button array notification value. */ -static const struct key_entry intel_array_keymap[] = { - { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* Press */ - { KE_IGNORE, 0xC3, { KEY_LEFTMETA } }, /* Release */ - { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* Press */ - { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* Release */ - { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* Press */ - { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* Release */ - { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* Press */ - { KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* Release */ - { KE_KEY, 0xCE, { KEY_POWER } }, /* Press */ - { KE_IGNORE, 0xCF, { KEY_POWER } }, /* Release */ - { KE_END }, -}; - -static const struct dmi_system_id button_array_table[] = { - { - .ident = "Wacom MobileStudio Pro 13", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"), - DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 13"), - }, - }, - { - .ident = "Wacom MobileStudio Pro 16", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"), - DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 16"), - }, - }, - { - .ident = "HP Spectre x2 (2015)", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "HP"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"), - }, - }, - { - .ident = "Lenovo ThinkPad X1 Tablet Gen 2", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2"), - }, - }, - { } -}; - -/* - * Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE, - * these need to be compared via a DMI based authorization list because some - * models have unreliable VGBS return which could cause incorrect - * SW_TABLET_MODE report. - */ -static const struct dmi_system_id dmi_vgbs_allow_list[] = { - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "HP"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"), - }, - }, - { } -}; - -struct intel_hid_priv { - struct input_dev *input_dev; - struct input_dev *array; - struct input_dev *switches; - bool wakeup_mode; - bool dual_accel; -}; - -#define HID_EVENT_FILTER_UUID "eeec56b3-4442-408f-a792-4edd4d758054" - -enum intel_hid_dsm_fn_codes { - INTEL_HID_DSM_FN_INVALID, - INTEL_HID_DSM_BTNL_FN, - INTEL_HID_DSM_HDMM_FN, - INTEL_HID_DSM_HDSM_FN, - INTEL_HID_DSM_HDEM_FN, - INTEL_HID_DSM_BTNS_FN, - INTEL_HID_DSM_BTNE_FN, - INTEL_HID_DSM_HEBC_V1_FN, - INTEL_HID_DSM_VGBS_FN, - INTEL_HID_DSM_HEBC_V2_FN, - INTEL_HID_DSM_FN_MAX -}; - -static const char *intel_hid_dsm_fn_to_method[INTEL_HID_DSM_FN_MAX] = { - NULL, - "BTNL", - "HDMM", - "HDSM", - "HDEM", - "BTNS", - "BTNE", - "HEBC", - "VGBS", - "HEBC" -}; - -static unsigned long long intel_hid_dsm_fn_mask; -static guid_t intel_dsm_guid; - -static bool intel_hid_execute_method(acpi_handle handle, - enum intel_hid_dsm_fn_codes fn_index, - unsigned long long arg) -{ - union acpi_object *obj, argv4, req; - acpi_status status; - char *method_name; - - if (fn_index <= INTEL_HID_DSM_FN_INVALID || - fn_index >= INTEL_HID_DSM_FN_MAX) - return false; - - method_name = (char *)intel_hid_dsm_fn_to_method[fn_index]; - - if (!(intel_hid_dsm_fn_mask & BIT(fn_index))) - goto skip_dsm_exec; - - /* All methods expects a package with one integer element */ - req.type = ACPI_TYPE_INTEGER; - req.integer.value = arg; - - argv4.type = ACPI_TYPE_PACKAGE; - argv4.package.count = 1; - argv4.package.elements = &req; - - obj = acpi_evaluate_dsm(handle, &intel_dsm_guid, 1, fn_index, &argv4); - if (obj) { - acpi_handle_debug(handle, "Exec DSM Fn code: %d[%s] success\n", - fn_index, method_name); - ACPI_FREE(obj); - return true; - } - -skip_dsm_exec: - status = acpi_execute_simple_method(handle, method_name, arg); - if (ACPI_SUCCESS(status)) - return true; - - return false; -} - -static bool intel_hid_evaluate_method(acpi_handle handle, - enum intel_hid_dsm_fn_codes fn_index, - unsigned long long *result) -{ - union acpi_object *obj; - acpi_status status; - char *method_name; - - if (fn_index <= INTEL_HID_DSM_FN_INVALID || - fn_index >= INTEL_HID_DSM_FN_MAX) - return false; - - method_name = (char *)intel_hid_dsm_fn_to_method[fn_index]; - - if (!(intel_hid_dsm_fn_mask & fn_index)) - goto skip_dsm_eval; - - obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, - 1, fn_index, - NULL, ACPI_TYPE_INTEGER); - if (obj) { - *result = obj->integer.value; - acpi_handle_debug(handle, - "Eval DSM Fn code: %d[%s] results: 0x%llx\n", - fn_index, method_name, *result); - ACPI_FREE(obj); - return true; - } - -skip_dsm_eval: - status = acpi_evaluate_integer(handle, method_name, NULL, result); - if (ACPI_SUCCESS(status)) - return true; - - return false; -} - -static void intel_hid_init_dsm(acpi_handle handle) -{ - union acpi_object *obj; - - guid_parse(HID_EVENT_FILTER_UUID, &intel_dsm_guid); - - obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL, - ACPI_TYPE_BUFFER); - if (obj) { - switch (obj->buffer.length) { - default: - case 2: - intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer; - break; - case 1: - intel_hid_dsm_fn_mask = *obj->buffer.pointer; - break; - case 0: - acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n"); - intel_hid_dsm_fn_mask = 0; - break; - } - ACPI_FREE(obj); - } - - acpi_handle_debug(handle, "intel_hid_dsm_fn_mask = %llx\n", - intel_hid_dsm_fn_mask); -} - -static int intel_hid_set_enable(struct device *device, bool enable) -{ - acpi_handle handle = ACPI_HANDLE(device); - - /* Enable|disable features - power button is always enabled */ - if (!intel_hid_execute_method(handle, INTEL_HID_DSM_HDSM_FN, - enable)) { - dev_warn(device, "failed to %sable hotkeys\n", - enable ? "en" : "dis"); - return -EIO; - } - - return 0; -} - -static void intel_button_array_enable(struct device *device, bool enable) -{ - struct intel_hid_priv *priv = dev_get_drvdata(device); - acpi_handle handle = ACPI_HANDLE(device); - unsigned long long button_cap; - acpi_status status; - - if (!priv->array) - return; - - /* Query supported platform features */ - status = acpi_evaluate_integer(handle, "BTNC", NULL, &button_cap); - if (ACPI_FAILURE(status)) { - dev_warn(device, "failed to get button capability\n"); - return; - } - - /* Enable|disable features - power button is always enabled */ - if (!intel_hid_execute_method(handle, INTEL_HID_DSM_BTNE_FN, - enable ? button_cap : 1)) - dev_warn(device, "failed to set button capability\n"); -} - -static int intel_hid_pm_prepare(struct device *device) -{ - if (device_may_wakeup(device)) { - struct intel_hid_priv *priv = dev_get_drvdata(device); - - priv->wakeup_mode = true; - } - return 0; -} - -static void intel_hid_pm_complete(struct device *device) -{ - struct intel_hid_priv *priv = dev_get_drvdata(device); - - priv->wakeup_mode = false; -} - -static int intel_hid_pl_suspend_handler(struct device *device) -{ - intel_button_array_enable(device, false); - - if (!pm_suspend_no_platform()) - intel_hid_set_enable(device, false); - - return 0; -} - -static int intel_hid_pl_resume_handler(struct device *device) -{ - intel_hid_pm_complete(device); - - if (!pm_suspend_no_platform()) - intel_hid_set_enable(device, true); - - intel_button_array_enable(device, true); - return 0; -} - -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, - .thaw = intel_hid_pl_resume_handler, - .restore = intel_hid_pl_resume_handler, - .suspend = intel_hid_pl_suspend_handler, - .resume = intel_hid_pl_resume_handler, -}; - -static int intel_hid_input_setup(struct platform_device *device) -{ - struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); - int ret; - - priv->input_dev = devm_input_allocate_device(&device->dev); - if (!priv->input_dev) - return -ENOMEM; - - ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL); - if (ret) - return ret; - - priv->input_dev->name = "Intel HID events"; - priv->input_dev->id.bustype = BUS_HOST; - - return input_register_device(priv->input_dev); -} - -static int intel_button_array_input_setup(struct platform_device *device) -{ - struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); - int ret; - - /* Setup input device for 5 button array */ - priv->array = devm_input_allocate_device(&device->dev); - if (!priv->array) - return -ENOMEM; - - ret = sparse_keymap_setup(priv->array, intel_array_keymap, NULL); - if (ret) - return ret; - - priv->array->name = "Intel HID 5 button array"; - priv->array->id.bustype = BUS_HOST; - - return input_register_device(priv->array); -} - -static int intel_hid_switches_setup(struct platform_device *device) -{ - struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); - - /* Setup input device for switches */ - priv->switches = devm_input_allocate_device(&device->dev); - if (!priv->switches) - return -ENOMEM; - - __set_bit(EV_SW, priv->switches->evbit); - __set_bit(SW_TABLET_MODE, priv->switches->swbit); - - priv->switches->name = "Intel HID switches"; - priv->switches->id.bustype = BUS_HOST; - return input_register_device(priv->switches); -} - -static void report_tablet_mode_state(struct platform_device *device) -{ - struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); - acpi_handle handle = ACPI_HANDLE(&device->dev); - unsigned long long vgbs; - int m; - - if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs)) - return; - - m = !(vgbs & TABLET_MODE_FLAG); - input_report_switch(priv->switches, SW_TABLET_MODE, m); - input_sync(priv->switches); -} - -static bool report_tablet_mode_event(struct input_dev *input_dev, u32 event) -{ - if (!input_dev) - return false; - - switch (event) { - case 0xcc: - input_report_switch(input_dev, SW_TABLET_MODE, 1); - input_sync(input_dev); - return true; - case 0xcd: - input_report_switch(input_dev, SW_TABLET_MODE, 0); - input_sync(input_dev); - return true; - default: - return false; - } -} - -static void notify_handler(acpi_handle handle, u32 event, void *context) -{ - struct platform_device *device = context; - struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); - unsigned long long ev_index; - int err; - - /* - * Some convertible have unreliable VGBS return which could cause incorrect - * SW_TABLET_MODE report, in these cases we enable support when receiving - * the first event instead of during driver setup. - * - * See dual_accel_detect.h for more info on the dual_accel check. - */ - if (!priv->switches && !priv->dual_accel && (event == 0xcc || event == 0xcd)) { - dev_info(&device->dev, "switch event received, enable switches supports\n"); - err = intel_hid_switches_setup(device); - if (err) - pr_err("Failed to setup Intel HID switches\n"); - } - - if (priv->wakeup_mode) { - /* - * Needed for wakeup from suspend-to-idle to work on some - * platforms that don't expose the 5-button array, but still - * send notifies with the power button event code to this - * device object on power button actions while suspended. - */ - if (event == 0xce) - goto wakeup; - - /* - * Some devices send (duplicate) tablet-mode events when moved - * around even though the mode has not changed; and they do this - * even when suspended. - * Update the switch state in case it changed and then return - * without waking up to avoid spurious wakeups. - */ - if (event == 0xcc || event == 0xcd) { - report_tablet_mode_event(priv->switches, event); - return; - } - - /* Wake up on 5-button array events only. */ - if (event == 0xc0 || !priv->array) - return; - - if (!sparse_keymap_entry_from_scancode(priv->array, event)) { - dev_info(&device->dev, "unknown event 0x%x\n", event); - return; - } - -wakeup: - pm_wakeup_hard_event(&device->dev); - - return; - } - - /* - * Needed for suspend to work on some platforms that don't expose - * the 5-button array, but still send notifies with power button - * event code to this device object on power button actions. - * - * Report the power button press and release. - */ - if (!priv->array) { - if (event == 0xce) { - input_report_key(priv->input_dev, KEY_POWER, 1); - input_sync(priv->input_dev); - return; - } - - if (event == 0xcf) { - input_report_key(priv->input_dev, KEY_POWER, 0); - input_sync(priv->input_dev); - return; - } - } - - if (report_tablet_mode_event(priv->switches, event)) - return; - - /* 0xC0 is for HID events, other values are for 5 button array */ - if (event != 0xc0) { - if (!priv->array || - !sparse_keymap_report_event(priv->array, event, 1, true)) - dev_dbg(&device->dev, "unknown event 0x%x\n", event); - return; - } - - if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDEM_FN, - &ev_index)) { - dev_warn(&device->dev, "failed to get event index\n"); - return; - } - - if (!sparse_keymap_report_event(priv->input_dev, ev_index, 1, true)) - dev_dbg(&device->dev, "unknown event index 0x%llx\n", - ev_index); -} - -static bool button_array_present(struct platform_device *device) -{ - acpi_handle handle = ACPI_HANDLE(&device->dev); - unsigned long long event_cap; - - if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V2_FN, - &event_cap)) { - /* Check presence of 5 button array or v2 power button */ - if (event_cap & 0x60000) - return true; - } - - if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V1_FN, - &event_cap)) { - if (event_cap & 0x20000) - return true; - } - - if (dmi_check_system(button_array_table)) - return true; - - return false; -} - -static int intel_hid_probe(struct platform_device *device) -{ - acpi_handle handle = ACPI_HANDLE(&device->dev); - unsigned long long mode; - struct intel_hid_priv *priv; - acpi_status status; - int err; - - intel_hid_init_dsm(handle); - - if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) { - dev_warn(&device->dev, "failed to read mode\n"); - return -ENODEV; - } - - if (mode != 0) { - /* - * This driver only implements "simple" mode. There appear - * to be no other modes, but we should be paranoid and check - * for compatibility. - */ - dev_info(&device->dev, "platform is not in simple mode\n"); - return -ENODEV; - } - - priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - dev_set_drvdata(&device->dev, priv); - - priv->dual_accel = dual_accel_detect(); - - err = intel_hid_input_setup(device); - if (err) { - pr_err("Failed to setup Intel HID hotkeys\n"); - return err; - } - - /* Setup 5 button array */ - if (button_array_present(device)) { - dev_info(&device->dev, "platform supports 5 button array\n"); - err = intel_button_array_input_setup(device); - if (err) - pr_err("Failed to setup Intel 5 button array hotkeys\n"); - } - - /* Setup switches for devices that we know VGBS return correctly */ - if (dmi_check_system(dmi_vgbs_allow_list)) { - dev_info(&device->dev, "platform supports switches\n"); - err = intel_hid_switches_setup(device); - if (err) - pr_err("Failed to setup Intel HID switches\n"); - else - report_tablet_mode_state(device); - } - - status = acpi_install_notify_handler(handle, - ACPI_DEVICE_NOTIFY, - notify_handler, - device); - if (ACPI_FAILURE(status)) - return -EBUSY; - - err = intel_hid_set_enable(&device->dev, true); - if (err) - goto err_remove_notify; - - if (priv->array) { - unsigned long long dummy; - - intel_button_array_enable(&device->dev, true); - - /* Call button load method to enable HID power button */ - if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_BTNL_FN, - &dummy)) { - dev_warn(&device->dev, - "failed to enable HID power button\n"); - } - } - - device_init_wakeup(&device->dev, true); - /* - * In order for system wakeup to work, the EC GPE has to be marked as - * a wakeup one, so do that here (this setting will persist, but it has - * no effect until the wakeup mask is set for the EC GPE). - */ - acpi_ec_mark_gpe_for_wake(); - return 0; - -err_remove_notify: - acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); - - return err; -} - -static int intel_hid_remove(struct platform_device *device) -{ - acpi_handle handle = ACPI_HANDLE(&device->dev); - - device_init_wakeup(&device->dev, false); - acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); - intel_hid_set_enable(&device->dev, false); - intel_button_array_enable(&device->dev, false); - - /* - * Even if we failed to shut off the event stream, we can still - * safely detach from the device. - */ - return 0; -} - -static struct platform_driver intel_hid_pl_driver = { - .driver = { - .name = "intel-hid", - .acpi_match_table = intel_hid_ids, - .pm = &intel_hid_pl_pm_ops, - }, - .probe = intel_hid_probe, - .remove = intel_hid_remove, -}; - -/* - * Unfortunately, some laptops provide a _HID="INT33D5" device with - * _CID="PNP0C02". This causes the pnpacpi scan driver to claim the - * ACPI node, so no platform device will be created. The pnpacpi - * driver rejects this device in subsequent processing, so no physical - * node is created at all. - * - * As a workaround until the ACPI core figures out how to handle - * this corner case, manually ask the ACPI platform device code to - * claim the ACPI node. - */ -static acpi_status __init -check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) -{ - const struct acpi_device_id *ids = context; - struct acpi_device *dev; - - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) - if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) - dev_info(&dev->dev, - "intel-hid: created platform device\n"); - - return AE_OK; -} - -static int __init intel_hid_init(void) -{ - acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, check_acpi_dev, NULL, - (void *)intel_hid_ids, NULL); - - return platform_driver_register(&intel_hid_pl_driver); -} -module_init(intel_hid_init); - -static void __exit intel_hid_exit(void) -{ - platform_driver_unregister(&intel_hid_pl_driver); -} -module_exit(intel_hid_exit); diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 9d0bc76a8948..eb6ac7e52cd6 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -25,6 +25,19 @@ source "drivers/platform/x86/intel/pmt/Kconfig" source "drivers/platform/x86/intel/speed_select_if/Kconfig" source "drivers/platform/x86/intel/telemetry/Kconfig" +config INTEL_HID_EVENT + tristate "Intel HID Event" + depends on ACPI + depends on INPUT + depends on I2C + select INPUT_SPARSEKMAP + help + This driver provides support for the Intel HID Event hotkey interface. + Some laptops require this driver for hotkey support. + + To compile this driver as a module, choose M here: the module will + be called intel_hid. + config INTEL_BXTWC_PMIC_TMU tristate "Intel Broxton Whiskey Cove TMU Driver" depends on INTEL_SOC_PMIC_BXTWC diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 080589b3082d..d0b5ef290bcd 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -13,6 +13,10 @@ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ +# Intel input drivers +intel-hid-y := hid.o +obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o + # Intel PMIC / PMC / P-Unit drivers intel_bxtwc_tmu-y := bxtwc_tmu.o obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c new file mode 100644 index 000000000000..a33a5826e81a --- /dev/null +++ b/drivers/platform/x86/intel/hid.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Intel HID event & 5 button array driver + * + * Copyright (C) 2015 Alex Hung + * Copyright (C) 2015 Andrew Lutomirski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../dual_accel_detect.h" + +/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ +#define TABLET_MODE_FLAG BIT(6) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alex Hung"); + +static const struct acpi_device_id intel_hid_ids[] = { + {"INT33D5", 0}, + {"INTC1051", 0}, + {"INTC1054", 0}, + {"INTC1070", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, intel_hid_ids); + +/* In theory, these are HID usages. */ +static const struct key_entry intel_hid_keymap[] = { + /* 1: LSuper (Page 0x07, usage 0xE3) -- unclear what to do */ + /* 2: Toggle SW_ROTATE_LOCK -- easy to implement if seen in wild */ + { KE_KEY, 3, { KEY_NUMLOCK } }, + { KE_KEY, 4, { KEY_HOME } }, + { KE_KEY, 5, { KEY_END } }, + { KE_KEY, 6, { KEY_PAGEUP } }, + { KE_KEY, 7, { KEY_PAGEDOWN } }, + { KE_KEY, 8, { KEY_RFKILL } }, + { KE_KEY, 9, { KEY_POWER } }, + { KE_KEY, 11, { KEY_SLEEP } }, + /* 13 has two different meanings in the spec -- ignore it. */ + { KE_KEY, 14, { KEY_STOPCD } }, + { KE_KEY, 15, { KEY_PLAYPAUSE } }, + { KE_KEY, 16, { KEY_MUTE } }, + { KE_KEY, 17, { KEY_VOLUMEUP } }, + { KE_KEY, 18, { KEY_VOLUMEDOWN } }, + { KE_KEY, 19, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 20, { KEY_BRIGHTNESSDOWN } }, + /* 27: wake -- needs special handling */ + { KE_END }, +}; + +/* 5 button array notification value. */ +static const struct key_entry intel_array_keymap[] = { + { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* Press */ + { KE_IGNORE, 0xC3, { KEY_LEFTMETA } }, /* Release */ + { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* Press */ + { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* Release */ + { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* Press */ + { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* Release */ + { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* Press */ + { KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* Release */ + { KE_KEY, 0xCE, { KEY_POWER } }, /* Press */ + { KE_IGNORE, 0xCF, { KEY_POWER } }, /* Release */ + { KE_END }, +}; + +static const struct dmi_system_id button_array_table[] = { + { + .ident = "Wacom MobileStudio Pro 13", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"), + DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 13"), + }, + }, + { + .ident = "Wacom MobileStudio Pro 16", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"), + DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 16"), + }, + }, + { + .ident = "HP Spectre x2 (2015)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"), + }, + }, + { + .ident = "Lenovo ThinkPad X1 Tablet Gen 2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2"), + }, + }, + { } +}; + +/* + * Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE, + * these need to be compared via a DMI based authorization list because some + * models have unreliable VGBS return which could cause incorrect + * SW_TABLET_MODE report. + */ +static const struct dmi_system_id dmi_vgbs_allow_list[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"), + }, + }, + { } +}; + +struct intel_hid_priv { + struct input_dev *input_dev; + struct input_dev *array; + struct input_dev *switches; + bool wakeup_mode; + bool dual_accel; +}; + +#define HID_EVENT_FILTER_UUID "eeec56b3-4442-408f-a792-4edd4d758054" + +enum intel_hid_dsm_fn_codes { + INTEL_HID_DSM_FN_INVALID, + INTEL_HID_DSM_BTNL_FN, + INTEL_HID_DSM_HDMM_FN, + INTEL_HID_DSM_HDSM_FN, + INTEL_HID_DSM_HDEM_FN, + INTEL_HID_DSM_BTNS_FN, + INTEL_HID_DSM_BTNE_FN, + INTEL_HID_DSM_HEBC_V1_FN, + INTEL_HID_DSM_VGBS_FN, + INTEL_HID_DSM_HEBC_V2_FN, + INTEL_HID_DSM_FN_MAX +}; + +static const char *intel_hid_dsm_fn_to_method[INTEL_HID_DSM_FN_MAX] = { + NULL, + "BTNL", + "HDMM", + "HDSM", + "HDEM", + "BTNS", + "BTNE", + "HEBC", + "VGBS", + "HEBC" +}; + +static unsigned long long intel_hid_dsm_fn_mask; +static guid_t intel_dsm_guid; + +static bool intel_hid_execute_method(acpi_handle handle, + enum intel_hid_dsm_fn_codes fn_index, + unsigned long long arg) +{ + union acpi_object *obj, argv4, req; + acpi_status status; + char *method_name; + + if (fn_index <= INTEL_HID_DSM_FN_INVALID || + fn_index >= INTEL_HID_DSM_FN_MAX) + return false; + + method_name = (char *)intel_hid_dsm_fn_to_method[fn_index]; + + if (!(intel_hid_dsm_fn_mask & BIT(fn_index))) + goto skip_dsm_exec; + + /* All methods expects a package with one integer element */ + req.type = ACPI_TYPE_INTEGER; + req.integer.value = arg; + + argv4.type = ACPI_TYPE_PACKAGE; + argv4.package.count = 1; + argv4.package.elements = &req; + + obj = acpi_evaluate_dsm(handle, &intel_dsm_guid, 1, fn_index, &argv4); + if (obj) { + acpi_handle_debug(handle, "Exec DSM Fn code: %d[%s] success\n", + fn_index, method_name); + ACPI_FREE(obj); + return true; + } + +skip_dsm_exec: + status = acpi_execute_simple_method(handle, method_name, arg); + if (ACPI_SUCCESS(status)) + return true; + + return false; +} + +static bool intel_hid_evaluate_method(acpi_handle handle, + enum intel_hid_dsm_fn_codes fn_index, + unsigned long long *result) +{ + union acpi_object *obj; + acpi_status status; + char *method_name; + + if (fn_index <= INTEL_HID_DSM_FN_INVALID || + fn_index >= INTEL_HID_DSM_FN_MAX) + return false; + + method_name = (char *)intel_hid_dsm_fn_to_method[fn_index]; + + if (!(intel_hid_dsm_fn_mask & fn_index)) + goto skip_dsm_eval; + + obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, + 1, fn_index, + NULL, ACPI_TYPE_INTEGER); + if (obj) { + *result = obj->integer.value; + acpi_handle_debug(handle, + "Eval DSM Fn code: %d[%s] results: 0x%llx\n", + fn_index, method_name, *result); + ACPI_FREE(obj); + return true; + } + +skip_dsm_eval: + status = acpi_evaluate_integer(handle, method_name, NULL, result); + if (ACPI_SUCCESS(status)) + return true; + + return false; +} + +static void intel_hid_init_dsm(acpi_handle handle) +{ + union acpi_object *obj; + + guid_parse(HID_EVENT_FILTER_UUID, &intel_dsm_guid); + + obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL, + ACPI_TYPE_BUFFER); + if (obj) { + switch (obj->buffer.length) { + default: + case 2: + intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer; + break; + case 1: + intel_hid_dsm_fn_mask = *obj->buffer.pointer; + break; + case 0: + acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n"); + intel_hid_dsm_fn_mask = 0; + break; + } + ACPI_FREE(obj); + } + + acpi_handle_debug(handle, "intel_hid_dsm_fn_mask = %llx\n", + intel_hid_dsm_fn_mask); +} + +static int intel_hid_set_enable(struct device *device, bool enable) +{ + acpi_handle handle = ACPI_HANDLE(device); + + /* Enable|disable features - power button is always enabled */ + if (!intel_hid_execute_method(handle, INTEL_HID_DSM_HDSM_FN, + enable)) { + dev_warn(device, "failed to %sable hotkeys\n", + enable ? "en" : "dis"); + return -EIO; + } + + return 0; +} + +static void intel_button_array_enable(struct device *device, bool enable) +{ + struct intel_hid_priv *priv = dev_get_drvdata(device); + acpi_handle handle = ACPI_HANDLE(device); + unsigned long long button_cap; + acpi_status status; + + if (!priv->array) + return; + + /* Query supported platform features */ + status = acpi_evaluate_integer(handle, "BTNC", NULL, &button_cap); + if (ACPI_FAILURE(status)) { + dev_warn(device, "failed to get button capability\n"); + return; + } + + /* Enable|disable features - power button is always enabled */ + if (!intel_hid_execute_method(handle, INTEL_HID_DSM_BTNE_FN, + enable ? button_cap : 1)) + dev_warn(device, "failed to set button capability\n"); +} + +static int intel_hid_pm_prepare(struct device *device) +{ + if (device_may_wakeup(device)) { + struct intel_hid_priv *priv = dev_get_drvdata(device); + + priv->wakeup_mode = true; + } + return 0; +} + +static void intel_hid_pm_complete(struct device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(device); + + priv->wakeup_mode = false; +} + +static int intel_hid_pl_suspend_handler(struct device *device) +{ + intel_button_array_enable(device, false); + + if (!pm_suspend_no_platform()) + intel_hid_set_enable(device, false); + + return 0; +} + +static int intel_hid_pl_resume_handler(struct device *device) +{ + intel_hid_pm_complete(device); + + if (!pm_suspend_no_platform()) + intel_hid_set_enable(device, true); + + intel_button_array_enable(device, true); + return 0; +} + +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, + .thaw = intel_hid_pl_resume_handler, + .restore = intel_hid_pl_resume_handler, + .suspend = intel_hid_pl_suspend_handler, + .resume = intel_hid_pl_resume_handler, +}; + +static int intel_hid_input_setup(struct platform_device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + int ret; + + priv->input_dev = devm_input_allocate_device(&device->dev); + if (!priv->input_dev) + return -ENOMEM; + + ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL); + if (ret) + return ret; + + priv->input_dev->name = "Intel HID events"; + priv->input_dev->id.bustype = BUS_HOST; + + return input_register_device(priv->input_dev); +} + +static int intel_button_array_input_setup(struct platform_device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + int ret; + + /* Setup input device for 5 button array */ + priv->array = devm_input_allocate_device(&device->dev); + if (!priv->array) + return -ENOMEM; + + ret = sparse_keymap_setup(priv->array, intel_array_keymap, NULL); + if (ret) + return ret; + + priv->array->name = "Intel HID 5 button array"; + priv->array->id.bustype = BUS_HOST; + + return input_register_device(priv->array); +} + +static int intel_hid_switches_setup(struct platform_device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + + /* Setup input device for switches */ + priv->switches = devm_input_allocate_device(&device->dev); + if (!priv->switches) + return -ENOMEM; + + __set_bit(EV_SW, priv->switches->evbit); + __set_bit(SW_TABLET_MODE, priv->switches->swbit); + + priv->switches->name = "Intel HID switches"; + priv->switches->id.bustype = BUS_HOST; + return input_register_device(priv->switches); +} + +static void report_tablet_mode_state(struct platform_device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long vgbs; + int m; + + if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs)) + return; + + m = !(vgbs & TABLET_MODE_FLAG); + input_report_switch(priv->switches, SW_TABLET_MODE, m); + input_sync(priv->switches); +} + +static bool report_tablet_mode_event(struct input_dev *input_dev, u32 event) +{ + if (!input_dev) + return false; + + switch (event) { + case 0xcc: + input_report_switch(input_dev, SW_TABLET_MODE, 1); + input_sync(input_dev); + return true; + case 0xcd: + input_report_switch(input_dev, SW_TABLET_MODE, 0); + input_sync(input_dev); + return true; + default: + return false; + } +} + +static void notify_handler(acpi_handle handle, u32 event, void *context) +{ + struct platform_device *device = context; + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + unsigned long long ev_index; + int err; + + /* + * Some convertible have unreliable VGBS return which could cause incorrect + * SW_TABLET_MODE report, in these cases we enable support when receiving + * the first event instead of during driver setup. + * + * See dual_accel_detect.h for more info on the dual_accel check. + */ + if (!priv->switches && !priv->dual_accel && (event == 0xcc || event == 0xcd)) { + dev_info(&device->dev, "switch event received, enable switches supports\n"); + err = intel_hid_switches_setup(device); + if (err) + pr_err("Failed to setup Intel HID switches\n"); + } + + if (priv->wakeup_mode) { + /* + * Needed for wakeup from suspend-to-idle to work on some + * platforms that don't expose the 5-button array, but still + * send notifies with the power button event code to this + * device object on power button actions while suspended. + */ + if (event == 0xce) + goto wakeup; + + /* + * Some devices send (duplicate) tablet-mode events when moved + * around even though the mode has not changed; and they do this + * even when suspended. + * Update the switch state in case it changed and then return + * without waking up to avoid spurious wakeups. + */ + if (event == 0xcc || event == 0xcd) { + report_tablet_mode_event(priv->switches, event); + return; + } + + /* Wake up on 5-button array events only. */ + if (event == 0xc0 || !priv->array) + return; + + if (!sparse_keymap_entry_from_scancode(priv->array, event)) { + dev_info(&device->dev, "unknown event 0x%x\n", event); + return; + } + +wakeup: + pm_wakeup_hard_event(&device->dev); + + return; + } + + /* + * Needed for suspend to work on some platforms that don't expose + * the 5-button array, but still send notifies with power button + * event code to this device object on power button actions. + * + * Report the power button press and release. + */ + if (!priv->array) { + if (event == 0xce) { + input_report_key(priv->input_dev, KEY_POWER, 1); + input_sync(priv->input_dev); + return; + } + + if (event == 0xcf) { + input_report_key(priv->input_dev, KEY_POWER, 0); + input_sync(priv->input_dev); + return; + } + } + + if (report_tablet_mode_event(priv->switches, event)) + return; + + /* 0xC0 is for HID events, other values are for 5 button array */ + if (event != 0xc0) { + if (!priv->array || + !sparse_keymap_report_event(priv->array, event, 1, true)) + dev_dbg(&device->dev, "unknown event 0x%x\n", event); + return; + } + + if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDEM_FN, + &ev_index)) { + dev_warn(&device->dev, "failed to get event index\n"); + return; + } + + if (!sparse_keymap_report_event(priv->input_dev, ev_index, 1, true)) + dev_dbg(&device->dev, "unknown event index 0x%llx\n", + ev_index); +} + +static bool button_array_present(struct platform_device *device) +{ + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long event_cap; + + if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V2_FN, + &event_cap)) { + /* Check presence of 5 button array or v2 power button */ + if (event_cap & 0x60000) + return true; + } + + if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V1_FN, + &event_cap)) { + if (event_cap & 0x20000) + return true; + } + + if (dmi_check_system(button_array_table)) + return true; + + return false; +} + +static int intel_hid_probe(struct platform_device *device) +{ + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long mode; + struct intel_hid_priv *priv; + acpi_status status; + int err; + + intel_hid_init_dsm(handle); + + if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) { + dev_warn(&device->dev, "failed to read mode\n"); + return -ENODEV; + } + + if (mode != 0) { + /* + * This driver only implements "simple" mode. There appear + * to be no other modes, but we should be paranoid and check + * for compatibility. + */ + dev_info(&device->dev, "platform is not in simple mode\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(&device->dev, priv); + + priv->dual_accel = dual_accel_detect(); + + err = intel_hid_input_setup(device); + if (err) { + pr_err("Failed to setup Intel HID hotkeys\n"); + return err; + } + + /* Setup 5 button array */ + if (button_array_present(device)) { + dev_info(&device->dev, "platform supports 5 button array\n"); + err = intel_button_array_input_setup(device); + if (err) + pr_err("Failed to setup Intel 5 button array hotkeys\n"); + } + + /* Setup switches for devices that we know VGBS return correctly */ + if (dmi_check_system(dmi_vgbs_allow_list)) { + dev_info(&device->dev, "platform supports switches\n"); + err = intel_hid_switches_setup(device); + if (err) + pr_err("Failed to setup Intel HID switches\n"); + else + report_tablet_mode_state(device); + } + + status = acpi_install_notify_handler(handle, + ACPI_DEVICE_NOTIFY, + notify_handler, + device); + if (ACPI_FAILURE(status)) + return -EBUSY; + + err = intel_hid_set_enable(&device->dev, true); + if (err) + goto err_remove_notify; + + if (priv->array) { + unsigned long long dummy; + + intel_button_array_enable(&device->dev, true); + + /* Call button load method to enable HID power button */ + if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_BTNL_FN, + &dummy)) { + dev_warn(&device->dev, + "failed to enable HID power button\n"); + } + } + + device_init_wakeup(&device->dev, true); + /* + * In order for system wakeup to work, the EC GPE has to be marked as + * a wakeup one, so do that here (this setting will persist, but it has + * no effect until the wakeup mask is set for the EC GPE). + */ + acpi_ec_mark_gpe_for_wake(); + return 0; + +err_remove_notify: + acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); + + return err; +} + +static int intel_hid_remove(struct platform_device *device) +{ + acpi_handle handle = ACPI_HANDLE(&device->dev); + + device_init_wakeup(&device->dev, false); + acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); + intel_hid_set_enable(&device->dev, false); + intel_button_array_enable(&device->dev, false); + + /* + * Even if we failed to shut off the event stream, we can still + * safely detach from the device. + */ + return 0; +} + +static struct platform_driver intel_hid_pl_driver = { + .driver = { + .name = "intel-hid", + .acpi_match_table = intel_hid_ids, + .pm = &intel_hid_pl_pm_ops, + }, + .probe = intel_hid_probe, + .remove = intel_hid_remove, +}; + +/* + * Unfortunately, some laptops provide a _HID="INT33D5" device with + * _CID="PNP0C02". This causes the pnpacpi scan driver to claim the + * ACPI node, so no platform device will be created. The pnpacpi + * driver rejects this device in subsequent processing, so no physical + * node is created at all. + * + * As a workaround until the ACPI core figures out how to handle + * this corner case, manually ask the ACPI platform device code to + * claim the ACPI node. + */ +static acpi_status __init +check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + const struct acpi_device_id *ids = context; + struct acpi_device *dev; + + if (acpi_bus_get_device(handle, &dev) != 0) + return AE_OK; + + if (acpi_match_device_ids(dev, ids) == 0) + if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) + dev_info(&dev->dev, + "intel-hid: created platform device\n"); + + return AE_OK; +} + +static int __init intel_hid_init(void) +{ + acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, check_acpi_dev, NULL, + (void *)intel_hid_ids, NULL); + + return platform_driver_register(&intel_hid_pl_driver); +} +module_init(intel_hid_init); + +static void __exit intel_hid_exit(void) +{ + platform_driver_unregister(&intel_hid_pl_driver); +} +module_exit(intel_hid_exit); -- cgit v1.2.3-59-g8ed1b From 3afeacfd39eadc1f5f19a992eff65f554bd3e717 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:56 +0300 Subject: platform/x86: intel-vbtn: Move to intel sub-directory Move Intel vButton driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-19-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 13 -- drivers/platform/x86/Makefile | 2 - drivers/platform/x86/intel-vbtn.c | 414 ------------------------------------ drivers/platform/x86/intel/Kconfig | 13 ++ drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/vbtn.c | 414 ++++++++++++++++++++++++++++++++++++ 7 files changed, 430 insertions(+), 430 deletions(-) delete mode 100644 drivers/platform/x86/intel-vbtn.c create mode 100644 drivers/platform/x86/intel/vbtn.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 5339b210d6a5..4662d4d4db5d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9571,7 +9571,7 @@ INTEL VIRTUAL BUTTON DRIVER M: AceLan Kao L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel-vbtn.c +F: drivers/platform/x86/intel/vbtn.c INTEL WIRELESS 3945ABG/BG, 4965AGN (iwlegacy) M: Stanislaw Gruszka diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 7f54db30cf5a..f1349a1e17ae 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,19 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/Kconfig" -config INTEL_VBTN - tristate "INTEL VIRTUAL BUTTON" - depends on ACPI - depends on INPUT - depends on I2C - select INPUT_SPARSEKMAP - help - This driver provides support for the Intel Virtual Button interface. - Some laptops require this driver for power button support. - - To compile this driver as a module, choose M here: the module will - be called intel_vbtn. - config MSI_LAPTOP tristate "MSI Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 836fbab61f47..e6e888af9df7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,8 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o - # MSI obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c deleted file mode 100644 index 309166431063..000000000000 --- a/drivers/platform/x86/intel-vbtn.c +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Intel Virtual Button driver for Windows 8.1+ - * - * Copyright (C) 2016 AceLan Kao - * Copyright (C) 2016 Alex Hung - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "dual_accel_detect.h" - -/* Returned when NOT in tablet mode on some HP Stream x360 11 models */ -#define VGBS_TABLET_MODE_FLAG_ALT 0x10 -/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ -#define VGBS_TABLET_MODE_FLAG 0x40 -#define VGBS_DOCK_MODE_FLAG 0x80 - -#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT) - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("AceLan Kao"); - -static const struct acpi_device_id intel_vbtn_ids[] = { - {"INT33D6", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids); - -/* In theory, these are HID usages. */ -static const struct key_entry intel_vbtn_keymap[] = { - { KE_KEY, 0xC0, { KEY_POWER } }, /* power key press */ - { KE_IGNORE, 0xC1, { KEY_POWER } }, /* power key release */ - { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* 'Windows' key press */ - { KE_KEY, 0xC3, { KEY_LEFTMETA } }, /* 'Windows' key release */ - { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */ - { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */ - { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */ - { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ - { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */ - { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */ - { KE_END } -}; - -static const struct key_entry intel_vbtn_switchmap[] = { - /* - * SW_DOCK should only be reported for docking stations, but DSDTs using the - * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set - * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0). - * This causes userspace to think the laptop is docked to a port-replicator - * and to disable suspend-on-lid-close, which is undesirable. - * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting. - */ - { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */ - { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */ - { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ - { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ - { KE_END } -}; - -struct intel_vbtn_priv { - struct input_dev *buttons_dev; - struct input_dev *switches_dev; - bool dual_accel; - bool has_buttons; - bool has_switches; - bool wakeup_mode; -}; - -static void detect_tablet_mode(struct platform_device *device) -{ - struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); - acpi_handle handle = ACPI_HANDLE(&device->dev); - unsigned long long vgbs; - acpi_status status; - int m; - - status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs); - if (ACPI_FAILURE(status)) - return; - - m = !(vgbs & VGBS_TABLET_MODE_FLAGS); - input_report_switch(priv->switches_dev, SW_TABLET_MODE, m); - m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0; - input_report_switch(priv->switches_dev, SW_DOCK, m); -} - -/* - * Note this unconditionally creates the 2 input_dev-s and sets up - * the sparse-keymaps. Only the registration is conditional on - * have_buttons / have_switches. This is done so that the notify - * handler can always call sparse_keymap_entry_from_scancode() - * on the input_dev-s do determine the event type. - */ -static int intel_vbtn_input_setup(struct platform_device *device) -{ - struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); - int ret; - - priv->buttons_dev = devm_input_allocate_device(&device->dev); - if (!priv->buttons_dev) - return -ENOMEM; - - ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL); - if (ret) - return ret; - - priv->buttons_dev->dev.parent = &device->dev; - priv->buttons_dev->name = "Intel Virtual Buttons"; - priv->buttons_dev->id.bustype = BUS_HOST; - - if (priv->has_buttons) { - ret = input_register_device(priv->buttons_dev); - if (ret) - return ret; - } - - priv->switches_dev = devm_input_allocate_device(&device->dev); - if (!priv->switches_dev) - return -ENOMEM; - - ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL); - if (ret) - return ret; - - priv->switches_dev->dev.parent = &device->dev; - priv->switches_dev->name = "Intel Virtual Switches"; - priv->switches_dev->id.bustype = BUS_HOST; - - if (priv->has_switches) { - detect_tablet_mode(device); - - ret = input_register_device(priv->switches_dev); - if (ret) - return ret; - } - - return 0; -} - -static void notify_handler(acpi_handle handle, u32 event, void *context) -{ - struct platform_device *device = context; - struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); - unsigned int val = !(event & 1); /* Even=press, Odd=release */ - const struct key_entry *ke, *ke_rel; - struct input_dev *input_dev; - bool autorelease; - int ret; - - if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) { - if (!priv->has_buttons) { - dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n"); - return; - } - input_dev = priv->buttons_dev; - } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) { - if (!priv->has_switches) { - /* See dual_accel_detect.h for more info */ - if (priv->dual_accel) - return; - - dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n"); - ret = input_register_device(priv->switches_dev); - if (ret) - return; - - priv->has_switches = true; - } - input_dev = priv->switches_dev; - } else { - dev_dbg(&device->dev, "unknown event index 0x%x\n", event); - return; - } - - if (priv->wakeup_mode) { - pm_wakeup_hard_event(&device->dev); - - /* - * Skip reporting an evdev event for button wake events, - * mirroring how the drivers/acpi/button.c code skips this too. - */ - if (ke->type == KE_KEY) - return; - } - - /* - * Even press events are autorelease if there is no corresponding odd - * release event, or if the odd event is KE_IGNORE. - */ - ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1); - autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE); - - sparse_keymap_report_event(input_dev, event, val, autorelease); -} - -/* - * There are several laptops (non 2-in-1) models out there which support VGBS, - * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in - * turn causes userspace (libinput) to suppress events from the builtin - * keyboard and touchpad, making the laptop essentially unusable. - * - * Since the problem of wrongly reporting SW_TABLET_MODE=1 in combination - * with libinput, leads to a non-usable system. Where as OTOH many people will - * not even notice when SW_TABLET_MODE is not being reported, a DMI based allow - * list is used here. This list mainly matches on the chassis-type of 2-in-1s. - * - * There are also some 2-in-1s which use the intel-vbtn ACPI interface to report - * SW_TABLET_MODE with a chassis-type of 8 ("Portable") or 10 ("Notebook"), - * these are matched on a per model basis, since many normal laptops with a - * possible broken VGBS ACPI-method also use these chassis-types. - */ -static const struct dmi_system_id dmi_switches_allow_list[] = { - { - .matches = { - DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */), - }, - }, - { - .matches = { - DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Acer"), - DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7352"), - }, - }, - {} /* Array terminator */ -}; - -static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel) -{ - unsigned long long vgbs; - acpi_status status; - - /* See dual_accel_detect.h for more info */ - if (dual_accel) - return false; - - if (!dmi_check_system(dmi_switches_allow_list)) - return false; - - status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs); - return ACPI_SUCCESS(status); -} - -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_status status; - int err; - - dual_accel = dual_accel_detect(); - has_buttons = acpi_has_method(handle, "VBDL"); - has_switches = intel_vbtn_has_switches(handle, dual_accel); - - if (!has_buttons && !has_switches) { - dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n"); - return -ENODEV; - } - - priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - dev_set_drvdata(&device->dev, priv); - - priv->dual_accel = dual_accel; - priv->has_buttons = has_buttons; - priv->has_switches = has_switches; - - err = intel_vbtn_input_setup(device); - if (err) { - pr_err("Failed to setup Intel Virtual Button\n"); - return err; - } - - status = acpi_install_notify_handler(handle, - ACPI_DEVICE_NOTIFY, - notify_handler, - device); - if (ACPI_FAILURE(status)) - return -EBUSY; - - if (has_buttons) { - status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); - if (ACPI_FAILURE(status)) - dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status); - } - - device_init_wakeup(&device->dev, true); - /* - * In order for system wakeup to work, the EC GPE has to be marked as - * a wakeup one, so do that here (this setting will persist, but it has - * no effect until the wakeup mask is set for the EC GPE). - */ - acpi_ec_mark_gpe_for_wake(); - return 0; -} - -static int intel_vbtn_remove(struct platform_device *device) -{ - acpi_handle handle = ACPI_HANDLE(&device->dev); - - device_init_wakeup(&device->dev, false); - acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); - - /* - * Even if we failed to shut off the event stream, we can still - * safely detach from the device. - */ - return 0; -} - -static int intel_vbtn_pm_prepare(struct device *dev) -{ - if (device_may_wakeup(dev)) { - struct intel_vbtn_priv *priv = dev_get_drvdata(dev); - - priv->wakeup_mode = true; - } - return 0; -} - -static void intel_vbtn_pm_complete(struct device *dev) -{ - struct intel_vbtn_priv *priv = dev_get_drvdata(dev); - - priv->wakeup_mode = false; -} - -static int intel_vbtn_pm_resume(struct device *dev) -{ - intel_vbtn_pm_complete(dev); - return 0; -} - -static const struct dev_pm_ops intel_vbtn_pm_ops = { - .prepare = intel_vbtn_pm_prepare, - .complete = intel_vbtn_pm_complete, - .resume = intel_vbtn_pm_resume, - .restore = intel_vbtn_pm_resume, - .thaw = intel_vbtn_pm_resume, -}; - -static struct platform_driver intel_vbtn_pl_driver = { - .driver = { - .name = "intel-vbtn", - .acpi_match_table = intel_vbtn_ids, - .pm = &intel_vbtn_pm_ops, - }, - .probe = intel_vbtn_probe, - .remove = intel_vbtn_remove, -}; - -static acpi_status __init -check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) -{ - const struct acpi_device_id *ids = context; - struct acpi_device *dev; - - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) - if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) - dev_info(&dev->dev, - "intel-vbtn: created platform device\n"); - - return AE_OK; -} - -static int __init intel_vbtn_init(void) -{ - acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, check_acpi_dev, NULL, - (void *)intel_vbtn_ids, NULL); - - return platform_driver_register(&intel_vbtn_pl_driver); -} -module_init(intel_vbtn_init); - -static void __exit intel_vbtn_exit(void) -{ - platform_driver_unregister(&intel_vbtn_pl_driver); -} -module_exit(intel_vbtn_exit); diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 3c7fd0bd39f1..a5d71d669b82 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -38,6 +38,19 @@ config INTEL_HID_EVENT To compile this driver as a module, choose M here: the module will be called intel_hid. +config INTEL_VBTN + tristate "Intel Virtual Button" + depends on ACPI + depends on INPUT + depends on I2C + select INPUT_SPARSEKMAP + help + This driver provides support for the Intel Virtual Button interface. + Some laptops require this driver for power button support. + + To compile this driver as a module, choose M here: the module will + be called intel_vbtn. + config INTEL_INT0002_VGPIO tristate "Intel ACPI INT0002 Virtual GPIO driver" depends on GPIOLIB && ACPI && PM_SLEEP diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index f27d91464cc9..67b16119e712 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -16,6 +16,8 @@ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ # Intel input drivers intel-hid-y := hid.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o +intel-vbtn-y := vbtn.o +obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o # Intel miscellaneous drivers intel_int0002_vgpio-y := int0002_vgpio.o diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c new file mode 100644 index 000000000000..15f013af9e62 --- /dev/null +++ b/drivers/platform/x86/intel/vbtn.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Intel Virtual Button driver for Windows 8.1+ + * + * Copyright (C) 2016 AceLan Kao + * Copyright (C) 2016 Alex Hung + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../dual_accel_detect.h" + +/* Returned when NOT in tablet mode on some HP Stream x360 11 models */ +#define VGBS_TABLET_MODE_FLAG_ALT 0x10 +/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ +#define VGBS_TABLET_MODE_FLAG 0x40 +#define VGBS_DOCK_MODE_FLAG 0x80 + +#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("AceLan Kao"); + +static const struct acpi_device_id intel_vbtn_ids[] = { + {"INT33D6", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids); + +/* In theory, these are HID usages. */ +static const struct key_entry intel_vbtn_keymap[] = { + { KE_KEY, 0xC0, { KEY_POWER } }, /* power key press */ + { KE_IGNORE, 0xC1, { KEY_POWER } }, /* power key release */ + { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* 'Windows' key press */ + { KE_KEY, 0xC3, { KEY_LEFTMETA } }, /* 'Windows' key release */ + { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */ + { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */ + { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */ + { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ + { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */ + { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */ + { KE_END } +}; + +static const struct key_entry intel_vbtn_switchmap[] = { + /* + * SW_DOCK should only be reported for docking stations, but DSDTs using the + * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set + * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0). + * This causes userspace to think the laptop is docked to a port-replicator + * and to disable suspend-on-lid-close, which is undesirable. + * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting. + */ + { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */ + { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */ + { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ + { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ + { KE_END } +}; + +struct intel_vbtn_priv { + struct input_dev *buttons_dev; + struct input_dev *switches_dev; + bool dual_accel; + bool has_buttons; + bool has_switches; + bool wakeup_mode; +}; + +static void detect_tablet_mode(struct platform_device *device) +{ + struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long vgbs; + acpi_status status; + int m; + + status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs); + if (ACPI_FAILURE(status)) + return; + + m = !(vgbs & VGBS_TABLET_MODE_FLAGS); + input_report_switch(priv->switches_dev, SW_TABLET_MODE, m); + m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0; + input_report_switch(priv->switches_dev, SW_DOCK, m); +} + +/* + * Note this unconditionally creates the 2 input_dev-s and sets up + * the sparse-keymaps. Only the registration is conditional on + * have_buttons / have_switches. This is done so that the notify + * handler can always call sparse_keymap_entry_from_scancode() + * on the input_dev-s do determine the event type. + */ +static int intel_vbtn_input_setup(struct platform_device *device) +{ + struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); + int ret; + + priv->buttons_dev = devm_input_allocate_device(&device->dev); + if (!priv->buttons_dev) + return -ENOMEM; + + ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL); + if (ret) + return ret; + + priv->buttons_dev->dev.parent = &device->dev; + priv->buttons_dev->name = "Intel Virtual Buttons"; + priv->buttons_dev->id.bustype = BUS_HOST; + + if (priv->has_buttons) { + ret = input_register_device(priv->buttons_dev); + if (ret) + return ret; + } + + priv->switches_dev = devm_input_allocate_device(&device->dev); + if (!priv->switches_dev) + return -ENOMEM; + + ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL); + if (ret) + return ret; + + priv->switches_dev->dev.parent = &device->dev; + priv->switches_dev->name = "Intel Virtual Switches"; + priv->switches_dev->id.bustype = BUS_HOST; + + if (priv->has_switches) { + detect_tablet_mode(device); + + ret = input_register_device(priv->switches_dev); + if (ret) + return ret; + } + + return 0; +} + +static void notify_handler(acpi_handle handle, u32 event, void *context) +{ + struct platform_device *device = context; + struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); + unsigned int val = !(event & 1); /* Even=press, Odd=release */ + const struct key_entry *ke, *ke_rel; + struct input_dev *input_dev; + bool autorelease; + int ret; + + if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) { + if (!priv->has_buttons) { + dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n"); + return; + } + input_dev = priv->buttons_dev; + } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) { + if (!priv->has_switches) { + /* See dual_accel_detect.h for more info */ + if (priv->dual_accel) + return; + + dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n"); + ret = input_register_device(priv->switches_dev); + if (ret) + return; + + priv->has_switches = true; + } + input_dev = priv->switches_dev; + } else { + dev_dbg(&device->dev, "unknown event index 0x%x\n", event); + return; + } + + if (priv->wakeup_mode) { + pm_wakeup_hard_event(&device->dev); + + /* + * Skip reporting an evdev event for button wake events, + * mirroring how the drivers/acpi/button.c code skips this too. + */ + if (ke->type == KE_KEY) + return; + } + + /* + * Even press events are autorelease if there is no corresponding odd + * release event, or if the odd event is KE_IGNORE. + */ + ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1); + autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE); + + sparse_keymap_report_event(input_dev, event, val, autorelease); +} + +/* + * There are several laptops (non 2-in-1) models out there which support VGBS, + * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in + * turn causes userspace (libinput) to suppress events from the builtin + * keyboard and touchpad, making the laptop essentially unusable. + * + * Since the problem of wrongly reporting SW_TABLET_MODE=1 in combination + * with libinput, leads to a non-usable system. Where as OTOH many people will + * not even notice when SW_TABLET_MODE is not being reported, a DMI based allow + * list is used here. This list mainly matches on the chassis-type of 2-in-1s. + * + * There are also some 2-in-1s which use the intel-vbtn ACPI interface to report + * SW_TABLET_MODE with a chassis-type of 8 ("Portable") or 10 ("Notebook"), + * these are matched on a per model basis, since many normal laptops with a + * possible broken VGBS ACPI-method also use these chassis-types. + */ +static const struct dmi_system_id dmi_switches_allow_list[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */), + }, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7352"), + }, + }, + {} /* Array terminator */ +}; + +static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel) +{ + unsigned long long vgbs; + acpi_status status; + + /* See dual_accel_detect.h for more info */ + if (dual_accel) + return false; + + if (!dmi_check_system(dmi_switches_allow_list)) + return false; + + status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs); + return ACPI_SUCCESS(status); +} + +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_status status; + int err; + + dual_accel = dual_accel_detect(); + has_buttons = acpi_has_method(handle, "VBDL"); + has_switches = intel_vbtn_has_switches(handle, dual_accel); + + if (!has_buttons && !has_switches) { + dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(&device->dev, priv); + + priv->dual_accel = dual_accel; + priv->has_buttons = has_buttons; + priv->has_switches = has_switches; + + err = intel_vbtn_input_setup(device); + if (err) { + pr_err("Failed to setup Intel Virtual Button\n"); + return err; + } + + status = acpi_install_notify_handler(handle, + ACPI_DEVICE_NOTIFY, + notify_handler, + device); + if (ACPI_FAILURE(status)) + return -EBUSY; + + if (has_buttons) { + status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); + if (ACPI_FAILURE(status)) + dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status); + } + + device_init_wakeup(&device->dev, true); + /* + * In order for system wakeup to work, the EC GPE has to be marked as + * a wakeup one, so do that here (this setting will persist, but it has + * no effect until the wakeup mask is set for the EC GPE). + */ + acpi_ec_mark_gpe_for_wake(); + return 0; +} + +static int intel_vbtn_remove(struct platform_device *device) +{ + acpi_handle handle = ACPI_HANDLE(&device->dev); + + device_init_wakeup(&device->dev, false); + acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); + + /* + * Even if we failed to shut off the event stream, we can still + * safely detach from the device. + */ + return 0; +} + +static int intel_vbtn_pm_prepare(struct device *dev) +{ + if (device_may_wakeup(dev)) { + struct intel_vbtn_priv *priv = dev_get_drvdata(dev); + + priv->wakeup_mode = true; + } + return 0; +} + +static void intel_vbtn_pm_complete(struct device *dev) +{ + struct intel_vbtn_priv *priv = dev_get_drvdata(dev); + + priv->wakeup_mode = false; +} + +static int intel_vbtn_pm_resume(struct device *dev) +{ + intel_vbtn_pm_complete(dev); + return 0; +} + +static const struct dev_pm_ops intel_vbtn_pm_ops = { + .prepare = intel_vbtn_pm_prepare, + .complete = intel_vbtn_pm_complete, + .resume = intel_vbtn_pm_resume, + .restore = intel_vbtn_pm_resume, + .thaw = intel_vbtn_pm_resume, +}; + +static struct platform_driver intel_vbtn_pl_driver = { + .driver = { + .name = "intel-vbtn", + .acpi_match_table = intel_vbtn_ids, + .pm = &intel_vbtn_pm_ops, + }, + .probe = intel_vbtn_probe, + .remove = intel_vbtn_remove, +}; + +static acpi_status __init +check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + const struct acpi_device_id *ids = context; + struct acpi_device *dev; + + if (acpi_bus_get_device(handle, &dev) != 0) + return AE_OK; + + if (acpi_match_device_ids(dev, ids) == 0) + if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) + dev_info(&dev->dev, + "intel-vbtn: created platform device\n"); + + return AE_OK; +} + +static int __init intel_vbtn_init(void) +{ + acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, check_acpi_dev, NULL, + (void *)intel_vbtn_ids, NULL); + + return platform_driver_register(&intel_vbtn_pl_driver); +} +module_init(intel_vbtn_init); + +static void __exit intel_vbtn_exit(void) +{ + platform_driver_unregister(&intel_vbtn_pl_driver); +} +module_exit(intel_vbtn_exit); -- cgit v1.2.3-59-g8ed1b From bd5b4fb47dde86b5e04686463dc2420e5ed6932a Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:57 +0300 Subject: platform/x86: intel-wmi-sbl-fw-update: Move to intel sub-directory Move Intel WMI Slim Bootloader FW update driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-20-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 10 -- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-wmi-sbl-fw-update.c | 144 ------------------------- drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/wmi/Kconfig | 18 ++++ drivers/platform/x86/intel/wmi/Makefile | 7 ++ drivers/platform/x86/intel/wmi/sbl-fw-update.c | 144 +++++++++++++++++++++++++ 9 files changed, 172 insertions(+), 156 deletions(-) delete mode 100644 drivers/platform/x86/intel-wmi-sbl-fw-update.c create mode 100644 drivers/platform/x86/intel/wmi/Kconfig create mode 100644 drivers/platform/x86/intel/wmi/Makefile create mode 100644 drivers/platform/x86/intel/wmi/sbl-fw-update.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 4662d4d4db5d..3ca5102ed811 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9592,7 +9592,7 @@ M: Jithu Joseph R: Maurice Ma S: Maintained W: https://slimbootloader.github.io/security/firmware-update.html -F: drivers/platform/x86/intel-wmi-sbl-fw-update.c +F: drivers/platform/x86/intel/wmi/sbl-fw-update.c INTEL WMI THUNDERBOLT FORCE POWER DRIVER L: Dell.Client.Kernel@dell.com diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index f1349a1e17ae..d23053b6e29f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -77,16 +77,6 @@ config UV_SYSFS To compile this driver as a module, choose M here: the module will be called uv_sysfs. -config INTEL_WMI_SBL_FW_UPDATE - tristate "Intel WMI Slim Bootloader firmware update signaling driver" - depends on ACPI_WMI - help - Say Y here if you want to be able to use the WMI interface to signal - Slim Bootloader to trigger update on next reboot. - - To compile this driver as a module, choose M here: the module will - be called intel-wmi-sbl-fw-update. - config INTEL_WMI_THUNDERBOLT tristate "Intel WMI thunderbolt force power driver" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e6e888af9df7..29ab09e0f36b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o -obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o diff --git a/drivers/platform/x86/intel-wmi-sbl-fw-update.c b/drivers/platform/x86/intel-wmi-sbl-fw-update.c deleted file mode 100644 index 3c86e0108a24..000000000000 --- a/drivers/platform/x86/intel-wmi-sbl-fw-update.c +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Slim Bootloader(SBL) firmware update signaling driver - * - * Slim Bootloader is a small, open-source, non UEFI compliant, boot firmware - * optimized for running on certain Intel platforms. - * - * SBL exposes an ACPI-WMI device via /sys/bus/wmi/devices/. - * This driver further adds "firmware_update_request" device attribute. - * This attribute normally has a value of 0 and userspace can signal SBL - * to update firmware, on next reboot, by writing a value of 1. - * - * More details of SBL firmware update process is available at: - * https://slimbootloader.github.io/security/firmware-update.html - */ - -#include -#include -#include -#include -#include -#include - -#define INTEL_WMI_SBL_GUID "44FADEB1-B204-40F2-8581-394BBDC1B651" - -static int get_fwu_request(struct device *dev, u32 *out) -{ - struct acpi_buffer result = {ACPI_ALLOCATE_BUFFER, NULL}; - union acpi_object *obj; - acpi_status status; - - status = wmi_query_block(INTEL_WMI_SBL_GUID, 0, &result); - if (ACPI_FAILURE(status)) { - dev_err(dev, "wmi_query_block failed\n"); - return -ENODEV; - } - - obj = (union acpi_object *)result.pointer; - if (!obj || obj->type != ACPI_TYPE_INTEGER) { - dev_warn(dev, "wmi_query_block returned invalid value\n"); - kfree(obj); - return -EINVAL; - } - - *out = obj->integer.value; - kfree(obj); - - return 0; -} - -static int set_fwu_request(struct device *dev, u32 in) -{ - struct acpi_buffer input; - acpi_status status; - u32 value; - - value = in; - input.length = sizeof(u32); - input.pointer = &value; - - status = wmi_set_block(INTEL_WMI_SBL_GUID, 0, &input); - if (ACPI_FAILURE(status)) { - dev_err(dev, "wmi_set_block failed\n"); - return -ENODEV; - } - - return 0; -} - -static ssize_t firmware_update_request_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - u32 val; - int ret; - - ret = get_fwu_request(dev, &val); - if (ret) - return ret; - - return sprintf(buf, "%d\n", val); -} - -static ssize_t firmware_update_request_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned int val; - int ret; - - ret = kstrtouint(buf, 0, &val); - if (ret) - return ret; - - /* May later be extended to support values other than 0 and 1 */ - if (val > 1) - return -ERANGE; - - ret = set_fwu_request(dev, val); - if (ret) - return ret; - - return count; -} -static DEVICE_ATTR_RW(firmware_update_request); - -static struct attribute *firmware_update_attrs[] = { - &dev_attr_firmware_update_request.attr, - NULL -}; -ATTRIBUTE_GROUPS(firmware_update); - -static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev, - const void *context) -{ - dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n"); - return 0; -} - -static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev) -{ - dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n"); -} - -static const struct wmi_device_id intel_wmi_sbl_id_table[] = { - { .guid_string = INTEL_WMI_SBL_GUID }, - {} -}; -MODULE_DEVICE_TABLE(wmi, intel_wmi_sbl_id_table); - -static struct wmi_driver intel_wmi_sbl_fw_update_driver = { - .driver = { - .name = "intel-wmi-sbl-fw-update", - .dev_groups = firmware_update_groups, - }, - .probe = intel_wmi_sbl_fw_update_probe, - .remove = intel_wmi_sbl_fw_update_remove, - .id_table = intel_wmi_sbl_id_table, -}; -module_wmi_driver(intel_wmi_sbl_fw_update_driver); - -MODULE_AUTHOR("Jithu Joseph "); -MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index a5d71d669b82..0b21468e1bd0 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -24,6 +24,7 @@ source "drivers/platform/x86/intel/pmc/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" source "drivers/platform/x86/intel/speed_select_if/Kconfig" source "drivers/platform/x86/intel/telemetry/Kconfig" +source "drivers/platform/x86/intel/wmi/Kconfig" config INTEL_HID_EVENT tristate "Intel HID Event" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 67b16119e712..8b3a3f7bab49 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_INTEL_PMC_CORE) += pmc/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ +obj-$(CONFIG_INTEL_WMI) += wmi/ # Intel input drivers intel-hid-y := hid.o diff --git a/drivers/platform/x86/intel/wmi/Kconfig b/drivers/platform/x86/intel/wmi/Kconfig new file mode 100644 index 000000000000..c5753b1e8f43 --- /dev/null +++ b/drivers/platform/x86/intel/wmi/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Intel x86 Platform Specific Drivers +# + +config INTEL_WMI + bool + +config INTEL_WMI_SBL_FW_UPDATE + tristate "Intel WMI Slim Bootloader firmware update signaling driver" + depends on ACPI_WMI + select INTEL_WMI + help + Say Y here if you want to be able to use the WMI interface to signal + Slim Bootloader to trigger update on next reboot. + + To compile this driver as a module, choose M here: the module will + be called intel-wmi-sbl-fw-update. diff --git a/drivers/platform/x86/intel/wmi/Makefile b/drivers/platform/x86/intel/wmi/Makefile new file mode 100644 index 000000000000..bf1f118b6839 --- /dev/null +++ b/drivers/platform/x86/intel/wmi/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Intel x86 Platform Specific Drivers +# + +intel-wmi-sbl-fw-update-y := sbl-fw-update.o +obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c new file mode 100644 index 000000000000..3c86e0108a24 --- /dev/null +++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Slim Bootloader(SBL) firmware update signaling driver + * + * Slim Bootloader is a small, open-source, non UEFI compliant, boot firmware + * optimized for running on certain Intel platforms. + * + * SBL exposes an ACPI-WMI device via /sys/bus/wmi/devices/. + * This driver further adds "firmware_update_request" device attribute. + * This attribute normally has a value of 0 and userspace can signal SBL + * to update firmware, on next reboot, by writing a value of 1. + * + * More details of SBL firmware update process is available at: + * https://slimbootloader.github.io/security/firmware-update.html + */ + +#include +#include +#include +#include +#include +#include + +#define INTEL_WMI_SBL_GUID "44FADEB1-B204-40F2-8581-394BBDC1B651" + +static int get_fwu_request(struct device *dev, u32 *out) +{ + struct acpi_buffer result = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obj; + acpi_status status; + + status = wmi_query_block(INTEL_WMI_SBL_GUID, 0, &result); + if (ACPI_FAILURE(status)) { + dev_err(dev, "wmi_query_block failed\n"); + return -ENODEV; + } + + obj = (union acpi_object *)result.pointer; + if (!obj || obj->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "wmi_query_block returned invalid value\n"); + kfree(obj); + return -EINVAL; + } + + *out = obj->integer.value; + kfree(obj); + + return 0; +} + +static int set_fwu_request(struct device *dev, u32 in) +{ + struct acpi_buffer input; + acpi_status status; + u32 value; + + value = in; + input.length = sizeof(u32); + input.pointer = &value; + + status = wmi_set_block(INTEL_WMI_SBL_GUID, 0, &input); + if (ACPI_FAILURE(status)) { + dev_err(dev, "wmi_set_block failed\n"); + return -ENODEV; + } + + return 0; +} + +static ssize_t firmware_update_request_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u32 val; + int ret; + + ret = get_fwu_request(dev, &val); + if (ret) + return ret; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t firmware_update_request_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + /* May later be extended to support values other than 0 and 1 */ + if (val > 1) + return -ERANGE; + + ret = set_fwu_request(dev, val); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(firmware_update_request); + +static struct attribute *firmware_update_attrs[] = { + &dev_attr_firmware_update_request.attr, + NULL +}; +ATTRIBUTE_GROUPS(firmware_update); + +static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev, + const void *context) +{ + dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n"); + return 0; +} + +static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev) +{ + dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n"); +} + +static const struct wmi_device_id intel_wmi_sbl_id_table[] = { + { .guid_string = INTEL_WMI_SBL_GUID }, + {} +}; +MODULE_DEVICE_TABLE(wmi, intel_wmi_sbl_id_table); + +static struct wmi_driver intel_wmi_sbl_fw_update_driver = { + .driver = { + .name = "intel-wmi-sbl-fw-update", + .dev_groups = firmware_update_groups, + }, + .probe = intel_wmi_sbl_fw_update_probe, + .remove = intel_wmi_sbl_fw_update_remove, + .id_table = intel_wmi_sbl_id_table, +}; +module_wmi_driver(intel_wmi_sbl_fw_update_driver); + +MODULE_AUTHOR("Jithu Joseph "); +MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 95c3e4b4282a4033ff3b0fe64bd4d2a2f3f0d31d Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:58 +0300 Subject: platform/x86: intel-wmi-thunderbolt: Move to intel sub-directory Move Intel WMI Thunderbolt driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-21-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 12 ---- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-wmi-thunderbolt.c | 94 ---------------------------- drivers/platform/x86/intel/wmi/Kconfig | 13 ++++ drivers/platform/x86/intel/wmi/Makefile | 2 + drivers/platform/x86/intel/wmi/thunderbolt.c | 94 ++++++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 108 deletions(-) delete mode 100644 drivers/platform/x86/intel-wmi-thunderbolt.c create mode 100644 drivers/platform/x86/intel/wmi/thunderbolt.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3ca5102ed811..60ee48c71c83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9597,7 +9597,7 @@ F: drivers/platform/x86/intel/wmi/sbl-fw-update.c INTEL WMI THUNDERBOLT FORCE POWER DRIVER L: Dell.Client.Kernel@dell.com S: Maintained -F: drivers/platform/x86/intel-wmi-thunderbolt.c +F: drivers/platform/x86/intel/wmi/thunderbolt.c INTEL WWAN IOSM DRIVER M: M Chetan Kumar diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index d23053b6e29f..e21ea3d23e6f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -77,18 +77,6 @@ config UV_SYSFS To compile this driver as a module, choose M here: the module will be called uv_sysfs. -config INTEL_WMI_THUNDERBOLT - tristate "Intel WMI thunderbolt force power driver" - depends on ACPI_WMI - help - Say Y here if you want to be able to use the WMI interface on select - systems to force the power control of Intel Thunderbolt controllers. - This is useful for updating the firmware when devices are not plugged - into the controller. - - To compile this driver as a module, choose M here: the module will - be called intel-wmi-thunderbolt. - config MXM_WMI tristate "WMI support for MXM Laptop Graphics" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 29ab09e0f36b..69690e26bb6d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o -obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c deleted file mode 100644 index 4ae87060d18b..000000000000 --- a/drivers/platform/x86/intel-wmi-thunderbolt.c +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * WMI Thunderbolt driver - * - * Copyright (C) 2017 Dell Inc. All Rights Reserved. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341" - -static ssize_t force_power_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct acpi_buffer input; - acpi_status status; - u8 mode; - - input.length = sizeof(u8); - input.pointer = &mode; - mode = hex_to_bin(buf[0]); - dev_dbg(dev, "force_power: storing %#x\n", mode); - if (mode == 0 || mode == 1) { - status = wmi_evaluate_method(INTEL_WMI_THUNDERBOLT_GUID, 0, 1, - &input, NULL); - if (ACPI_FAILURE(status)) { - dev_dbg(dev, "force_power: failed to evaluate ACPI method\n"); - return -ENODEV; - } - } else { - dev_dbg(dev, "force_power: unsupported mode\n"); - return -EINVAL; - } - return count; -} - -static DEVICE_ATTR_WO(force_power); - -static struct attribute *tbt_attrs[] = { - &dev_attr_force_power.attr, - NULL -}; - -static const struct attribute_group tbt_attribute_group = { - .attrs = tbt_attrs, -}; - -static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev, - const void *context) -{ - int ret; - - ret = sysfs_create_group(&wdev->dev.kobj, &tbt_attribute_group); - kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE); - return ret; -} - -static void intel_wmi_thunderbolt_remove(struct wmi_device *wdev) -{ - sysfs_remove_group(&wdev->dev.kobj, &tbt_attribute_group); - kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE); -} - -static const struct wmi_device_id intel_wmi_thunderbolt_id_table[] = { - { .guid_string = INTEL_WMI_THUNDERBOLT_GUID }, - { }, -}; - -static struct wmi_driver intel_wmi_thunderbolt_driver = { - .driver = { - .name = "intel-wmi-thunderbolt", - }, - .probe = intel_wmi_thunderbolt_probe, - .remove = intel_wmi_thunderbolt_remove, - .id_table = intel_wmi_thunderbolt_id_table, -}; - -module_wmi_driver(intel_wmi_thunderbolt_driver); - -MODULE_DEVICE_TABLE(wmi, intel_wmi_thunderbolt_id_table); -MODULE_AUTHOR("Mario Limonciello "); -MODULE_DESCRIPTION("Intel WMI Thunderbolt force power driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/wmi/Kconfig b/drivers/platform/x86/intel/wmi/Kconfig index c5753b1e8f43..8e159f712179 100644 --- a/drivers/platform/x86/intel/wmi/Kconfig +++ b/drivers/platform/x86/intel/wmi/Kconfig @@ -16,3 +16,16 @@ config INTEL_WMI_SBL_FW_UPDATE To compile this driver as a module, choose M here: the module will be called intel-wmi-sbl-fw-update. + +config INTEL_WMI_THUNDERBOLT + tristate "Intel WMI thunderbolt force power driver" + depends on ACPI_WMI + select INTEL_WMI + help + Say Y here if you want to be able to use the WMI interface on select + systems to force the power control of Intel Thunderbolt controllers. + This is useful for updating the firmware when devices are not plugged + into the controller. + + To compile this driver as a module, choose M here: the module will + be called intel-wmi-thunderbolt. diff --git a/drivers/platform/x86/intel/wmi/Makefile b/drivers/platform/x86/intel/wmi/Makefile index bf1f118b6839..c2d56d25dea0 100644 --- a/drivers/platform/x86/intel/wmi/Makefile +++ b/drivers/platform/x86/intel/wmi/Makefile @@ -5,3 +5,5 @@ intel-wmi-sbl-fw-update-y := sbl-fw-update.o obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o +intel-wmi-thunderbolt-y := thunderbolt.o +obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o diff --git a/drivers/platform/x86/intel/wmi/thunderbolt.c b/drivers/platform/x86/intel/wmi/thunderbolt.c new file mode 100644 index 000000000000..4ae87060d18b --- /dev/null +++ b/drivers/platform/x86/intel/wmi/thunderbolt.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * WMI Thunderbolt driver + * + * Copyright (C) 2017 Dell Inc. All Rights Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341" + +static ssize_t force_power_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_buffer input; + acpi_status status; + u8 mode; + + input.length = sizeof(u8); + input.pointer = &mode; + mode = hex_to_bin(buf[0]); + dev_dbg(dev, "force_power: storing %#x\n", mode); + if (mode == 0 || mode == 1) { + status = wmi_evaluate_method(INTEL_WMI_THUNDERBOLT_GUID, 0, 1, + &input, NULL); + if (ACPI_FAILURE(status)) { + dev_dbg(dev, "force_power: failed to evaluate ACPI method\n"); + return -ENODEV; + } + } else { + dev_dbg(dev, "force_power: unsupported mode\n"); + return -EINVAL; + } + return count; +} + +static DEVICE_ATTR_WO(force_power); + +static struct attribute *tbt_attrs[] = { + &dev_attr_force_power.attr, + NULL +}; + +static const struct attribute_group tbt_attribute_group = { + .attrs = tbt_attrs, +}; + +static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev, + const void *context) +{ + int ret; + + ret = sysfs_create_group(&wdev->dev.kobj, &tbt_attribute_group); + kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE); + return ret; +} + +static void intel_wmi_thunderbolt_remove(struct wmi_device *wdev) +{ + sysfs_remove_group(&wdev->dev.kobj, &tbt_attribute_group); + kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE); +} + +static const struct wmi_device_id intel_wmi_thunderbolt_id_table[] = { + { .guid_string = INTEL_WMI_THUNDERBOLT_GUID }, + { }, +}; + +static struct wmi_driver intel_wmi_thunderbolt_driver = { + .driver = { + .name = "intel-wmi-thunderbolt", + }, + .probe = intel_wmi_thunderbolt_probe, + .remove = intel_wmi_thunderbolt_remove, + .id_table = intel_wmi_thunderbolt_id_table, +}; + +module_wmi_driver(intel_wmi_thunderbolt_driver); + +MODULE_DEVICE_TABLE(wmi, intel_wmi_thunderbolt_id_table); +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Intel WMI Thunderbolt force power driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b