From 5be52fccaf3d218b278320b0d183aa36aab48add Mon Sep 17 00:00:00 2001 From: Lukasz Luba Date: Mon, 15 Oct 2018 09:21:01 +0200 Subject: thermal: remove unused function parameter Clean unused parameter from internal framework function. Signed-off-by: Lukasz Luba Signed-off-by: Zhang Rui --- drivers/thermal/thermal_core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index d6ebc1cf6aa9..39fc8124741c 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -315,9 +315,7 @@ static void monitor_thermal_zone(struct thermal_zone_device *tz) mutex_unlock(&tz->lock); } -static void handle_non_critical_trips(struct thermal_zone_device *tz, - int trip, - enum thermal_trip_type trip_type) +static void handle_non_critical_trips(struct thermal_zone_device *tz, int trip) { tz->governor ? tz->governor->throttle(tz, trip) : def_governor->throttle(tz, trip); @@ -418,7 +416,7 @@ static void handle_thermal_trip(struct thermal_zone_device *tz, int trip) if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT) handle_critical_trips(tz, trip, type); else - handle_non_critical_trips(tz, trip, type); + handle_non_critical_trips(tz, trip); /* * Alright, we handled this trip successfully. * So, start monitoring again. -- cgit v1.2.3 From 9d6f76c6e82c336aaa57bc0c6e54e0444117bcee Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Oct 2018 22:00:48 +0200 Subject: thermal: int340x_thermal: int3400_thermal: simplify getting .driver_data We should get 'driver_data' from 'struct device' directly. Going via platform_device is an unneeded step back and forth. Signed-off-by: Wolfram Sang Reviewed-by: Daniel Lezcano Signed-off-by: Zhang Rui --- drivers/thermal/int340x_thermal/int3400_thermal.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c index e26b01c05e82..61ca7ce3624e 100644 --- a/drivers/thermal/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/int340x_thermal/int3400_thermal.c @@ -48,8 +48,7 @@ static ssize_t available_uuids_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct platform_device *pdev = to_platform_device(dev); - struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); + struct int3400_thermal_priv *priv = dev_get_drvdata(dev); int i; int length = 0; @@ -68,8 +67,7 @@ static ssize_t available_uuids_show(struct device *dev, static ssize_t current_uuid_show(struct device *dev, struct device_attribute *devattr, char *buf) { - struct platform_device *pdev = to_platform_device(dev); - struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); + struct int3400_thermal_priv *priv = dev_get_drvdata(dev); if (priv->uuid_bitmap & (1 << priv->current_uuid_index)) return sprintf(buf, "%s\n", @@ -82,8 +80,7 @@ static ssize_t current_uuid_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct platform_device *pdev = to_platform_device(dev); - struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); + struct int3400_thermal_priv *priv = dev_get_drvdata(dev); int i; for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) { -- cgit v1.2.3 From 26d84c276c1ecde8548aff41c9fa9e63ceac924e Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Oct 2018 22:00:49 +0200 Subject: thermal: rockchip_thermal: simplify getting .driver_data We should get 'driver_data' from 'struct device' directly. Going via platform_device is an unneeded step back and forth. Signed-off-by: Wolfram Sang Reviewed-by: Heiko Stuebner Reviewed-by: Daniel Lezcano Signed-off-by: Zhang Rui --- drivers/thermal/rockchip_thermal.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index f36375d5a16c..9c7643d62ed7 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1327,8 +1327,7 @@ static int rockchip_thermal_remove(struct platform_device *pdev) static int __maybe_unused rockchip_thermal_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); + struct rockchip_thermal_data *thermal = dev_get_drvdata(dev); int i; for (i = 0; i < thermal->chip->chn_num; i++) @@ -1346,8 +1345,7 @@ static int __maybe_unused rockchip_thermal_suspend(struct device *dev) static int __maybe_unused rockchip_thermal_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); + struct rockchip_thermal_data *thermal = dev_get_drvdata(dev); int i; int error; @@ -1376,7 +1374,7 @@ static int __maybe_unused rockchip_thermal_resume(struct device *dev) id, thermal->regs, thermal->tshut_temp); if (error) - dev_err(&pdev->dev, "%s: invalid tshut=%d, error=%d\n", + dev_err(dev, "%s: invalid tshut=%d, error=%d\n", __func__, thermal->tshut_temp, error); } -- cgit v1.2.3 From 3fc62efe09975c160c413a4b7d10c029e0f04470 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Oct 2018 22:00:50 +0200 Subject: thermal: spear_thermal: simplify getting .driver_data We should get 'driver_data' from 'struct device' directly. Going via platform_device is an unneeded step back and forth. Signed-off-by: Wolfram Sang Reviewed-by: Daniel Lezcano Signed-off-by: Zhang Rui --- drivers/thermal/spear_thermal.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c index 81b35aace9de..8b9d567134d0 100644 --- a/drivers/thermal/spear_thermal.c +++ b/drivers/thermal/spear_thermal.c @@ -56,8 +56,7 @@ static struct thermal_zone_device_ops ops = { static int __maybe_unused spear_thermal_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev); + struct thermal_zone_device *spear_thermal = dev_get_drvdata(dev); struct spear_thermal_dev *stdev = spear_thermal->devdata; unsigned int actual_mask = 0; @@ -73,15 +72,14 @@ static int __maybe_unused spear_thermal_suspend(struct device *dev) static int __maybe_unused spear_thermal_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev); + struct thermal_zone_device *spear_thermal = dev_get_drvdata(dev); struct spear_thermal_dev *stdev = spear_thermal->devdata; unsigned int actual_mask = 0; int ret = 0; ret = clk_enable(stdev->clk); if (ret) { - dev_err(&pdev->dev, "Can't enable clock\n"); + dev_err(dev, "Can't enable clock\n"); return ret; } -- cgit v1.2.3 From 445ae758ceaf394636ad7ab9872f52cdc24165a6 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Oct 2018 22:00:51 +0200 Subject: thermal: st: st_thermal: simplify getting .driver_data We should get 'driver_data' from 'struct device' directly. Going via platform_device is an unneeded step back and forth. Signed-off-by: Wolfram Sang Reviewed-by: Daniel Lezcano Signed-off-by: Zhang Rui --- drivers/thermal/st/st_thermal.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/st/st_thermal.c b/drivers/thermal/st/st_thermal.c index be637e6b01d2..b2bbdf6eb02b 100644 --- a/drivers/thermal/st/st_thermal.c +++ b/drivers/thermal/st/st_thermal.c @@ -277,8 +277,7 @@ EXPORT_SYMBOL_GPL(st_thermal_unregister); #ifdef CONFIG_PM_SLEEP static int st_thermal_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); + struct st_thermal_sensor *sensor = dev_get_drvdata(dev); return st_thermal_sensor_off(sensor); } @@ -286,8 +285,7 @@ static int st_thermal_suspend(struct device *dev) static int st_thermal_resume(struct device *dev) { int ret; - struct platform_device *pdev = to_platform_device(dev); - struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); + struct st_thermal_sensor *sensor = dev_get_drvdata(dev); ret = st_thermal_sensor_on(sensor); if (ret) -- cgit v1.2.3 From 209d07e63e14e04558cebba5e401e41bbde67b88 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 21 Oct 2018 22:00:52 +0200 Subject: thermal: zx2967_thermal: simplify getting .driver_data We should get 'driver_data' from 'struct device' directly. Going via platform_device is an unneeded step back and forth. Signed-off-by: Wolfram Sang Acked-by: Shawn Guo Reviewed-by: Daniel Lezcano Signed-off-by: Zhang Rui --- drivers/thermal/zx2967_thermal.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/zx2967_thermal.c b/drivers/thermal/zx2967_thermal.c index 6acce0bce7c0..145ebf371598 100644 --- a/drivers/thermal/zx2967_thermal.c +++ b/drivers/thermal/zx2967_thermal.c @@ -207,8 +207,7 @@ MODULE_DEVICE_TABLE(of, zx2967_thermal_id_table); #ifdef CONFIG_PM_SLEEP static int zx2967_thermal_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev); + struct zx2967_thermal_priv *priv = dev_get_drvdata(dev); if (priv && priv->clk_topcrm) clk_disable_unprepare(priv->clk_topcrm); @@ -221,8 +220,7 @@ static int zx2967_thermal_suspend(struct device *dev) static int zx2967_thermal_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev); + struct zx2967_thermal_priv *priv = dev_get_drvdata(dev); int error; error = clk_prepare_enable(priv->clk_topcrm); -- cgit v1.2.3 From 964f4843a455d2ffb199512b08be8d5f077c4cac Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Wed, 7 Nov 2018 14:36:11 -0800 Subject: Thermal: do not clear passive state during system sleep commit ff140fea847e ("Thermal: handle thermal zone device properly during system sleep") added PM hook to call thermal zone reset during sleep. However resetting thermal zone will also clear the passive state and thus cancel the polling queue which leads the passive cooling device state not being cleared properly after sleep. thermal_pm_notify => thermal_zone_device_reset set passive to 0 thermal_zone_trip_update will skip update passive as `old_target == instance->target'. monitor_thermal_zone => thermal_zone_device_set_polling will cancel tz->poll_queue, so the cooling device state will not be changed afterwards. Reported-by: Kame Wang Signed-off-by: Wei Wang Signed-off-by: Zhang Rui --- drivers/thermal/thermal_core.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 39fc8124741c..6590bb5cb688 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -451,16 +451,20 @@ static void update_temperature(struct thermal_zone_device *tz) tz->last_temperature, tz->temperature); } -static void thermal_zone_device_reset(struct thermal_zone_device *tz) +static void thermal_zone_device_init(struct thermal_zone_device *tz) { struct thermal_instance *pos; - tz->temperature = THERMAL_TEMP_INVALID; - tz->passive = 0; list_for_each_entry(pos, &tz->thermal_instances, tz_node) pos->initialized = false; } +static void thermal_zone_device_reset(struct thermal_zone_device *tz) +{ + tz->passive = 0; + thermal_zone_device_init(tz); +} + void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { @@ -1502,7 +1506,7 @@ static int thermal_pm_notify(struct notifier_block *nb, case PM_POST_SUSPEND: atomic_set(&in_suspend, 0); list_for_each_entry(tz, &thermal_tz_list, node) { - thermal_zone_device_reset(tz); + thermal_zone_device_init(tz); thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); } -- cgit v1.2.3 From 68000a0d983f539c95ebe5dccd4f29535c7ac0af Mon Sep 17 00:00:00 2001 From: Thara Gopinath Date: Tue, 27 Nov 2018 17:43:11 -0500 Subject: thermal: Fix locking in cooling device sysfs update cur_state Sysfs interface to update cooling device cur_state does not currently holding cooling device lock sometimes leading to stale values in cur_state if getting updated simultanelously from user space and thermal framework. Adding the proper locking code fixes this issue. Signed-off-by: Thara Gopinath Signed-off-by: Zhang Rui --- drivers/thermal/thermal_sysfs.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index 2241ceae7d7f..aa99edb4dff7 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -712,11 +712,14 @@ cur_state_store(struct device *dev, struct device_attribute *attr, if ((long)state < 0) return -EINVAL; + mutex_lock(&cdev->lock); + result = cdev->ops->set_cur_state(cdev, state); - if (result) - return result; - thermal_cooling_device_stats_update(cdev, state); - return count; + if (!result) + thermal_cooling_device_stats_update(cdev, state); + + mutex_unlock(&cdev->lock); + return result ? result : count; } static struct device_attribute -- cgit v1.2.3 From 3e8c4d31f8eddc957ee293b3556586ee698d9a21 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Fri, 7 Dec 2018 12:25:26 +0530 Subject: drivers: thermal: Move various drivers for intel platforms into a subdir This cleans up the directory a bit, now that we have several other platforms using platform-specific sub-directories. Compile-tested with ARCH=x86 defconfig and the drivers explicitly enabled with menuconfig. Signed-off-by: Amit Kucheria Acked-by: Daniel Lezcano Signed-off-by: Zhang Rui --- drivers/thermal/Kconfig | 83 +-- drivers/thermal/Makefile | 9 +- drivers/thermal/int340x_thermal/Kconfig | 42 -- drivers/thermal/int340x_thermal/Makefile | 8 - drivers/thermal/int340x_thermal/acpi_thermal_rel.c | 394 ---------- drivers/thermal/int340x_thermal/acpi_thermal_rel.h | 85 --- drivers/thermal/int340x_thermal/int3400_thermal.c | 382 ---------- drivers/thermal/int340x_thermal/int3402_thermal.c | 108 --- drivers/thermal/int340x_thermal/int3403_thermal.c | 311 -------- drivers/thermal/int340x_thermal/int3406_thermal.c | 213 ------ .../thermal/int340x_thermal/int340x_thermal_zone.c | 295 -------- .../thermal/int340x_thermal/int340x_thermal_zone.h | 70 -- .../int340x_thermal/processor_thermal_device.c | 525 ------------- drivers/thermal/intel/Kconfig | 77 ++ drivers/thermal/intel/Makefile | 12 + drivers/thermal/intel/int340x_thermal/Kconfig | 42 ++ drivers/thermal/intel/int340x_thermal/Makefile | 8 + .../intel/int340x_thermal/acpi_thermal_rel.c | 394 ++++++++++ .../intel/int340x_thermal/acpi_thermal_rel.h | 85 +++ .../intel/int340x_thermal/int3400_thermal.c | 382 ++++++++++ .../intel/int340x_thermal/int3402_thermal.c | 108 +++ .../intel/int340x_thermal/int3403_thermal.c | 311 ++++++++ .../intel/int340x_thermal/int3406_thermal.c | 213 ++++++ .../intel/int340x_thermal/int340x_thermal_zone.c | 295 ++++++++ .../intel/int340x_thermal/int340x_thermal_zone.h | 70 ++ .../int340x_thermal/processor_thermal_device.c | 525 +++++++++++++ drivers/thermal/intel/intel_bxt_pmic_thermal.c | 299 ++++++++ drivers/thermal/intel/intel_pch_thermal.c | 432 +++++++++++ drivers/thermal/intel/intel_powerclamp.c | 815 +++++++++++++++++++++ drivers/thermal/intel/intel_quark_dts_thermal.c | 471 ++++++++++++ drivers/thermal/intel/intel_soc_dts_iosf.c | 478 ++++++++++++ drivers/thermal/intel/intel_soc_dts_iosf.h | 62 ++ drivers/thermal/intel/intel_soc_dts_thermal.c | 132 ++++ drivers/thermal/intel/x86_pkg_temp_thermal.c | 558 ++++++++++++++ drivers/thermal/intel_bxt_pmic_thermal.c | 299 -------- drivers/thermal/intel_pch_thermal.c | 432 ----------- drivers/thermal/intel_powerclamp.c | 815 --------------------- drivers/thermal/intel_quark_dts_thermal.c | 471 ------------ drivers/thermal/intel_soc_dts_iosf.c | 478 ------------ drivers/thermal/intel_soc_dts_iosf.h | 62 -- drivers/thermal/intel_soc_dts_thermal.c | 132 ---- drivers/thermal/x86_pkg_temp_thermal.c | 558 -------------- 42 files changed, 5775 insertions(+), 5766 deletions(-) delete mode 100644 drivers/thermal/int340x_thermal/Kconfig delete mode 100644 drivers/thermal/int340x_thermal/Makefile delete mode 100644 drivers/thermal/int340x_thermal/acpi_thermal_rel.c delete mode 100644 drivers/thermal/int340x_thermal/acpi_thermal_rel.h delete mode 100644 drivers/thermal/int340x_thermal/int3400_thermal.c delete mode 100644 drivers/thermal/int340x_thermal/int3402_thermal.c delete mode 100644 drivers/thermal/int340x_thermal/int3403_thermal.c delete mode 100644 drivers/thermal/int340x_thermal/int3406_thermal.c delete mode 100644 drivers/thermal/int340x_thermal/int340x_thermal_zone.c delete mode 100644 drivers/thermal/int340x_thermal/int340x_thermal_zone.h delete mode 100644 drivers/thermal/int340x_thermal/processor_thermal_device.c create mode 100644 drivers/thermal/intel/Kconfig create mode 100644 drivers/thermal/intel/Makefile create mode 100644 drivers/thermal/intel/int340x_thermal/Kconfig create mode 100644 drivers/thermal/intel/int340x_thermal/Makefile create mode 100644 drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c create mode 100644 drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h create mode 100644 drivers/thermal/intel/int340x_thermal/int3400_thermal.c create mode 100644 drivers/thermal/intel/int340x_thermal/int3402_thermal.c create mode 100644 drivers/thermal/intel/int340x_thermal/int3403_thermal.c create mode 100644 drivers/thermal/intel/int340x_thermal/int3406_thermal.c create mode 100644 drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c create mode 100644 drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h create mode 100644 drivers/thermal/intel/int340x_thermal/processor_thermal_device.c create mode 100644 drivers/thermal/intel/intel_bxt_pmic_thermal.c create mode 100644 drivers/thermal/intel/intel_pch_thermal.c create mode 100644 drivers/thermal/intel/intel_powerclamp.c create mode 100644 drivers/thermal/intel/intel_quark_dts_thermal.c create mode 100644 drivers/thermal/intel/intel_soc_dts_iosf.c create mode 100644 drivers/thermal/intel/intel_soc_dts_iosf.h create mode 100644 drivers/thermal/intel/intel_soc_dts_thermal.c create mode 100644 drivers/thermal/intel/x86_pkg_temp_thermal.c delete mode 100644 drivers/thermal/intel_bxt_pmic_thermal.c delete mode 100644 drivers/thermal/intel_pch_thermal.c delete mode 100644 drivers/thermal/intel_powerclamp.c delete mode 100644 drivers/thermal/intel_quark_dts_thermal.c delete mode 100644 drivers/thermal/intel_soc_dts_iosf.c delete mode 100644 drivers/thermal/intel_soc_dts_iosf.h delete mode 100644 drivers/thermal/intel_soc_dts_thermal.c delete mode 100644 drivers/thermal/x86_pkg_temp_thermal.c (limited to 'drivers/thermal') diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 5422523c03f8..772ab9dadda7 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -326,84 +326,6 @@ config DA9062_THERMAL zone. Compatible with the DA9062 and DA9061 PMICs. -config INTEL_POWERCLAMP - tristate "Intel PowerClamp idle injection driver" - depends on THERMAL - depends on X86 - depends on CPU_SUP_INTEL - help - Enable this to enable Intel PowerClamp idle injection driver. This - enforce idle time which results in more package C-state residency. The - user interface is exposed via generic thermal framework. - -config X86_PKG_TEMP_THERMAL - tristate "X86 package temperature thermal driver" - depends on X86_THERMAL_VECTOR - select THERMAL_GOV_USER_SPACE - select THERMAL_WRITABLE_TRIPS - default m - help - Enable this to register CPU digital sensor for package temperature as - thermal zone. Each package will have its own thermal zone. There are - two trip points which can be set by user to get notifications via thermal - notification methods. - -config INTEL_SOC_DTS_IOSF_CORE - tristate - depends on X86 && PCI - select IOSF_MBI - help - This is becoming a common feature for Intel SoCs to expose the additional - digital temperature sensors (DTSs) using side band interface (IOSF). This - implements the common set of helper functions to register, get temperature - and get/set thresholds on DTSs. - -config INTEL_SOC_DTS_THERMAL - tristate "Intel SoCs DTS thermal driver" - depends on X86 && PCI && ACPI - select INTEL_SOC_DTS_IOSF_CORE - select THERMAL_WRITABLE_TRIPS - help - Enable this to register Intel SoCs (e.g. Bay Trail) platform digital - temperature sensor (DTS). These SoCs have two additional DTSs in - addition to DTSs on CPU cores. Each DTS will be registered as a - thermal zone. There are two trip points. One of the trip point can - be set by user mode programs to get notifications via Linux thermal - notification methods.The other trip is a critical trip point, which - was set by the driver based on the TJ MAX temperature. - -config INTEL_QUARK_DTS_THERMAL - tristate "Intel Quark DTS thermal driver" - depends on X86_INTEL_QUARK - help - Enable this to register Intel Quark SoC (e.g. X1000) platform digital - temperature sensor (DTS). For X1000 SoC, it has one on-die DTS. - The DTS will be registered as a thermal zone. There are two trip points: - hot & critical. The critical trip point default value is set by - underlying BIOS/Firmware. - -menu "ACPI INT340X thermal drivers" -source drivers/thermal/int340x_thermal/Kconfig -endmenu - -config INTEL_BXT_PMIC_THERMAL - tristate "Intel Broxton PMIC thermal driver" - depends on X86 && INTEL_SOC_PMIC_BXTWC && REGMAP - help - Select this driver for Intel Broxton PMIC with ADC channels monitoring - system temperature measurements and alerts. - This driver is used for monitoring the ADC channels of PMIC and handles - the alert trip point interrupts and notifies the thermal framework with - the trip point and temperature details of the zone. - -config INTEL_PCH_THERMAL - tristate "Intel PCH Thermal Reporting Driver" - depends on X86 && PCI - help - Enable this to support thermal reporting on certain intel PCHs. - Thermal reporting device will provide temperature reading, - programmable trip points and other information. - config MTK_THERMAL tristate "Temperature sensor driver for mediatek SoCs" depends on ARCH_MEDIATEK || COMPILE_TEST @@ -415,6 +337,11 @@ config MTK_THERMAL Enable this option if you want to have support for thermal management controller present in Mediatek SoCs +menu "Intel thermal drivers" +depends on X86 || X86_INTEL_QUARK || COMPILE_TEST +source "drivers/thermal/intel/Kconfig" +endmenu + menu "Broadcom thermal drivers" depends on ARCH_BCM || ARCH_BRCMSTB || ARCH_BCM2835 || COMPILE_TEST source "drivers/thermal/broadcom/Kconfig" diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 82bb50dc6423..0b5d33a49b3e 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -44,15 +44,8 @@ obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o -obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o -obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o -obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE) += intel_soc_dts_iosf.o -obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o -obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o +obj-y += intel/ obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ -obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ -obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o -obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o obj-y += st/ obj-$(CONFIG_QCOM_TSENS) += qcom/ obj-y += tegra/ diff --git a/drivers/thermal/int340x_thermal/Kconfig b/drivers/thermal/int340x_thermal/Kconfig deleted file mode 100644 index 0582bd12a239..000000000000 --- a/drivers/thermal/int340x_thermal/Kconfig +++ /dev/null @@ -1,42 +0,0 @@ -# -# ACPI INT340x thermal drivers configuration -# - -config INT340X_THERMAL - tristate "ACPI INT340X thermal drivers" - depends on X86 && ACPI - select THERMAL_GOV_USER_SPACE - select ACPI_THERMAL_REL - select ACPI_FAN - select INTEL_SOC_DTS_IOSF_CORE - help - Newer laptops and tablets that use ACPI may have thermal sensors and - other devices with thermal control capabilities outside the core - CPU/SOC, for thermal safety reasons. - They are exposed for the OS to use via the INT3400 ACPI device object - as the master, and INT3401~INT340B ACPI device objects as the slaves. - Enable this to expose the temperature information and cooling ability - from these objects to userspace via the normal thermal framework. - This means that a wide range of applications and GUI widgets can show - the information to the user or use this information for making - decisions. For example, the Intel Thermal Daemon can use this - information to allow the user to select his laptop to run without - turning on the fans. - -config ACPI_THERMAL_REL - tristate - depends on ACPI - -if INT340X_THERMAL - -config INT3406_THERMAL - tristate "ACPI INT3406 display thermal driver" - depends on ACPI_VIDEO - help - The display thermal device represents the LED/LCD display panel - that may or may not include touch support. The main function of - the display thermal device is to allow control of the display - brightness in order to address a thermal condition or to reduce - power consumed by display device. - -endif diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile deleted file mode 100644 index 287eb0a1476d..000000000000 --- a/drivers/thermal/int340x_thermal/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o -obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal_zone.o -obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o -obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o -obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o -obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o -obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o diff --git a/drivers/thermal/int340x_thermal/acpi_thermal_rel.c b/drivers/thermal/int340x_thermal/acpi_thermal_rel.c deleted file mode 100644 index 45e7e5cbdffb..000000000000 --- a/drivers/thermal/int340x_thermal/acpi_thermal_rel.c +++ /dev/null @@ -1,394 +0,0 @@ -/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship - * - * Copyright (c) 2014 Intel Corp - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * - */ - -/* - * Two functionalities included: - * 1. Export _TRT, _ART, via misc device interface to the userspace. - * 2. Provide parsing result to kernel drivers - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "acpi_thermal_rel.h" - -static acpi_handle acpi_thermal_rel_handle; -static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock); -static int acpi_thermal_rel_chrdev_count; /* #times opened */ -static int acpi_thermal_rel_chrdev_exclu; /* already open exclusive? */ - -static int acpi_thermal_rel_open(struct inode *inode, struct file *file) -{ - spin_lock(&acpi_thermal_rel_chrdev_lock); - if (acpi_thermal_rel_chrdev_exclu || - (acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) { - spin_unlock(&acpi_thermal_rel_chrdev_lock); - return -EBUSY; - } - - if (file->f_flags & O_EXCL) - acpi_thermal_rel_chrdev_exclu = 1; - acpi_thermal_rel_chrdev_count++; - - spin_unlock(&acpi_thermal_rel_chrdev_lock); - - return nonseekable_open(inode, file); -} - -static int acpi_thermal_rel_release(struct inode *inode, struct file *file) -{ - spin_lock(&acpi_thermal_rel_chrdev_lock); - acpi_thermal_rel_chrdev_count--; - acpi_thermal_rel_chrdev_exclu = 0; - spin_unlock(&acpi_thermal_rel_chrdev_lock); - - return 0; -} - -/** - * acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling - * - * @handle: ACPI handle of the device contains _TRT - * @trt_count: the number of valid entries resulted from parsing _TRT - * @trtp: pointer to pointer of array of _TRT entries in parsing result - * @create_dev: whether to create platform devices for target and source - * - */ -int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp, - bool create_dev) -{ - acpi_status status; - int result = 0; - int i; - int nr_bad_entries = 0; - struct trt *trts; - struct acpi_device *adev; - union acpi_object *p; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_buffer element = { 0, NULL }; - struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" }; - - if (!acpi_has_method(handle, "_TRT")) - return -ENODEV; - - status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer); - if (ACPI_FAILURE(status)) - return -ENODEV; - - p = buffer.pointer; - if (!p || (p->type != ACPI_TYPE_PACKAGE)) { - pr_err("Invalid _TRT data\n"); - result = -EFAULT; - goto end; - } - - *trt_count = p->package.count; - trts = kcalloc(*trt_count, sizeof(struct trt), GFP_KERNEL); - if (!trts) { - result = -ENOMEM; - goto end; - } - - for (i = 0; i < *trt_count; i++) { - struct trt *trt = &trts[i - nr_bad_entries]; - - element.length = sizeof(struct trt); - element.pointer = trt; - - status = acpi_extract_package(&(p->package.elements[i]), - &trt_format, &element); - if (ACPI_FAILURE(status)) { - nr_bad_entries++; - pr_warn("_TRT package %d is invalid, ignored\n", i); - continue; - } - if (!create_dev) - continue; - - result = acpi_bus_get_device(trt->source, &adev); - if (result) - pr_warn("Failed to get source ACPI device\n"); - - result = acpi_bus_get_device(trt->target, &adev); - if (result) - pr_warn("Failed to get target ACPI device\n"); - } - - result = 0; - - *trtp = trts; - /* don't count bad entries */ - *trt_count -= nr_bad_entries; -end: - kfree(buffer.pointer); - return result; -} -EXPORT_SYMBOL(acpi_parse_trt); - -/** - * acpi_parse_art - Parse Active Relationship Table _ART - * - * @handle: ACPI handle of the device contains _ART - * @art_count: the number of valid entries resulted from parsing _ART - * @artp: pointer to pointer of array of art entries in parsing result - * @create_dev: whether to create platform devices for target and source - * - */ -int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp, - bool create_dev) -{ - acpi_status status; - int result = 0; - int i; - int nr_bad_entries = 0; - struct art *arts; - struct acpi_device *adev; - union acpi_object *p; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_buffer element = { 0, NULL }; - struct acpi_buffer art_format = { - sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" }; - - if (!acpi_has_method(handle, "_ART")) - return -ENODEV; - - status = acpi_evaluate_object(handle, "_ART", NULL, &buffer); - if (ACPI_FAILURE(status)) - return -ENODEV; - - p = buffer.pointer; - if (!p || (p->type != ACPI_TYPE_PACKAGE)) { - pr_err("Invalid _ART data\n"); - result = -EFAULT; - goto end; - } - - /* ignore p->package.elements[0], as this is _ART Revision field */ - *art_count = p->package.count - 1; - arts = kcalloc(*art_count, sizeof(struct art), GFP_KERNEL); - if (!arts) { - result = -ENOMEM; - goto end; - } - - for (i = 0; i < *art_count; i++) { - struct art *art = &arts[i - nr_bad_entries]; - - element.length = sizeof(struct art); - element.pointer = art; - - status = acpi_extract_package(&(p->package.elements[i + 1]), - &art_format, &element); - if (ACPI_FAILURE(status)) { - pr_warn("_ART package %d is invalid, ignored", i); - nr_bad_entries++; - continue; - } - if (!create_dev) - continue; - - if (art->source) { - result = acpi_bus_get_device(art->source, &adev); - if (result) - pr_warn("Failed to get source ACPI device\n"); - } - if (art->target) { - result = acpi_bus_get_device(art->target, &adev); - if (result) - pr_warn("Failed to get target ACPI device\n"); - } - } - - *artp = arts; - /* don't count bad entries */ - *art_count -= nr_bad_entries; -end: - kfree(buffer.pointer); - return result; -} -EXPORT_SYMBOL(acpi_parse_art); - - -/* get device name from acpi handle */ -static void get_single_name(acpi_handle handle, char *name) -{ - struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER}; - - if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer))) - pr_warn("Failed to get device name from acpi handle\n"); - else { - memcpy(name, buffer.pointer, ACPI_NAME_SIZE); - kfree(buffer.pointer); - } -} - -static int fill_art(char __user *ubuf) -{ - int i; - int ret; - int count; - int art_len; - struct art *arts = NULL; - union art_object *art_user; - - ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false); - if (ret) - goto free_art; - art_len = count * sizeof(union art_object); - art_user = kzalloc(art_len, GFP_KERNEL); - if (!art_user) { - ret = -ENOMEM; - goto free_art; - } - /* now fill in user art data */ - for (i = 0; i < count; i++) { - /* userspace art needs device name instead of acpi reference */ - get_single_name(arts[i].source, art_user[i].source_device); - get_single_name(arts[i].target, art_user[i].target_device); - /* copy the rest int data in addition to source and target */ - memcpy(&art_user[i].weight, &arts[i].weight, - sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2)); - } - - if (copy_to_user(ubuf, art_user, art_len)) - ret = -EFAULT; - kfree(art_user); -free_art: - kfree(arts); - return ret; -} - -static int fill_trt(char __user *ubuf) -{ - int i; - int ret; - int count; - int trt_len; - struct trt *trts = NULL; - union trt_object *trt_user; - - ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false); - if (ret) - goto free_trt; - trt_len = count * sizeof(union trt_object); - trt_user = kzalloc(trt_len, GFP_KERNEL); - if (!trt_user) { - ret = -ENOMEM; - goto free_trt; - } - /* now fill in user trt data */ - for (i = 0; i < count; i++) { - /* userspace trt needs device name instead of acpi reference */ - get_single_name(trts[i].source, trt_user[i].source_device); - get_single_name(trts[i].target, trt_user[i].target_device); - trt_user[i].sample_period = trts[i].sample_period; - trt_user[i].influence = trts[i].influence; - } - - if (copy_to_user(ubuf, trt_user, trt_len)) - ret = -EFAULT; - kfree(trt_user); -free_trt: - kfree(trts); - return ret; -} - -static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd, - unsigned long __arg) -{ - int ret = 0; - unsigned long length = 0; - int count = 0; - char __user *arg = (void __user *)__arg; - struct trt *trts = NULL; - struct art *arts = NULL; - - switch (cmd) { - case ACPI_THERMAL_GET_TRT_COUNT: - ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, - &trts, false); - kfree(trts); - if (!ret) - return put_user(count, (unsigned long __user *)__arg); - return ret; - case ACPI_THERMAL_GET_TRT_LEN: - ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, - &trts, false); - kfree(trts); - length = count * sizeof(union trt_object); - if (!ret) - return put_user(length, (unsigned long __user *)__arg); - return ret; - case ACPI_THERMAL_GET_TRT: - return fill_trt(arg); - case ACPI_THERMAL_GET_ART_COUNT: - ret = acpi_parse_art(acpi_thermal_rel_handle, &count, - &arts, false); - kfree(arts); - if (!ret) - return put_user(count, (unsigned long __user *)__arg); - return ret; - case ACPI_THERMAL_GET_ART_LEN: - ret = acpi_parse_art(acpi_thermal_rel_handle, &count, - &arts, false); - kfree(arts); - length = count * sizeof(union art_object); - if (!ret) - return put_user(length, (unsigned long __user *)__arg); - return ret; - - case ACPI_THERMAL_GET_ART: - return fill_art(arg); - - default: - return -ENOTTY; - } -} - -static const struct file_operations acpi_thermal_rel_fops = { - .owner = THIS_MODULE, - .open = acpi_thermal_rel_open, - .release = acpi_thermal_rel_release, - .unlocked_ioctl = acpi_thermal_rel_ioctl, - .llseek = no_llseek, -}; - -static struct miscdevice acpi_thermal_rel_misc_device = { - .minor = MISC_DYNAMIC_MINOR, - "acpi_thermal_rel", - &acpi_thermal_rel_fops -}; - -int acpi_thermal_rel_misc_device_add(acpi_handle handle) -{ - acpi_thermal_rel_handle = handle; - - return misc_register(&acpi_thermal_rel_misc_device); -} -EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add); - -int acpi_thermal_rel_misc_device_remove(acpi_handle handle) -{ - misc_deregister(&acpi_thermal_rel_misc_device); - - return 0; -} -EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove); - -MODULE_AUTHOR("Zhang Rui "); -MODULE_AUTHOR("Jacob Pan - -#define ACPI_THERMAL_MAGIC 's' - -#define ACPI_THERMAL_GET_TRT_LEN _IOR(ACPI_THERMAL_MAGIC, 1, unsigned long) -#define ACPI_THERMAL_GET_ART_LEN _IOR(ACPI_THERMAL_MAGIC, 2, unsigned long) -#define ACPI_THERMAL_GET_TRT_COUNT _IOR(ACPI_THERMAL_MAGIC, 3, unsigned long) -#define ACPI_THERMAL_GET_ART_COUNT _IOR(ACPI_THERMAL_MAGIC, 4, unsigned long) - -#define ACPI_THERMAL_GET_TRT _IOR(ACPI_THERMAL_MAGIC, 5, unsigned long) -#define ACPI_THERMAL_GET_ART _IOR(ACPI_THERMAL_MAGIC, 6, unsigned long) - -struct art { - acpi_handle source; - acpi_handle target; - u64 weight; - u64 ac0_max; - u64 ac1_max; - u64 ac2_max; - u64 ac3_max; - u64 ac4_max; - u64 ac5_max; - u64 ac6_max; - u64 ac7_max; - u64 ac8_max; - u64 ac9_max; -} __packed; - -struct trt { - acpi_handle source; - acpi_handle target; - u64 influence; - u64 sample_period; - u64 reserved1; - u64 reserved2; - u64 reserved3; - u64 reserved4; -} __packed; - -#define ACPI_NR_ART_ELEMENTS 13 -/* for usrspace */ -union art_object { - struct { - char source_device[8]; /* ACPI single name */ - char target_device[8]; /* ACPI single name */ - u64 weight; - u64 ac0_max_level; - u64 ac1_max_level; - u64 ac2_max_level; - u64 ac3_max_level; - u64 ac4_max_level; - u64 ac5_max_level; - u64 ac6_max_level; - u64 ac7_max_level; - u64 ac8_max_level; - u64 ac9_max_level; - }; - u64 __data[ACPI_NR_ART_ELEMENTS]; -}; - -union trt_object { - struct { - char source_device[8]; /* ACPI single name */ - char target_device[8]; /* ACPI single name */ - u64 influence; - u64 sample_period; - u64 reserved[4]; - }; - u64 __data[8]; -}; - -#ifdef __KERNEL__ -int acpi_thermal_rel_misc_device_add(acpi_handle handle); -int acpi_thermal_rel_misc_device_remove(acpi_handle handle); -int acpi_parse_art(acpi_handle handle, int *art_count, struct art **arts, - bool create_dev); -int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trts, - bool create_dev); -#endif - -#endif /* __ACPI_ACPI_THERMAL_H */ diff --git a/drivers/thermal/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c deleted file mode 100644 index 61ca7ce3624e..000000000000 --- a/drivers/thermal/int340x_thermal/int3400_thermal.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * INT3400 thermal driver - * - * Copyright (C) 2014, Intel Corporation - * Authors: Zhang Rui - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include "acpi_thermal_rel.h" - -#define INT3400_THERMAL_TABLE_CHANGED 0x83 - -enum int3400_thermal_uuid { - INT3400_THERMAL_PASSIVE_1, - INT3400_THERMAL_ACTIVE, - INT3400_THERMAL_CRITICAL, - INT3400_THERMAL_MAXIMUM_UUID, -}; - -static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { - "42A441D6-AE6A-462b-A84B-4A8CE79027D3", - "3A95C389-E4B8-4629-A526-C52C88626BAE", - "97C68AE7-15FA-499c-B8C9-5DA81D606E0A", -}; - -struct int3400_thermal_priv { - struct acpi_device *adev; - struct thermal_zone_device *thermal; - int mode; - int art_count; - struct art *arts; - int trt_count; - struct trt *trts; - u8 uuid_bitmap; - int rel_misc_dev_res; - int current_uuid_index; -}; - -static ssize_t available_uuids_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct int3400_thermal_priv *priv = dev_get_drvdata(dev); - int i; - int length = 0; - - for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) { - if (priv->uuid_bitmap & (1 << i)) - if (PAGE_SIZE - length > 0) - length += snprintf(&buf[length], - PAGE_SIZE - length, - "%s\n", - int3400_thermal_uuids[i]); - } - - return length; -} - -static ssize_t current_uuid_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct int3400_thermal_priv *priv = dev_get_drvdata(dev); - - if (priv->uuid_bitmap & (1 << priv->current_uuid_index)) - return sprintf(buf, "%s\n", - int3400_thermal_uuids[priv->current_uuid_index]); - else - return sprintf(buf, "INVALID\n"); -} - -static ssize_t current_uuid_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct int3400_thermal_priv *priv = dev_get_drvdata(dev); - int i; - - for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) { - if ((priv->uuid_bitmap & (1 << i)) && - !(strncmp(buf, int3400_thermal_uuids[i], - sizeof(int3400_thermal_uuids[i]) - 1))) { - priv->current_uuid_index = i; - return count; - } - } - - return -EINVAL; -} - -static DEVICE_ATTR_RW(current_uuid); -static DEVICE_ATTR_RO(available_uuids); -static struct attribute *uuid_attrs[] = { - &dev_attr_available_uuids.attr, - &dev_attr_current_uuid.attr, - NULL -}; - -static const struct attribute_group uuid_attribute_group = { - .attrs = uuid_attrs, - .name = "uuids" -}; - -static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv) -{ - struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL}; - union acpi_object *obja, *objb; - int i, j; - int result = 0; - acpi_status status; - - status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf); - if (ACPI_FAILURE(status)) - return -ENODEV; - - obja = (union acpi_object *)buf.pointer; - if (obja->type != ACPI_TYPE_PACKAGE) { - result = -EINVAL; - goto end; - } - - for (i = 0; i < obja->package.count; i++) { - objb = &obja->package.elements[i]; - if (objb->type != ACPI_TYPE_BUFFER) { - result = -EINVAL; - goto end; - } - - /* UUID must be 16 bytes */ - if (objb->buffer.length != 16) { - result = -EINVAL; - goto end; - } - - for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) { - guid_t guid; - - guid_parse(int3400_thermal_uuids[j], &guid); - if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) { - priv->uuid_bitmap |= (1 << j); - break; - } - } - } - -end: - kfree(buf.pointer); - return result; -} - -static int int3400_thermal_run_osc(acpi_handle handle, - enum int3400_thermal_uuid uuid, bool enable) -{ - u32 ret, buf[2]; - acpi_status status; - int result = 0; - struct acpi_osc_context context = { - .uuid_str = int3400_thermal_uuids[uuid], - .rev = 1, - .cap.length = 8, - }; - - buf[OSC_QUERY_DWORD] = 0; - buf[OSC_SUPPORT_DWORD] = enable; - - context.cap.pointer = buf; - - status = acpi_run_osc(handle, &context); - if (ACPI_SUCCESS(status)) { - ret = *((u32 *)(context.ret.pointer + 4)); - if (ret != enable) - result = -EPERM; - } else - result = -EPERM; - - kfree(context.ret.pointer); - return result; -} - -static void int3400_notify(acpi_handle handle, - u32 event, - void *data) -{ - struct int3400_thermal_priv *priv = data; - char *thermal_prop[5]; - - if (!priv) - return; - - switch (event) { - case INT3400_THERMAL_TABLE_CHANGED: - thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", - priv->thermal->type); - thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", - priv->thermal->temperature); - thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP="); - thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", - THERMAL_TABLE_CHANGED); - thermal_prop[4] = NULL; - kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, - thermal_prop); - break; - default: - /* Ignore unknown notification codes sent to INT3400 device */ - break; - } -} - -static int int3400_thermal_get_temp(struct thermal_zone_device *thermal, - int *temp) -{ - *temp = 20 * 1000; /* faked temp sensor with 20C */ - return 0; -} - -static int int3400_thermal_get_mode(struct thermal_zone_device *thermal, - enum thermal_device_mode *mode) -{ - struct int3400_thermal_priv *priv = thermal->devdata; - - if (!priv) - return -EINVAL; - - *mode = priv->mode; - - return 0; -} - -static int int3400_thermal_set_mode(struct thermal_zone_device *thermal, - enum thermal_device_mode mode) -{ - struct int3400_thermal_priv *priv = thermal->devdata; - bool enable; - int result = 0; - - if (!priv) - return -EINVAL; - - if (mode == THERMAL_DEVICE_ENABLED) - enable = true; - else if (mode == THERMAL_DEVICE_DISABLED) - enable = false; - else - return -EINVAL; - - if (enable != priv->mode) { - priv->mode = enable; - result = int3400_thermal_run_osc(priv->adev->handle, - priv->current_uuid_index, - enable); - } - return result; -} - -static struct thermal_zone_device_ops int3400_thermal_ops = { - .get_temp = int3400_thermal_get_temp, -}; - -static struct thermal_zone_params int3400_thermal_params = { - .governor_name = "user_space", - .no_hwmon = true, -}; - -static int int3400_thermal_probe(struct platform_device *pdev) -{ - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - struct int3400_thermal_priv *priv; - int result; - - if (!adev) - return -ENODEV; - - priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->adev = adev; - - result = int3400_thermal_get_uuids(priv); - if (result) - goto free_priv; - - result = acpi_parse_art(priv->adev->handle, &priv->art_count, - &priv->arts, true); - if (result) - dev_dbg(&pdev->dev, "_ART table parsing error\n"); - - result = acpi_parse_trt(priv->adev->handle, &priv->trt_count, - &priv->trts, true); - if (result) - dev_dbg(&pdev->dev, "_TRT table parsing error\n"); - - platform_set_drvdata(pdev, priv); - - if (priv->uuid_bitmap & 1 << INT3400_THERMAL_PASSIVE_1) { - int3400_thermal_ops.get_mode = int3400_thermal_get_mode; - int3400_thermal_ops.set_mode = int3400_thermal_set_mode; - } - priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, - priv, &int3400_thermal_ops, - &int3400_thermal_params, 0, 0); - if (IS_ERR(priv->thermal)) { - result = PTR_ERR(priv->thermal); - goto free_art_trt; - } - - priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add( - priv->adev->handle); - - result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group); - if (result) - goto free_rel_misc; - - result = acpi_install_notify_handler( - priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify, - (void *)priv); - if (result) - goto free_sysfs; - - return 0; - -free_sysfs: - sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); -free_rel_misc: - if (!priv->rel_misc_dev_res) - acpi_thermal_rel_misc_device_remove(priv->adev->handle); - thermal_zone_device_unregister(priv->thermal); -free_art_trt: - kfree(priv->trts); - kfree(priv->arts); -free_priv: - kfree(priv); - return result; -} - -static int int3400_thermal_remove(struct platform_device *pdev) -{ - struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); - - acpi_remove_notify_handler( - priv->adev->handle, ACPI_DEVICE_NOTIFY, - int3400_notify); - - if (!priv->rel_misc_dev_res) - acpi_thermal_rel_misc_device_remove(priv->adev->handle); - - sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); - thermal_zone_device_unregister(priv->thermal); - kfree(priv->trts); - kfree(priv->arts); - kfree(priv); - return 0; -} - -static const struct acpi_device_id int3400_thermal_match[] = { - {"INT3400", 0}, - {} -}; - -MODULE_DEVICE_TABLE(acpi, int3400_thermal_match); - -static struct platform_driver int3400_thermal_driver = { - .probe = int3400_thermal_probe, - .remove = int3400_thermal_remove, - .driver = { - .name = "int3400 thermal", - .acpi_match_table = ACPI_PTR(int3400_thermal_match), - }, -}; - -module_platform_driver(int3400_thermal_driver); - -MODULE_DESCRIPTION("INT3400 Thermal driver"); -MODULE_AUTHOR("Zhang Rui "); -MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/int340x_thermal/int3402_thermal.c b/drivers/thermal/int340x_thermal/int3402_thermal.c deleted file mode 100644 index 8e90b3151a42..000000000000 --- a/drivers/thermal/int340x_thermal/int3402_thermal.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * INT3402 thermal driver for memory temperature reporting - * - * Copyright (C) 2014, Intel Corporation - * Authors: Aaron Lu - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include "int340x_thermal_zone.h" - -#define INT3402_PERF_CHANGED_EVENT 0x80 -#define INT3402_THERMAL_EVENT 0x90 - -struct int3402_thermal_data { - acpi_handle *handle; - struct int34x_thermal_zone *int340x_zone; -}; - -static void int3402_notify(acpi_handle handle, u32 event, void *data) -{ - struct int3402_thermal_data *priv = data; - - if (!priv) - return; - - switch (event) { - case INT3402_PERF_CHANGED_EVENT: - break; - case INT3402_THERMAL_EVENT: - int340x_thermal_zone_device_update(priv->int340x_zone, - THERMAL_TRIP_VIOLATED); - break; - default: - break; - } -} - -static int int3402_thermal_probe(struct platform_device *pdev) -{ - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - struct int3402_thermal_data *d; - int ret; - - if (!acpi_has_method(adev->handle, "_TMP")) - return -ENODEV; - - d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); - if (!d) - return -ENOMEM; - - d->int340x_zone = int340x_thermal_zone_add(adev, NULL); - if (IS_ERR(d->int340x_zone)) - return PTR_ERR(d->int340x_zone); - - ret = acpi_install_notify_handler(adev->handle, - ACPI_DEVICE_NOTIFY, - int3402_notify, - d); - if (ret) { - int340x_thermal_zone_remove(d->int340x_zone); - return ret; - } - - d->handle = adev->handle; - platform_set_drvdata(pdev, d); - - return 0; -} - -static int int3402_thermal_remove(struct platform_device *pdev) -{ - struct int3402_thermal_data *d = platform_get_drvdata(pdev); - - acpi_remove_notify_handler(d->handle, - ACPI_DEVICE_NOTIFY, int3402_notify); - int340x_thermal_zone_remove(d->int340x_zone); - - return 0; -} - -static const struct acpi_device_id int3402_thermal_match[] = { - {"INT3402", 0}, - {} -}; - -MODULE_DEVICE_TABLE(acpi, int3402_thermal_match); - -static struct platform_driver int3402_thermal_driver = { - .probe = int3402_thermal_probe, - .remove = int3402_thermal_remove, - .driver = { - .name = "int3402 thermal", - .acpi_match_table = int3402_thermal_match, - }, -}; - -module_platform_driver(int3402_thermal_driver); - -MODULE_DESCRIPTION("INT3402 Thermal driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/int340x_thermal/int3403_thermal.c b/drivers/thermal/int340x_thermal/int3403_thermal.c deleted file mode 100644 index 0c19fcd56a0d..000000000000 --- a/drivers/thermal/int340x_thermal/int3403_thermal.c +++ /dev/null @@ -1,311 +0,0 @@ -/* - * ACPI INT3403 thermal driver - * Copyright (c) 2013, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include "int340x_thermal_zone.h" - -#define INT3403_TYPE_SENSOR 0x03 -#define INT3403_TYPE_CHARGER 0x0B -#define INT3403_TYPE_BATTERY 0x0C -#define INT3403_PERF_CHANGED_EVENT 0x80 -#define INT3403_PERF_TRIP_POINT_CHANGED 0x81 -#define INT3403_THERMAL_EVENT 0x90 - -/* Preserved structure for future expandbility */ -struct int3403_sensor { - struct int34x_thermal_zone *int340x_zone; -}; - -struct int3403_performance_state { - u64 performance; - u64 power; - u64 latency; - u64 linear; - u64 control; - u64 raw_performace; - char *raw_unit; - int reserved; -}; - -struct int3403_cdev { - struct thermal_cooling_device *cdev; - unsigned long max_state; -}; - -struct int3403_priv { - struct platform_device *pdev; - struct acpi_device *adev; - unsigned long long type; - void *priv; -}; - -static void int3403_notify(acpi_handle handle, - u32 event, void *data) -{ - struct int3403_priv *priv = data; - struct int3403_sensor *obj; - - if (!priv) - return; - - obj = priv->priv; - if (priv->type != INT3403_TYPE_SENSOR || !obj) - return; - - switch (event) { - case INT3403_PERF_CHANGED_EVENT: - break; - case INT3403_THERMAL_EVENT: - int340x_thermal_zone_device_update(obj->int340x_zone, - THERMAL_TRIP_VIOLATED); - break; - case INT3403_PERF_TRIP_POINT_CHANGED: - int340x_thermal_read_trips(obj->int340x_zone); - int340x_thermal_zone_device_update(obj->int340x_zone, - THERMAL_TRIP_CHANGED); - break; - default: - dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); - break; - } -} - -static int int3403_sensor_add(struct int3403_priv *priv) -{ - int result = 0; - struct int3403_sensor *obj; - - obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); - if (!obj) - return -ENOMEM; - - priv->priv = obj; - - obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); - if (IS_ERR(obj->int340x_zone)) - return PTR_ERR(obj->int340x_zone); - - result = acpi_install_notify_handler(priv->adev->handle, - ACPI_DEVICE_NOTIFY, int3403_notify, - (void *)priv); - if (result) - goto err_free_obj; - - return 0; - - err_free_obj: - int340x_thermal_zone_remove(obj->int340x_zone); - return result; -} - -static int int3403_sensor_remove(struct int3403_priv *priv) -{ - struct int3403_sensor *obj = priv->priv; - - acpi_remove_notify_handler(priv->adev->handle, - ACPI_DEVICE_NOTIFY, int3403_notify); - int340x_thermal_zone_remove(obj->int340x_zone); - - return 0; -} - -/* INT3403 Cooling devices */ -static int int3403_get_max_state(struct thermal_cooling_device *cdev, - unsigned long *state) -{ - struct int3403_priv *priv = cdev->devdata; - struct int3403_cdev *obj = priv->priv; - - *state = obj->max_state; - return 0; -} - -static int int3403_get_cur_state(struct thermal_cooling_device *cdev, - unsigned long *state) -{ - struct int3403_priv *priv = cdev->devdata; - unsigned long long level; - acpi_status status; - - status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); - if (ACPI_SUCCESS(status)) { - *state = level; - return 0; - } else - return -EINVAL; -} - -static int -int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) -{ - struct int3403_priv *priv = cdev->devdata; - acpi_status status; - - status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); - if (ACPI_SUCCESS(status)) - return 0; - else - return -EINVAL; -} - -static const struct thermal_cooling_device_ops int3403_cooling_ops = { - .get_max_state = int3403_get_max_state, - .get_cur_state = int3403_get_cur_state, - .set_cur_state = int3403_set_cur_state, -}; - -static int int3403_cdev_add(struct int3403_priv *priv) -{ - int result = 0; - acpi_status status; - struct int3403_cdev *obj; - struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *p; - - obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); - if (!obj) - return -ENOMEM; - - status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); - if (ACPI_FAILURE(status)) - return -ENODEV; - - p = buf.pointer; - if (!p || (p->type != ACPI_TYPE_PACKAGE)) { - printk(KERN_WARNING "Invalid PPSS data\n"); - kfree(buf.pointer); - return -EFAULT; - } - - priv->priv = obj; - obj->max_state = p->package.count - 1; - obj->cdev = - thermal_cooling_device_register(acpi_device_bid(priv->adev), - priv, &int3403_cooling_ops); - if (IS_ERR(obj->cdev)) - result = PTR_ERR(obj->cdev); - - kfree(buf.pointer); - /* TODO: add ACPI notification support */ - - return result; -} - -static int int3403_cdev_remove(struct int3403_priv *priv) -{ - struct int3403_cdev *obj = priv->priv; - - thermal_cooling_device_unregister(obj->cdev); - return 0; -} - -static int int3403_add(struct platform_device *pdev) -{ - struct int3403_priv *priv; - int result = 0; - acpi_status status; - - priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), - GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->pdev = pdev; - priv->adev = ACPI_COMPANION(&(pdev->dev)); - if (!priv->adev) { - result = -EINVAL; - goto err; - } - - status = acpi_evaluate_integer(priv->adev->handle, "PTYP", - NULL, &priv->type); - if (ACPI_FAILURE(status)) { - unsigned long long tmp; - - status = acpi_evaluate_integer(priv->adev->handle, "_TMP", - NULL, &tmp); - if (ACPI_FAILURE(status)) { - result = -EINVAL; - goto err; - } else { - priv->type = INT3403_TYPE_SENSOR; - } - } - - platform_set_drvdata(pdev, priv); - switch (priv->type) { - case INT3403_TYPE_SENSOR: - result = int3403_sensor_add(priv); - break; - case INT3403_TYPE_CHARGER: - case INT3403_TYPE_BATTERY: - result = int3403_cdev_add(priv); - break; - default: - result = -EINVAL; - } - - if (result) - goto err; - return result; - -err: - return result; -} - -static int int3403_remove(struct platform_device *pdev) -{ - struct int3403_priv *priv = platform_get_drvdata(pdev); - - switch (priv->type) { - case INT3403_TYPE_SENSOR: - int3403_sensor_remove(priv); - break; - case INT3403_TYPE_CHARGER: - case INT3403_TYPE_BATTERY: - int3403_cdev_remove(priv); - break; - default: - break; - } - - return 0; -} - -static const struct acpi_device_id int3403_device_ids[] = { - {"INT3403", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, int3403_device_ids); - -static struct platform_driver int3403_driver = { - .probe = int3403_add, - .remove = int3403_remove, - .driver = { - .name = "int3403 thermal", - .acpi_match_table = int3403_device_ids, - }, -}; - -module_platform_driver(int3403_driver); - -MODULE_AUTHOR("Srinivas Pandruvada "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); diff --git a/drivers/thermal/int340x_thermal/int3406_thermal.c b/drivers/thermal/int340x_thermal/int3406_thermal.c deleted file mode 100644 index f69ab026ba24..000000000000 --- a/drivers/thermal/int340x_thermal/int3406_thermal.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * INT3406 thermal driver for display participant device - * - * Copyright (C) 2016, Intel Corporation - * Authors: Aaron Lu - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include - -#define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 - -struct int3406_thermal_data { - int upper_limit; - int lower_limit; - acpi_handle handle; - struct acpi_video_device_brightness *br; - struct backlight_device *raw_bd; - struct thermal_cooling_device *cooling_dev; -}; - -/* - * According to the ACPI spec, - * "Each brightness level is represented by a number between 0 and 100, - * and can be thought of as a percentage. For example, 50 can be 50% - * power consumption or 50% brightness, as defined by the OEM." - * - * As int3406 device uses this value to communicate with the native - * graphics driver, we make the assumption that it represents - * the percentage of brightness only - */ -#define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100) -#define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness) - -static int -int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, - unsigned long *state) -{ - struct int3406_thermal_data *d = cooling_dev->devdata; - - *state = d->upper_limit - d->lower_limit; - return 0; -} - -static int -int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, - unsigned long state) -{ - struct int3406_thermal_data *d = cooling_dev->devdata; - int acpi_level, raw_level; - - if (state > d->upper_limit - d->lower_limit) - return -EINVAL; - - acpi_level = d->br->levels[d->upper_limit - state]; - - raw_level = ACPI_TO_RAW(acpi_level, d); - - return backlight_device_set_brightness(d->raw_bd, raw_level); -} - -static int -int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, - unsigned long *state) -{ - struct int3406_thermal_data *d = cooling_dev->devdata; - int acpi_level; - int index; - - acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d); - - /* - * There is no 1:1 mapping between the firmware interface level - * with the raw interface level, we will have to find one that is - * right above it. - */ - for (index = d->lower_limit; index < d->upper_limit; index++) { - if (acpi_level <= d->br->levels[index]) - break; - } - - *state = d->upper_limit - index; - return 0; -} - -static const struct thermal_cooling_device_ops video_cooling_ops = { - .get_max_state = int3406_thermal_get_max_state, - .get_cur_state = int3406_thermal_get_cur_state, - .set_cur_state = int3406_thermal_set_cur_state, -}; - -static int int3406_thermal_get_index(int *array, int nr, int value) -{ - int i; - - for (i = 2; i < nr; i++) { - if (array[i] == value) - break; - } - return i == nr ? -ENOENT : i; -} - -static void int3406_thermal_get_limit(struct int3406_thermal_data *d) -{ - acpi_status status; - unsigned long long lower_limit, upper_limit; - - status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit); - if (ACPI_SUCCESS(status)) - d->lower_limit = int3406_thermal_get_index(d->br->levels, - d->br->count, lower_limit); - - status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit); - if (ACPI_SUCCESS(status)) - d->upper_limit = int3406_thermal_get_index(d->br->levels, - d->br->count, upper_limit); - - /* lower_limit and upper_limit should be always set */ - d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2; - d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1; -} - -static void int3406_notify(acpi_handle handle, u32 event, void *data) -{ - if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) - int3406_thermal_get_limit(data); -} - -static int int3406_thermal_probe(struct platform_device *pdev) -{ - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - struct int3406_thermal_data *d; - struct backlight_device *bd; - int ret; - - if (!ACPI_HANDLE(&pdev->dev)) - return -ENODEV; - - d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); - if (!d) - return -ENOMEM; - d->handle = ACPI_HANDLE(&pdev->dev); - - bd = backlight_device_get_by_type(BACKLIGHT_RAW); - if (!bd) - return -ENODEV; - d->raw_bd = bd; - - ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL); - if (ret) - return ret; - - int3406_thermal_get_limit(d); - - d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), - d, &video_cooling_ops); - if (IS_ERR(d->cooling_dev)) - goto err; - - ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, - int3406_notify, d); - if (ret) - goto err_cdev; - - platform_set_drvdata(pdev, d); - - return 0; - -err_cdev: - thermal_cooling_device_unregister(d->cooling_dev); -err: - kfree(d->br); - return -ENODEV; -} - -static int int3406_thermal_remove(struct platform_device *pdev) -{ - struct int3406_thermal_data *d = platform_get_drvdata(pdev); - - thermal_cooling_device_unregister(d->cooling_dev); - kfree(d->br); - return 0; -} - -static const struct acpi_device_id int3406_thermal_match[] = { - {"INT3406", 0}, - {} -}; - -MODULE_DEVICE_TABLE(acpi, int3406_thermal_match); - -static struct platform_driver int3406_thermal_driver = { - .probe = int3406_thermal_probe, - .remove = int3406_thermal_remove, - .driver = { - .name = "int3406 thermal", - .acpi_match_table = int3406_thermal_match, - }, -}; - -module_platform_driver(int3406_thermal_driver); - -MODULE_DESCRIPTION("INT3406 Thermal driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/int340x_thermal/int340x_thermal_zone.c deleted file mode 100644 index 9ec27ac1856b..000000000000 --- a/drivers/thermal/int340x_thermal/int340x_thermal_zone.c +++ /dev/null @@ -1,295 +0,0 @@ -/* - * int340x_thermal_zone.c - * Copyright (c) 2015, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ -#include -#include -#include -#include -#include -#include "int340x_thermal_zone.h" - -static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone, - int *temp) -{ - struct int34x_thermal_zone *d = zone->devdata; - unsigned long long tmp; - acpi_status status; - - if (d->override_ops && d->override_ops->get_temp) - return d->override_ops->get_temp(zone, temp); - - status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp); - if (ACPI_FAILURE(status)) - return -EIO; - - if (d->lpat_table) { - int conv_temp; - - conv_temp = acpi_lpat_raw_to_temp(d->lpat_table, (int)tmp); - if (conv_temp < 0) - return conv_temp; - - *temp = (unsigned long)conv_temp * 10; - } else - /* _TMP returns the temperature in tenths of degrees Kelvin */ - *temp = DECI_KELVIN_TO_MILLICELSIUS(tmp); - - return 0; -} - -static int int340x_thermal_get_trip_temp(struct thermal_zone_device *zone, - int trip, int *temp) -{ - struct int34x_thermal_zone *d = zone->devdata; - int i; - - if (d->override_ops && d->override_ops->get_trip_temp) - return d->override_ops->get_trip_temp(zone, trip, temp); - - if (trip < d->aux_trip_nr) - *temp = d->aux_trips[trip]; - else if (trip == d->crt_trip_id) - *temp = d->crt_temp; - else if (trip == d->psv_trip_id) - *temp = d->psv_temp; - else if (trip == d->hot_trip_id) - *temp = d->hot_temp; - else { - for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { - if (d->act_trips[i].valid && - d->act_trips[i].id == trip) { - *temp = d->act_trips[i].temp; - break; - } - } - if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT) - return -EINVAL; - } - - return 0; -} - -static int int340x_thermal_get_trip_type(struct thermal_zone_device *zone, - int trip, - enum thermal_trip_type *type) -{ - struct int34x_thermal_zone *d = zone->devdata; - int i; - - if (d->override_ops && d->override_ops->get_trip_type) - return d->override_ops->get_trip_type(zone, trip, type); - - if (trip < d->aux_trip_nr) - *type = THERMAL_TRIP_PASSIVE; - else if (trip == d->crt_trip_id) - *type = THERMAL_TRIP_CRITICAL; - else if (trip == d->hot_trip_id) - *type = THERMAL_TRIP_HOT; - else if (trip == d->psv_trip_id) - *type = THERMAL_TRIP_PASSIVE; - else { - for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { - if (d->act_trips[i].valid && - d->act_trips[i].id == trip) { - *type = THERMAL_TRIP_ACTIVE; - break; - } - } - if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT) - return -EINVAL; - } - - return 0; -} - -static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone, - int trip, int temp) -{ - struct int34x_thermal_zone *d = zone->devdata; - acpi_status status; - char name[10]; - - if (d->override_ops && d->override_ops->set_trip_temp) - return d->override_ops->set_trip_temp(zone, trip, temp); - - snprintf(name, sizeof(name), "PAT%d", trip); - status = acpi_execute_simple_method(d->adev->handle, name, - MILLICELSIUS_TO_DECI_KELVIN(temp)); - if (ACPI_FAILURE(status)) - return -EIO; - - d->aux_trips[trip] = temp; - - return 0; -} - - -static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone, - int trip, int *temp) -{ - struct int34x_thermal_zone *d = zone->devdata; - acpi_status status; - unsigned long long hyst; - - if (d->override_ops && d->override_ops->get_trip_hyst) - return d->override_ops->get_trip_hyst(zone, trip, temp); - - status = acpi_evaluate_integer(d->adev->handle, "GTSH", NULL, &hyst); - if (ACPI_FAILURE(status)) - *temp = 0; - else - *temp = hyst * 100; - - return 0; -} - -static struct thermal_zone_device_ops int340x_thermal_zone_ops = { - .get_temp = int340x_thermal_get_zone_temp, - .get_trip_temp = int340x_thermal_get_trip_temp, - .get_trip_type = int340x_thermal_get_trip_type, - .set_trip_temp = int340x_thermal_set_trip_temp, - .get_trip_hyst = int340x_thermal_get_trip_hyst, -}; - -static int int340x_thermal_get_trip_config(acpi_handle handle, char *name, - int *temp) -{ - unsigned long long r; - acpi_status status; - - status = acpi_evaluate_integer(handle, name, NULL, &r); - if (ACPI_FAILURE(status)) - return -EIO; - - *temp = DECI_KELVIN_TO_MILLICELSIUS(r); - - return 0; -} - -int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone) -{ - int trip_cnt = int34x_zone->aux_trip_nr; - int i; - - int34x_zone->crt_trip_id = -1; - if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_CRT", - &int34x_zone->crt_temp)) - int34x_zone->crt_trip_id = trip_cnt++; - - int34x_zone->hot_trip_id = -1; - if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_HOT", - &int34x_zone->hot_temp)) - int34x_zone->hot_trip_id = trip_cnt++; - - int34x_zone->psv_trip_id = -1; - if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_PSV", - &int34x_zone->psv_temp)) - int34x_zone->psv_trip_id = trip_cnt++; - - for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { - char name[5] = { '_', 'A', 'C', '0' + i, '\0' }; - - if (int340x_thermal_get_trip_config(int34x_zone->adev->handle, - name, - &int34x_zone->act_trips[i].temp)) - break; - - int34x_zone->act_trips[i].id = trip_cnt++; - int34x_zone->act_trips[i].valid = true; - } - - return trip_cnt; -} -EXPORT_SYMBOL_GPL(int340x_thermal_read_trips); - -static struct thermal_zone_params int340x_thermal_params = { - .governor_name = "user_space", - .no_hwmon = true, -}; - -struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, - struct thermal_zone_device_ops *override_ops) -{ - struct int34x_thermal_zone *int34x_thermal_zone; - acpi_status status; - unsigned long long trip_cnt; - int trip_mask = 0; - int ret; - - int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone), - GFP_KERNEL); - if (!int34x_thermal_zone) - return ERR_PTR(-ENOMEM); - - int34x_thermal_zone->adev = adev; - int34x_thermal_zone->override_ops = override_ops; - - status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt); - if (ACPI_FAILURE(status)) - trip_cnt = 0; - else { - int34x_thermal_zone->aux_trips = - kcalloc(trip_cnt, - sizeof(*int34x_thermal_zone->aux_trips), - GFP_KERNEL); - if (!int34x_thermal_zone->aux_trips) { - ret = -ENOMEM; - goto err_trip_alloc; - } - trip_mask = BIT(trip_cnt) - 1; - int34x_thermal_zone->aux_trip_nr = trip_cnt; - } - - trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone); - - int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table( - adev->handle); - - int34x_thermal_zone->zone = thermal_zone_device_register( - acpi_device_bid(adev), - trip_cnt, - trip_mask, int34x_thermal_zone, - &int340x_thermal_zone_ops, - &int340x_thermal_params, - 0, 0); - if (IS_ERR(int34x_thermal_zone->zone)) { - ret = PTR_ERR(int34x_thermal_zone->zone); - goto err_thermal_zone; - } - - return int34x_thermal_zone; - -err_thermal_zone: - acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table); - kfree(int34x_thermal_zone->aux_trips); -err_trip_alloc: - kfree(int34x_thermal_zone); - return ERR_PTR(ret); -} -EXPORT_SYMBOL_GPL(int340x_thermal_zone_add); - -void int340x_thermal_zone_remove(struct int34x_thermal_zone - *int34x_thermal_zone) -{ - thermal_zone_device_unregister(int34x_thermal_zone->zone); - acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table); - kfree(int34x_thermal_zone->aux_trips); - kfree(int34x_thermal_zone); -} -EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove); - -MODULE_AUTHOR("Aaron Lu "); -MODULE_AUTHOR("Srinivas Pandruvada "); -MODULE_DESCRIPTION("Intel INT340x common thermal zone handler"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/int340x_thermal/int340x_thermal_zone.h b/drivers/thermal/int340x_thermal/int340x_thermal_zone.h deleted file mode 100644 index 5f3ba4775c5c..000000000000 --- a/drivers/thermal/int340x_thermal/int340x_thermal_zone.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * int340x_thermal_zone.h - * Copyright (c) 2015, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ - -#ifndef __INT340X_THERMAL_ZONE_H__ -#define __INT340X_THERMAL_ZONE_H__ - -#include - -#define INT340X_THERMAL_MAX_ACT_TRIP_COUNT 10 - -struct active_trip { - int temp; - int id; - bool valid; -}; - -struct int34x_thermal_zone { - struct acpi_device *adev; - struct active_trip act_trips[INT340X_THERMAL_MAX_ACT_TRIP_COUNT]; - unsigned long *aux_trips; - int aux_trip_nr; - int psv_temp; - int psv_trip_id; - int crt_temp; - int crt_trip_id; - int hot_temp; - int hot_trip_id; - struct thermal_zone_device *zone; - struct thermal_zone_device_ops *override_ops; - void *priv_data; - struct acpi_lpat_conversion_table *lpat_table; -}; - -struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *, - struct thermal_zone_device_ops *override_ops); -void int340x_thermal_zone_remove(struct int34x_thermal_zone *); -int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone); - -static inline void int340x_thermal_zone_set_priv_data( - struct int34x_thermal_zone *tzone, void *priv_data) -{ - tzone->priv_data = priv_data; -} - -static inline void *int340x_thermal_zone_get_priv_data( - struct int34x_thermal_zone *tzone) -{ - return tzone->priv_data; -} - -static inline void int340x_thermal_zone_device_update( - struct int34x_thermal_zone *tzone, - enum thermal_notify_event event) -{ - thermal_zone_device_update(tzone->zone, event); -} - -#endif diff --git a/drivers/thermal/int340x_thermal/processor_thermal_device.c b/drivers/thermal/int340x_thermal/processor_thermal_device.c deleted file mode 100644 index 284cf2c5a8fd..000000000000 --- a/drivers/thermal/int340x_thermal/processor_thermal_device.c +++ /dev/null @@ -1,525 +0,0 @@ -/* - * processor_thermal_device.c - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include "int340x_thermal_zone.h" -#include "../intel_soc_dts_iosf.h" - -/* Broadwell-U/HSB thermal reporting device */ -#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603 -#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03 - -/* Skylake thermal reporting device */ -#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903 - -/* CannonLake thermal reporting device */ -#define PCI_DEVICE_ID_PROC_CNL_THERMAL 0x5a03 -#define PCI_DEVICE_ID_PROC_CFL_THERMAL 0x3E83 - -/* Braswell thermal reporting device */ -#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC - -/* Broxton thermal reporting device */ -#define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C -#define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C -#define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C -#define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C - -/* GeminiLake thermal reporting device */ -#define PCI_DEVICE_ID_PROC_GLK_THERMAL 0x318C - -struct power_config { - u32 index; - u32 min_uw; - u32 max_uw; - u32 tmin_us; - u32 tmax_us; - u32 step_uw; -}; - -struct proc_thermal_device { - struct device *dev; - struct acpi_device *adev; - struct power_config power_limits[2]; - struct int34x_thermal_zone *int340x_zone; - struct intel_soc_dts_sensors *soc_dts; -}; - -enum proc_thermal_emum_mode_type { - PROC_THERMAL_NONE, - PROC_THERMAL_PCI, - PROC_THERMAL_PLATFORM_DEV -}; - -/* - * We can have only one type of enumeration, PCI or Platform, - * not both. So we don't need instance specific data. - */ -static enum proc_thermal_emum_mode_type proc_thermal_emum_mode = - PROC_THERMAL_NONE; - -#define POWER_LIMIT_SHOW(index, suffix) \ -static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ -{ \ - struct pci_dev *pci_dev; \ - struct platform_device *pdev; \ - struct proc_thermal_device *proc_dev; \ -\ - if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { \ - pdev = to_platform_device(dev); \ - proc_dev = platform_get_drvdata(pdev); \ - } else { \ - pci_dev = to_pci_dev(dev); \ - proc_dev = pci_get_drvdata(pci_dev); \ - } \ - return sprintf(buf, "%lu\n",\ - (unsigned long)proc_dev->power_limits[index].suffix * 1000); \ -} - -POWER_LIMIT_SHOW(0, min_uw) -POWER_LIMIT_SHOW(0, max_uw) -POWER_LIMIT_SHOW(0, step_uw) -POWER_LIMIT_SHOW(0, tmin_us) -POWER_LIMIT_SHOW(0, tmax_us) - -POWER_LIMIT_SHOW(1, min_uw) -POWER_LIMIT_SHOW(1, max_uw) -POWER_LIMIT_SHOW(1, step_uw) -POWER_LIMIT_SHOW(1, tmin_us) -POWER_LIMIT_SHOW(1, tmax_us) - -static DEVICE_ATTR_RO(power_limit_0_min_uw); -static DEVICE_ATTR_RO(power_limit_0_max_uw); -static DEVICE_ATTR_RO(power_limit_0_step_uw); -static DEVICE_ATTR_RO(power_limit_0_tmin_us); -static DEVICE_ATTR_RO(power_limit_0_tmax_us); - -static DEVICE_ATTR_RO(power_limit_1_min_uw); -static DEVICE_ATTR_RO(power_limit_1_max_uw); -static DEVICE_ATTR_RO(power_limit_1_step_uw); -static DEVICE_ATTR_RO(power_limit_1_tmin_us); -static DEVICE_ATTR_RO(power_limit_1_tmax_us); - -static struct attribute *power_limit_attrs[] = { - &dev_attr_power_limit_0_min_uw.attr, - &dev_attr_power_limit_1_min_uw.attr, - &dev_attr_power_limit_0_max_uw.attr, - &dev_attr_power_limit_1_max_uw.attr, - &dev_attr_power_limit_0_step_uw.attr, - &dev_attr_power_limit_1_step_uw.attr, - &dev_attr_power_limit_0_tmin_us.attr, - &dev_attr_power_limit_1_tmin_us.attr, - &dev_attr_power_limit_0_tmax_us.attr, - &dev_attr_power_limit_1_tmax_us.attr, - NULL -}; - -static const struct attribute_group power_limit_attribute_group = { - .attrs = power_limit_attrs, - .name = "power_limits" -}; - -static int stored_tjmax; /* since it is fixed, we can have local storage */ - -static int get_tjmax(void) -{ - u32 eax, edx; - u32 val; - int err; - - err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); - if (err) - return err; - - val = (eax >> 16) & 0xff; - if (val) - return val; - - return -EINVAL; -} - -static int read_temp_msr(int *temp) -{ - int cpu; - u32 eax, edx; - int err; - unsigned long curr_temp_off = 0; - - *temp = 0; - - for_each_online_cpu(cpu) { - err = rdmsr_safe_on_cpu(cpu, MSR_IA32_THERM_STATUS, &eax, - &edx); - if (err) - goto err_ret; - else { - if (eax & 0x80000000) { - curr_temp_off = (eax >> 16) & 0x7f; - if (!*temp || curr_temp_off < *temp) - *temp = curr_temp_off; - } else { - err = -EINVAL; - goto err_ret; - } - } - } - - return 0; -err_ret: - return err; -} - -static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone, - int *temp) -{ - int ret; - - ret = read_temp_msr(temp); - if (!ret) - *temp = (stored_tjmax - *temp) * 1000; - - return ret; -} - -static struct thermal_zone_device_ops proc_thermal_local_ops = { - .get_temp = proc_thermal_get_zone_temp, -}; - -static int proc_thermal_read_ppcc(struct proc_thermal_device *proc_priv) -{ - int i; - acpi_status status; - struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *elements, *ppcc; - union acpi_object *p; - int ret = 0; - - status = acpi_evaluate_object(proc_priv->adev->handle, "PPCC", - NULL, &buf); - if (ACPI_FAILURE(status)) - return -ENODEV; - - p = buf.pointer; - if (!p || (p->type != ACPI_TYPE_PACKAGE)) { - dev_err(proc_priv->dev, "Invalid PPCC data\n"); - ret = -EFAULT; - goto free_buffer; - } - - if (!p->package.count) { - dev_err(proc_priv->dev, "Invalid PPCC package size\n"); - ret = -EFAULT; - goto free_buffer; - } - - for (i = 0; i < min((int)p->package.count - 1, 2); ++i) { - elements = &(p->package.elements[i+1]); - if (elements->type != ACPI_TYPE_PACKAGE || - elements->package.count != 6) { - ret = -EFAULT; - goto free_buffer; - } - ppcc = elements->package.elements; - proc_priv->power_limits[i].index = ppcc[0].integer.value; - proc_priv->power_limits[i].min_uw = ppcc[1].integer.value; - proc_priv->power_limits[i].max_uw = ppcc[2].integer.value; - proc_priv->power_limits[i].tmin_us = ppcc[3].integer.value; - proc_priv->power_limits[i].tmax_us = ppcc[4].integer.value; - proc_priv->power_limits[i].step_uw = ppcc[5].integer.value; - } - -free_buffer: - kfree(buf.pointer); - - return ret; -} - -#define PROC_POWER_CAPABILITY_CHANGED 0x83 -static void proc_thermal_notify(acpi_handle handle, u32 event, void *data) -{ - struct proc_thermal_device *proc_priv = data; - - if (!proc_priv) - return; - - switch (event) { - case PROC_POWER_CAPABILITY_CHANGED: - proc_thermal_read_ppcc(proc_priv); - int340x_thermal_zone_device_update(proc_priv->int340x_zone, - THERMAL_DEVICE_POWER_CAPABILITY_CHANGED); - break; - default: - dev_err(proc_priv->dev, "Unsupported event [0x%x]\n", event); - break; - } -} - - -static int proc_thermal_add(struct device *dev, - struct proc_thermal_device **priv) -{ - struct proc_thermal_device *proc_priv; - struct acpi_device *adev; - acpi_status status; - unsigned long long tmp; - struct thermal_zone_device_ops *ops = NULL; - int ret; - - adev = ACPI_COMPANION(dev); - if (!adev) - return -ENODEV; - - proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL); - if (!proc_priv) - return -ENOMEM; - - proc_priv->dev = dev; - proc_priv->adev = adev; - *priv = proc_priv; - - ret = proc_thermal_read_ppcc(proc_priv); - if (!ret) { - ret = sysfs_create_group(&dev->kobj, - &power_limit_attribute_group); - - } - if (ret) - return ret; - - status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp); - if (ACPI_FAILURE(status)) { - /* there is no _TMP method, add local method */ - stored_tjmax = get_tjmax(); - if (stored_tjmax > 0) - ops = &proc_thermal_local_ops; - } - - proc_priv->int340x_zone = int340x_thermal_zone_add(adev, ops); - if (IS_ERR(proc_priv->int340x_zone)) { - ret = PTR_ERR(proc_priv->int340x_zone); - goto remove_group; - } else - ret = 0; - - ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, - proc_thermal_notify, - (void *)proc_priv); - if (ret) - goto remove_zone; - - return 0; - -remove_zone: - int340x_thermal_zone_remove(proc_priv->int340x_zone); -remove_group: - sysfs_remove_group(&proc_priv->dev->kobj, - &power_limit_attribute_group); - - return ret; -} - -static void proc_thermal_remove(struct proc_thermal_device *proc_priv) -{ - acpi_remove_notify_handler(proc_priv->adev->handle, - ACPI_DEVICE_NOTIFY, proc_thermal_notify); - int340x_thermal_zone_remove(proc_priv->int340x_zone); - sysfs_remove_group(&proc_priv->dev->kobj, - &power_limit_attribute_group); -} - -static int int3401_add(struct platform_device *pdev) -{ - struct proc_thermal_device *proc_priv; - int ret; - - if (proc_thermal_emum_mode == PROC_THERMAL_PCI) { - dev_err(&pdev->dev, "error: enumerated as PCI dev\n"); - return -ENODEV; - } - - ret = proc_thermal_add(&pdev->dev, &proc_priv); - if (ret) - return ret; - - platform_set_drvdata(pdev, proc_priv); - proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV; - - return 0; -} - -static int int3401_remove(struct platform_device *pdev) -{ - proc_thermal_remove(platform_get_drvdata(pdev)); - - return 0; -} - -static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid) -{ - struct proc_thermal_device *proc_priv; - struct pci_dev *pdev = devid; - - proc_priv = pci_get_drvdata(pdev); - - intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts); - - return IRQ_HANDLED; -} - -static int proc_thermal_pci_probe(struct pci_dev *pdev, - const struct pci_device_id *unused) -{ - struct proc_thermal_device *proc_priv; - int ret; - - if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { - dev_err(&pdev->dev, "error: enumerated as platform dev\n"); - return -ENODEV; - } - - ret = pci_enable_device(pdev); - if (ret < 0) { - dev_err(&pdev->dev, "error: could not enable device\n"); - return ret; - } - - ret = proc_thermal_add(&pdev->dev, &proc_priv); - if (ret) { - pci_disable_device(pdev); - return ret; - } - - pci_set_drvdata(pdev, proc_priv); - proc_thermal_emum_mode = PROC_THERMAL_PCI; - - if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) { - /* - * Enumerate additional DTS sensors available via IOSF. - * But we are not treating as a failure condition, if - * there are no aux DTSs enabled or fails. This driver - * already exposes sensors, which can be accessed via - * ACPI/MSR. So we don't want to fail for auxiliary DTSs. - */ - proc_priv->soc_dts = intel_soc_dts_iosf_init( - INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0); - - if (proc_priv->soc_dts && pdev->irq) { - ret = pci_enable_msi(pdev); - if (!ret) { - ret = request_threaded_irq(pdev->irq, NULL, - proc_thermal_pci_msi_irq, - IRQF_ONESHOT, "proc_thermal", - pdev); - if (ret) { - intel_soc_dts_iosf_exit( - proc_priv->soc_dts); - pci_disable_msi(pdev); - proc_priv->soc_dts = NULL; - } - } - } else - dev_err(&pdev->dev, "No auxiliary DTSs enabled\n"); - } - - return 0; -} - -static void proc_thermal_pci_remove(struct pci_dev *pdev) -{ - struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev); - - if (proc_priv->soc_dts) { - intel_soc_dts_iosf_exit(proc_priv->soc_dts); - if (pdev->irq) { - free_irq(pdev->irq, pdev); - pci_disable_msi(pdev); - } - } - proc_thermal_remove(proc_priv); - pci_disable_device(pdev); -} - -static const struct pci_device_id proc_thermal_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT0_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT1_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTX_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CNL_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CFL_THERMAL)}, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)}, - { 0, }, -}; - -MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids); - -static struct pci_driver proc_thermal_pci_driver = { - .name = "proc_thermal", - .probe = proc_thermal_pci_probe, - .remove = proc_thermal_pci_remove, - .id_table = proc_thermal_pci_ids, -}; - -static const struct acpi_device_id int3401_device_ids[] = { - {"INT3401", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, int3401_device_ids); - -static struct platform_driver int3401_driver = { - .probe = int3401_add, - .remove = int3401_remove, - .driver = { - .name = "int3401 thermal", - .acpi_match_table = int3401_device_ids, - }, -}; - -static int __init proc_thermal_init(void) -{ - int ret; - - ret = platform_driver_register(&int3401_driver); - if (ret) - return ret; - - ret = pci_register_driver(&proc_thermal_pci_driver); - - return ret; -} - -static void __exit proc_thermal_exit(void) -{ - platform_driver_unregister(&int3401_driver); - pci_unregister_driver(&proc_thermal_pci_driver); -} - -module_init(proc_thermal_init); -module_exit(proc_thermal_exit); - -MODULE_AUTHOR("Srinivas Pandruvada "); -MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig new file mode 100644 index 000000000000..9c06d4ad7c97 --- /dev/null +++ b/drivers/thermal/intel/Kconfig @@ -0,0 +1,77 @@ +config INTEL_POWERCLAMP + tristate "Intel PowerClamp idle injection driver" + depends on THERMAL + depends on X86 + depends on CPU_SUP_INTEL + help + Enable this to enable Intel PowerClamp idle injection driver. This + enforce idle time which results in more package C-state residency. The + user interface is exposed via generic thermal framework. + +config X86_PKG_TEMP_THERMAL + tristate "X86 package temperature thermal driver" + depends on X86_THERMAL_VECTOR + select THERMAL_GOV_USER_SPACE + select THERMAL_WRITABLE_TRIPS + default m + help + Enable this to register CPU digital sensor for package temperature as + thermal zone. Each package will have its own thermal zone. There are + two trip points which can be set by user to get notifications via thermal + notification methods. + +config INTEL_SOC_DTS_IOSF_CORE + tristate + depends on X86 && PCI + select IOSF_MBI + help + This is becoming a common feature for Intel SoCs to expose the additional + digital temperature sensors (DTSs) using side band interface (IOSF). This + implements the common set of helper functions to register, get temperature + and get/set thresholds on DTSs. + +config INTEL_SOC_DTS_THERMAL + tristate "Intel SoCs DTS thermal driver" + depends on X86 && PCI && ACPI + select INTEL_SOC_DTS_IOSF_CORE + select THERMAL_WRITABLE_TRIPS + help + Enable this to register Intel SoCs (e.g. Bay Trail) platform digital + temperature sensor (DTS). These SoCs have two additional DTSs in + addition to DTSs on CPU cores. Each DTS will be registered as a + thermal zone. There are two trip points. One of the trip point can + be set by user mode programs to get notifications via Linux thermal + notification methods.The other trip is a critical trip point, which + was set by the driver based on the TJ MAX temperature. + +config INTEL_QUARK_DTS_THERMAL + tristate "Intel Quark DTS thermal driver" + depends on X86_INTEL_QUARK + help + Enable this to register Intel Quark SoC (e.g. X1000) platform digital + temperature sensor (DTS). For X1000 SoC, it has one on-die DTS. + The DTS will be registered as a thermal zone. There are two trip points: + hot & critical. The critical trip point default value is set by + underlying BIOS/Firmware. + +menu "ACPI INT340X thermal drivers" +source drivers/thermal/intel/int340x_thermal/Kconfig +endmenu + +config INTEL_BXT_PMIC_THERMAL + tristate "Intel Broxton PMIC thermal driver" + depends on X86 && INTEL_SOC_PMIC_BXTWC && REGMAP + help + Select this driver for Intel Broxton PMIC with ADC channels monitoring + system temperature measurements and alerts. + This driver is used for monitoring the ADC channels of PMIC and handles + the alert trip point interrupts and notifies the thermal framework with + the trip point and temperature details of the zone. + +config INTEL_PCH_THERMAL + tristate "Intel PCH Thermal Reporting Driver" + depends on X86 && PCI + help + Enable this to support thermal reporting on certain intel PCHs. + Thermal reporting device will provide temperature reading, + programmable trip points and other information. diff --git a/drivers/thermal/intel/Makefile b/drivers/thermal/intel/Makefile new file mode 100644 index 000000000000..0d9736ced5d4 --- /dev/null +++ b/drivers/thermal/intel/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for various Intel thermal drivers. + +obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o +obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o +obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE) += intel_soc_dts_iosf.o +obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o +obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o +obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ +obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o +obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o diff --git a/drivers/thermal/intel/int340x_thermal/Kconfig b/drivers/thermal/intel/int340x_thermal/Kconfig new file mode 100644 index 000000000000..0582bd12a239 --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/Kconfig @@ -0,0 +1,42 @@ +# +# ACPI INT340x thermal drivers configuration +# + +config INT340X_THERMAL + tristate "ACPI INT340X thermal drivers" + depends on X86 && ACPI + select THERMAL_GOV_USER_SPACE + select ACPI_THERMAL_REL + select ACPI_FAN + select INTEL_SOC_DTS_IOSF_CORE + help + Newer laptops and tablets that use ACPI may have thermal sensors and + other devices with thermal control capabilities outside the core + CPU/SOC, for thermal safety reasons. + They are exposed for the OS to use via the INT3400 ACPI device object + as the master, and INT3401~INT340B ACPI device objects as the slaves. + Enable this to expose the temperature information and cooling ability + from these objects to userspace via the normal thermal framework. + This means that a wide range of applications and GUI widgets can show + the information to the user or use this information for making + decisions. For example, the Intel Thermal Daemon can use this + information to allow the user to select his laptop to run without + turning on the fans. + +config ACPI_THERMAL_REL + tristate + depends on ACPI + +if INT340X_THERMAL + +config INT3406_THERMAL + tristate "ACPI INT3406 display thermal driver" + depends on ACPI_VIDEO + help + The display thermal device represents the LED/LCD display panel + that may or may not include touch support. The main function of + the display thermal device is to allow control of the display + brightness in order to address a thermal condition or to reduce + power consumed by display device. + +endif diff --git a/drivers/thermal/intel/int340x_thermal/Makefile b/drivers/thermal/intel/int340x_thermal/Makefile new file mode 100644 index 000000000000..287eb0a1476d --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o +obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal_zone.o +obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o +obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o +obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o +obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o +obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o diff --git a/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c new file mode 100644 index 000000000000..45e7e5cbdffb --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c @@ -0,0 +1,394 @@ +/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship + * + * Copyright (c) 2014 Intel Corp + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ + +/* + * Two functionalities included: + * 1. Export _TRT, _ART, via misc device interface to the userspace. + * 2. Provide parsing result to kernel drivers + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "acpi_thermal_rel.h" + +static acpi_handle acpi_thermal_rel_handle; +static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock); +static int acpi_thermal_rel_chrdev_count; /* #times opened */ +static int acpi_thermal_rel_chrdev_exclu; /* already open exclusive? */ + +static int acpi_thermal_rel_open(struct inode *inode, struct file *file) +{ + spin_lock(&acpi_thermal_rel_chrdev_lock); + if (acpi_thermal_rel_chrdev_exclu || + (acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) { + spin_unlock(&acpi_thermal_rel_chrdev_lock); + return -EBUSY; + } + + if (file->f_flags & O_EXCL) + acpi_thermal_rel_chrdev_exclu = 1; + acpi_thermal_rel_chrdev_count++; + + spin_unlock(&acpi_thermal_rel_chrdev_lock); + + return nonseekable_open(inode, file); +} + +static int acpi_thermal_rel_release(struct inode *inode, struct file *file) +{ + spin_lock(&acpi_thermal_rel_chrdev_lock); + acpi_thermal_rel_chrdev_count--; + acpi_thermal_rel_chrdev_exclu = 0; + spin_unlock(&acpi_thermal_rel_chrdev_lock); + + return 0; +} + +/** + * acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling + * + * @handle: ACPI handle of the device contains _TRT + * @trt_count: the number of valid entries resulted from parsing _TRT + * @trtp: pointer to pointer of array of _TRT entries in parsing result + * @create_dev: whether to create platform devices for target and source + * + */ +int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp, + bool create_dev) +{ + acpi_status status; + int result = 0; + int i; + int nr_bad_entries = 0; + struct trt *trts; + struct acpi_device *adev; + union acpi_object *p; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer element = { 0, NULL }; + struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" }; + + if (!acpi_has_method(handle, "_TRT")) + return -ENODEV; + + status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buffer.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _TRT data\n"); + result = -EFAULT; + goto end; + } + + *trt_count = p->package.count; + trts = kcalloc(*trt_count, sizeof(struct trt), GFP_KERNEL); + if (!trts) { + result = -ENOMEM; + goto end; + } + + for (i = 0; i < *trt_count; i++) { + struct trt *trt = &trts[i - nr_bad_entries]; + + element.length = sizeof(struct trt); + element.pointer = trt; + + status = acpi_extract_package(&(p->package.elements[i]), + &trt_format, &element); + if (ACPI_FAILURE(status)) { + nr_bad_entries++; + pr_warn("_TRT package %d is invalid, ignored\n", i); + continue; + } + if (!create_dev) + continue; + + result = acpi_bus_get_device(trt->source, &adev); + if (result) + pr_warn("Failed to get source ACPI device\n"); + + result = acpi_bus_get_device(trt->target, &adev); + if (result) + pr_warn("Failed to get target ACPI device\n"); + } + + result = 0; + + *trtp = trts; + /* don't count bad entries */ + *trt_count -= nr_bad_entries; +end: + kfree(buffer.pointer); + return result; +} +EXPORT_SYMBOL(acpi_parse_trt); + +/** + * acpi_parse_art - Parse Active Relationship Table _ART + * + * @handle: ACPI handle of the device contains _ART + * @art_count: the number of valid entries resulted from parsing _ART + * @artp: pointer to pointer of array of art entries in parsing result + * @create_dev: whether to create platform devices for target and source + * + */ +int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp, + bool create_dev) +{ + acpi_status status; + int result = 0; + int i; + int nr_bad_entries = 0; + struct art *arts; + struct acpi_device *adev; + union acpi_object *p; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer element = { 0, NULL }; + struct acpi_buffer art_format = { + sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" }; + + if (!acpi_has_method(handle, "_ART")) + return -ENODEV; + + status = acpi_evaluate_object(handle, "_ART", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buffer.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _ART data\n"); + result = -EFAULT; + goto end; + } + + /* ignore p->package.elements[0], as this is _ART Revision field */ + *art_count = p->package.count - 1; + arts = kcalloc(*art_count, sizeof(struct art), GFP_KERNEL); + if (!arts) { + result = -ENOMEM; + goto end; + } + + for (i = 0; i < *art_count; i++) { + struct art *art = &arts[i - nr_bad_entries]; + + element.length = sizeof(struct art); + element.pointer = art; + + status = acpi_extract_package(&(p->package.elements[i + 1]), + &art_format, &element); + if (ACPI_FAILURE(status)) { + pr_warn("_ART package %d is invalid, ignored", i); + nr_bad_entries++; + continue; + } + if (!create_dev) + continue; + + if (art->source) { + result = acpi_bus_get_device(art->source, &adev); + if (result) + pr_warn("Failed to get source ACPI device\n"); + } + if (art->target) { + result = acpi_bus_get_device(art->target, &adev); + if (result) + pr_warn("Failed to get target ACPI device\n"); + } + } + + *artp = arts; + /* don't count bad entries */ + *art_count -= nr_bad_entries; +end: + kfree(buffer.pointer); + return result; +} +EXPORT_SYMBOL(acpi_parse_art); + + +/* get device name from acpi handle */ +static void get_single_name(acpi_handle handle, char *name) +{ + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER}; + + if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer))) + pr_warn("Failed to get device name from acpi handle\n"); + else { + memcpy(name, buffer.pointer, ACPI_NAME_SIZE); + kfree(buffer.pointer); + } +} + +static int fill_art(char __user *ubuf) +{ + int i; + int ret; + int count; + int art_len; + struct art *arts = NULL; + union art_object *art_user; + + ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false); + if (ret) + goto free_art; + art_len = count * sizeof(union art_object); + art_user = kzalloc(art_len, GFP_KERNEL); + if (!art_user) { + ret = -ENOMEM; + goto free_art; + } + /* now fill in user art data */ + for (i = 0; i < count; i++) { + /* userspace art needs device name instead of acpi reference */ + get_single_name(arts[i].source, art_user[i].source_device); + get_single_name(arts[i].target, art_user[i].target_device); + /* copy the rest int data in addition to source and target */ + memcpy(&art_user[i].weight, &arts[i].weight, + sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2)); + } + + if (copy_to_user(ubuf, art_user, art_len)) + ret = -EFAULT; + kfree(art_user); +free_art: + kfree(arts); + return ret; +} + +static int fill_trt(char __user *ubuf) +{ + int i; + int ret; + int count; + int trt_len; + struct trt *trts = NULL; + union trt_object *trt_user; + + ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false); + if (ret) + goto free_trt; + trt_len = count * sizeof(union trt_object); + trt_user = kzalloc(trt_len, GFP_KERNEL); + if (!trt_user) { + ret = -ENOMEM; + goto free_trt; + } + /* now fill in user trt data */ + for (i = 0; i < count; i++) { + /* userspace trt needs device name instead of acpi reference */ + get_single_name(trts[i].source, trt_user[i].source_device); + get_single_name(trts[i].target, trt_user[i].target_device); + trt_user[i].sample_period = trts[i].sample_period; + trt_user[i].influence = trts[i].influence; + } + + if (copy_to_user(ubuf, trt_user, trt_len)) + ret = -EFAULT; + kfree(trt_user); +free_trt: + kfree(trts); + return ret; +} + +static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd, + unsigned long __arg) +{ + int ret = 0; + unsigned long length = 0; + int count = 0; + char __user *arg = (void __user *)__arg; + struct trt *trts = NULL; + struct art *arts = NULL; + + switch (cmd) { + case ACPI_THERMAL_GET_TRT_COUNT: + ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, + &trts, false); + kfree(trts); + if (!ret) + return put_user(count, (unsigned long __user *)__arg); + return ret; + case ACPI_THERMAL_GET_TRT_LEN: + ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, + &trts, false); + kfree(trts); + length = count * sizeof(union trt_object); + if (!ret) + return put_user(length, (unsigned long __user *)__arg); + return ret; + case ACPI_THERMAL_GET_TRT: + return fill_trt(arg); + case ACPI_THERMAL_GET_ART_COUNT: + ret = acpi_parse_art(acpi_thermal_rel_handle, &count, + &arts, false); + kfree(arts); + if (!ret) + return put_user(count, (unsigned long __user *)__arg); + return ret; + case ACPI_THERMAL_GET_ART_LEN: + ret = acpi_parse_art(acpi_thermal_rel_handle, &count, + &arts, false); + kfree(arts); + length = count * sizeof(union art_object); + if (!ret) + return put_user(length, (unsigned long __user *)__arg); + return ret; + + case ACPI_THERMAL_GET_ART: + return fill_art(arg); + + default: + return -ENOTTY; + } +} + +static const struct file_operations acpi_thermal_rel_fops = { + .owner = THIS_MODULE, + .open = acpi_thermal_rel_open, + .release = acpi_thermal_rel_release, + .unlocked_ioctl = acpi_thermal_rel_ioctl, + .llseek = no_llseek, +}; + +static struct miscdevice acpi_thermal_rel_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + "acpi_thermal_rel", + &acpi_thermal_rel_fops +}; + +int acpi_thermal_rel_misc_device_add(acpi_handle handle) +{ + acpi_thermal_rel_handle = handle; + + return misc_register(&acpi_thermal_rel_misc_device); +} +EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add); + +int acpi_thermal_rel_misc_device_remove(acpi_handle handle) +{ + misc_deregister(&acpi_thermal_rel_misc_device); + + return 0; +} +EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove); + +MODULE_AUTHOR("Zhang Rui "); +MODULE_AUTHOR("Jacob Pan + +#define ACPI_THERMAL_MAGIC 's' + +#define ACPI_THERMAL_GET_TRT_LEN _IOR(ACPI_THERMAL_MAGIC, 1, unsigned long) +#define ACPI_THERMAL_GET_ART_LEN _IOR(ACPI_THERMAL_MAGIC, 2, unsigned long) +#define ACPI_THERMAL_GET_TRT_COUNT _IOR(ACPI_THERMAL_MAGIC, 3, unsigned long) +#define ACPI_THERMAL_GET_ART_COUNT _IOR(ACPI_THERMAL_MAGIC, 4, unsigned long) + +#define ACPI_THERMAL_GET_TRT _IOR(ACPI_THERMAL_MAGIC, 5, unsigned long) +#define ACPI_THERMAL_GET_ART _IOR(ACPI_THERMAL_MAGIC, 6, unsigned long) + +struct art { + acpi_handle source; + acpi_handle target; + u64 weight; + u64 ac0_max; + u64 ac1_max; + u64 ac2_max; + u64 ac3_max; + u64 ac4_max; + u64 ac5_max; + u64 ac6_max; + u64 ac7_max; + u64 ac8_max; + u64 ac9_max; +} __packed; + +struct trt { + acpi_handle source; + acpi_handle target; + u64 influence; + u64 sample_period; + u64 reserved1; + u64 reserved2; + u64 reserved3; + u64 reserved4; +} __packed; + +#define ACPI_NR_ART_ELEMENTS 13 +/* for usrspace */ +union art_object { + struct { + char source_device[8]; /* ACPI single name */ + char target_device[8]; /* ACPI single name */ + u64 weight; + u64 ac0_max_level; + u64 ac1_max_level; + u64 ac2_max_level; + u64 ac3_max_level; + u64 ac4_max_level; + u64 ac5_max_level; + u64 ac6_max_level; + u64 ac7_max_level; + u64 ac8_max_level; + u64 ac9_max_level; + }; + u64 __data[ACPI_NR_ART_ELEMENTS]; +}; + +union trt_object { + struct { + char source_device[8]; /* ACPI single name */ + char target_device[8]; /* ACPI single name */ + u64 influence; + u64 sample_period; + u64 reserved[4]; + }; + u64 __data[8]; +}; + +#ifdef __KERNEL__ +int acpi_thermal_rel_misc_device_add(acpi_handle handle); +int acpi_thermal_rel_misc_device_remove(acpi_handle handle); +int acpi_parse_art(acpi_handle handle, int *art_count, struct art **arts, + bool create_dev); +int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trts, + bool create_dev); +#endif + +#endif /* __ACPI_ACPI_THERMAL_H */ diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c new file mode 100644 index 000000000000..61ca7ce3624e --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -0,0 +1,382 @@ +/* + * INT3400 thermal driver + * + * Copyright (C) 2014, Intel Corporation + * Authors: Zhang Rui + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include "acpi_thermal_rel.h" + +#define INT3400_THERMAL_TABLE_CHANGED 0x83 + +enum int3400_thermal_uuid { + INT3400_THERMAL_PASSIVE_1, + INT3400_THERMAL_ACTIVE, + INT3400_THERMAL_CRITICAL, + INT3400_THERMAL_MAXIMUM_UUID, +}; + +static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { + "42A441D6-AE6A-462b-A84B-4A8CE79027D3", + "3A95C389-E4B8-4629-A526-C52C88626BAE", + "97C68AE7-15FA-499c-B8C9-5DA81D606E0A", +}; + +struct int3400_thermal_priv { + struct acpi_device *adev; + struct thermal_zone_device *thermal; + int mode; + int art_count; + struct art *arts; + int trt_count; + struct trt *trts; + u8 uuid_bitmap; + int rel_misc_dev_res; + int current_uuid_index; +}; + +static ssize_t available_uuids_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct int3400_thermal_priv *priv = dev_get_drvdata(dev); + int i; + int length = 0; + + for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) { + if (priv->uuid_bitmap & (1 << i)) + if (PAGE_SIZE - length > 0) + length += snprintf(&buf[length], + PAGE_SIZE - length, + "%s\n", + int3400_thermal_uuids[i]); + } + + return length; +} + +static ssize_t current_uuid_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct int3400_thermal_priv *priv = dev_get_drvdata(dev); + + if (priv->uuid_bitmap & (1 << priv->current_uuid_index)) + return sprintf(buf, "%s\n", + int3400_thermal_uuids[priv->current_uuid_index]); + else + return sprintf(buf, "INVALID\n"); +} + +static ssize_t current_uuid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct int3400_thermal_priv *priv = dev_get_drvdata(dev); + int i; + + for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) { + if ((priv->uuid_bitmap & (1 << i)) && + !(strncmp(buf, int3400_thermal_uuids[i], + sizeof(int3400_thermal_uuids[i]) - 1))) { + priv->current_uuid_index = i; + return count; + } + } + + return -EINVAL; +} + +static DEVICE_ATTR_RW(current_uuid); +static DEVICE_ATTR_RO(available_uuids); +static struct attribute *uuid_attrs[] = { + &dev_attr_available_uuids.attr, + &dev_attr_current_uuid.attr, + NULL +}; + +static const struct attribute_group uuid_attribute_group = { + .attrs = uuid_attrs, + .name = "uuids" +}; + +static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obja, *objb; + int i, j; + int result = 0; + acpi_status status; + + status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf); + if (ACPI_FAILURE(status)) + return -ENODEV; + + obja = (union acpi_object *)buf.pointer; + if (obja->type != ACPI_TYPE_PACKAGE) { + result = -EINVAL; + goto end; + } + + for (i = 0; i < obja->package.count; i++) { + objb = &obja->package.elements[i]; + if (objb->type != ACPI_TYPE_BUFFER) { + result = -EINVAL; + goto end; + } + + /* UUID must be 16 bytes */ + if (objb->buffer.length != 16) { + result = -EINVAL; + goto end; + } + + for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) { + guid_t guid; + + guid_parse(int3400_thermal_uuids[j], &guid); + if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) { + priv->uuid_bitmap |= (1 << j); + break; + } + } + } + +end: + kfree(buf.pointer); + return result; +} + +static int int3400_thermal_run_osc(acpi_handle handle, + enum int3400_thermal_uuid uuid, bool enable) +{ + u32 ret, buf[2]; + acpi_status status; + int result = 0; + struct acpi_osc_context context = { + .uuid_str = int3400_thermal_uuids[uuid], + .rev = 1, + .cap.length = 8, + }; + + buf[OSC_QUERY_DWORD] = 0; + buf[OSC_SUPPORT_DWORD] = enable; + + context.cap.pointer = buf; + + status = acpi_run_osc(handle, &context); + if (ACPI_SUCCESS(status)) { + ret = *((u32 *)(context.ret.pointer + 4)); + if (ret != enable) + result = -EPERM; + } else + result = -EPERM; + + kfree(context.ret.pointer); + return result; +} + +static void int3400_notify(acpi_handle handle, + u32 event, + void *data) +{ + struct int3400_thermal_priv *priv = data; + char *thermal_prop[5]; + + if (!priv) + return; + + switch (event) { + case INT3400_THERMAL_TABLE_CHANGED: + thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", + priv->thermal->type); + thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", + priv->thermal->temperature); + thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP="); + thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", + THERMAL_TABLE_CHANGED); + thermal_prop[4] = NULL; + kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, + thermal_prop); + break; + default: + /* Ignore unknown notification codes sent to INT3400 device */ + break; + } +} + +static int int3400_thermal_get_temp(struct thermal_zone_device *thermal, + int *temp) +{ + *temp = 20 * 1000; /* faked temp sensor with 20C */ + return 0; +} + +static int int3400_thermal_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct int3400_thermal_priv *priv = thermal->devdata; + + if (!priv) + return -EINVAL; + + *mode = priv->mode; + + return 0; +} + +static int int3400_thermal_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct int3400_thermal_priv *priv = thermal->devdata; + bool enable; + int result = 0; + + if (!priv) + return -EINVAL; + + if (mode == THERMAL_DEVICE_ENABLED) + enable = true; + else if (mode == THERMAL_DEVICE_DISABLED) + enable = false; + else + return -EINVAL; + + if (enable != priv->mode) { + priv->mode = enable; + result = int3400_thermal_run_osc(priv->adev->handle, + priv->current_uuid_index, + enable); + } + return result; +} + +static struct thermal_zone_device_ops int3400_thermal_ops = { + .get_temp = int3400_thermal_get_temp, +}; + +static struct thermal_zone_params int3400_thermal_params = { + .governor_name = "user_space", + .no_hwmon = true, +}; + +static int int3400_thermal_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3400_thermal_priv *priv; + int result; + + if (!adev) + return -ENODEV; + + priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->adev = adev; + + result = int3400_thermal_get_uuids(priv); + if (result) + goto free_priv; + + result = acpi_parse_art(priv->adev->handle, &priv->art_count, + &priv->arts, true); + if (result) + dev_dbg(&pdev->dev, "_ART table parsing error\n"); + + result = acpi_parse_trt(priv->adev->handle, &priv->trt_count, + &priv->trts, true); + if (result) + dev_dbg(&pdev->dev, "_TRT table parsing error\n"); + + platform_set_drvdata(pdev, priv); + + if (priv->uuid_bitmap & 1 << INT3400_THERMAL_PASSIVE_1) { + int3400_thermal_ops.get_mode = int3400_thermal_get_mode; + int3400_thermal_ops.set_mode = int3400_thermal_set_mode; + } + priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, + priv, &int3400_thermal_ops, + &int3400_thermal_params, 0, 0); + if (IS_ERR(priv->thermal)) { + result = PTR_ERR(priv->thermal); + goto free_art_trt; + } + + priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add( + priv->adev->handle); + + result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group); + if (result) + goto free_rel_misc; + + result = acpi_install_notify_handler( + priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify, + (void *)priv); + if (result) + goto free_sysfs; + + return 0; + +free_sysfs: + sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); +free_rel_misc: + if (!priv->rel_misc_dev_res) + acpi_thermal_rel_misc_device_remove(priv->adev->handle); + thermal_zone_device_unregister(priv->thermal); +free_art_trt: + kfree(priv->trts); + kfree(priv->arts); +free_priv: + kfree(priv); + return result; +} + +static int int3400_thermal_remove(struct platform_device *pdev) +{ + struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); + + acpi_remove_notify_handler( + priv->adev->handle, ACPI_DEVICE_NOTIFY, + int3400_notify); + + if (!priv->rel_misc_dev_res) + acpi_thermal_rel_misc_device_remove(priv->adev->handle); + + sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); + thermal_zone_device_unregister(priv->thermal); + kfree(priv->trts); + kfree(priv->arts); + kfree(priv); + return 0; +} + +static const struct acpi_device_id int3400_thermal_match[] = { + {"INT3400", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, int3400_thermal_match); + +static struct platform_driver int3400_thermal_driver = { + .probe = int3400_thermal_probe, + .remove = int3400_thermal_remove, + .driver = { + .name = "int3400 thermal", + .acpi_match_table = ACPI_PTR(int3400_thermal_match), + }, +}; + +module_platform_driver(int3400_thermal_driver); + +MODULE_DESCRIPTION("INT3400 Thermal driver"); +MODULE_AUTHOR("Zhang Rui "); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/intel/int340x_thermal/int3402_thermal.c b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c new file mode 100644 index 000000000000..8e90b3151a42 --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c @@ -0,0 +1,108 @@ +/* + * INT3402 thermal driver for memory temperature reporting + * + * Copyright (C) 2014, Intel Corporation + * Authors: Aaron Lu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include "int340x_thermal_zone.h" + +#define INT3402_PERF_CHANGED_EVENT 0x80 +#define INT3402_THERMAL_EVENT 0x90 + +struct int3402_thermal_data { + acpi_handle *handle; + struct int34x_thermal_zone *int340x_zone; +}; + +static void int3402_notify(acpi_handle handle, u32 event, void *data) +{ + struct int3402_thermal_data *priv = data; + + if (!priv) + return; + + switch (event) { + case INT3402_PERF_CHANGED_EVENT: + break; + case INT3402_THERMAL_EVENT: + int340x_thermal_zone_device_update(priv->int340x_zone, + THERMAL_TRIP_VIOLATED); + break; + default: + break; + } +} + +static int int3402_thermal_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3402_thermal_data *d; + int ret; + + if (!acpi_has_method(adev->handle, "_TMP")) + return -ENODEV; + + d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->int340x_zone = int340x_thermal_zone_add(adev, NULL); + if (IS_ERR(d->int340x_zone)) + return PTR_ERR(d->int340x_zone); + + ret = acpi_install_notify_handler(adev->handle, + ACPI_DEVICE_NOTIFY, + int3402_notify, + d); + if (ret) { + int340x_thermal_zone_remove(d->int340x_zone); + return ret; + } + + d->handle = adev->handle; + platform_set_drvdata(pdev, d); + + return 0; +} + +static int int3402_thermal_remove(struct platform_device *pdev) +{ + struct int3402_thermal_data *d = platform_get_drvdata(pdev); + + acpi_remove_notify_handler(d->handle, + ACPI_DEVICE_NOTIFY, int3402_notify); + int340x_thermal_zone_remove(d->int340x_zone); + + return 0; +} + +static const struct acpi_device_id int3402_thermal_match[] = { + {"INT3402", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, int3402_thermal_match); + +static struct platform_driver int3402_thermal_driver = { + .probe = int3402_thermal_probe, + .remove = int3402_thermal_remove, + .driver = { + .name = "int3402 thermal", + .acpi_match_table = int3402_thermal_match, + }, +}; + +module_platform_driver(int3402_thermal_driver); + +MODULE_DESCRIPTION("INT3402 Thermal driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c new file mode 100644 index 000000000000..0c19fcd56a0d --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c @@ -0,0 +1,311 @@ +/* + * ACPI INT3403 thermal driver + * Copyright (c) 2013, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "int340x_thermal_zone.h" + +#define INT3403_TYPE_SENSOR 0x03 +#define INT3403_TYPE_CHARGER 0x0B +#define INT3403_TYPE_BATTERY 0x0C +#define INT3403_PERF_CHANGED_EVENT 0x80 +#define INT3403_PERF_TRIP_POINT_CHANGED 0x81 +#define INT3403_THERMAL_EVENT 0x90 + +/* Preserved structure for future expandbility */ +struct int3403_sensor { + struct int34x_thermal_zone *int340x_zone; +}; + +struct int3403_performance_state { + u64 performance; + u64 power; + u64 latency; + u64 linear; + u64 control; + u64 raw_performace; + char *raw_unit; + int reserved; +}; + +struct int3403_cdev { + struct thermal_cooling_device *cdev; + unsigned long max_state; +}; + +struct int3403_priv { + struct platform_device *pdev; + struct acpi_device *adev; + unsigned long long type; + void *priv; +}; + +static void int3403_notify(acpi_handle handle, + u32 event, void *data) +{ + struct int3403_priv *priv = data; + struct int3403_sensor *obj; + + if (!priv) + return; + + obj = priv->priv; + if (priv->type != INT3403_TYPE_SENSOR || !obj) + return; + + switch (event) { + case INT3403_PERF_CHANGED_EVENT: + break; + case INT3403_THERMAL_EVENT: + int340x_thermal_zone_device_update(obj->int340x_zone, + THERMAL_TRIP_VIOLATED); + break; + case INT3403_PERF_TRIP_POINT_CHANGED: + int340x_thermal_read_trips(obj->int340x_zone); + int340x_thermal_zone_device_update(obj->int340x_zone, + THERMAL_TRIP_CHANGED); + break; + default: + dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); + break; + } +} + +static int int3403_sensor_add(struct int3403_priv *priv) +{ + int result = 0; + struct int3403_sensor *obj; + + obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); + if (!obj) + return -ENOMEM; + + priv->priv = obj; + + obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); + if (IS_ERR(obj->int340x_zone)) + return PTR_ERR(obj->int340x_zone); + + result = acpi_install_notify_handler(priv->adev->handle, + ACPI_DEVICE_NOTIFY, int3403_notify, + (void *)priv); + if (result) + goto err_free_obj; + + return 0; + + err_free_obj: + int340x_thermal_zone_remove(obj->int340x_zone); + return result; +} + +static int int3403_sensor_remove(struct int3403_priv *priv) +{ + struct int3403_sensor *obj = priv->priv; + + acpi_remove_notify_handler(priv->adev->handle, + ACPI_DEVICE_NOTIFY, int3403_notify); + int340x_thermal_zone_remove(obj->int340x_zone); + + return 0; +} + +/* INT3403 Cooling devices */ +static int int3403_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct int3403_priv *priv = cdev->devdata; + struct int3403_cdev *obj = priv->priv; + + *state = obj->max_state; + return 0; +} + +static int int3403_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct int3403_priv *priv = cdev->devdata; + unsigned long long level; + acpi_status status; + + status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); + if (ACPI_SUCCESS(status)) { + *state = level; + return 0; + } else + return -EINVAL; +} + +static int +int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +{ + struct int3403_priv *priv = cdev->devdata; + acpi_status status; + + status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); + if (ACPI_SUCCESS(status)) + return 0; + else + return -EINVAL; +} + +static const struct thermal_cooling_device_ops int3403_cooling_ops = { + .get_max_state = int3403_get_max_state, + .get_cur_state = int3403_get_cur_state, + .set_cur_state = int3403_set_cur_state, +}; + +static int int3403_cdev_add(struct int3403_priv *priv) +{ + int result = 0; + acpi_status status; + struct int3403_cdev *obj; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *p; + + obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); + if (!obj) + return -ENOMEM; + + status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buf.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + printk(KERN_WARNING "Invalid PPSS data\n"); + kfree(buf.pointer); + return -EFAULT; + } + + priv->priv = obj; + obj->max_state = p->package.count - 1; + obj->cdev = + thermal_cooling_device_register(acpi_device_bid(priv->adev), + priv, &int3403_cooling_ops); + if (IS_ERR(obj->cdev)) + result = PTR_ERR(obj->cdev); + + kfree(buf.pointer); + /* TODO: add ACPI notification support */ + + return result; +} + +static int int3403_cdev_remove(struct int3403_priv *priv) +{ + struct int3403_cdev *obj = priv->priv; + + thermal_cooling_device_unregister(obj->cdev); + return 0; +} + +static int int3403_add(struct platform_device *pdev) +{ + struct int3403_priv *priv; + int result = 0; + acpi_status status; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + priv->adev = ACPI_COMPANION(&(pdev->dev)); + if (!priv->adev) { + result = -EINVAL; + goto err; + } + + status = acpi_evaluate_integer(priv->adev->handle, "PTYP", + NULL, &priv->type); + if (ACPI_FAILURE(status)) { + unsigned long long tmp; + + status = acpi_evaluate_integer(priv->adev->handle, "_TMP", + NULL, &tmp); + if (ACPI_FAILURE(status)) { + result = -EINVAL; + goto err; + } else { + priv->type = INT3403_TYPE_SENSOR; + } + } + + platform_set_drvdata(pdev, priv); + switch (priv->type) { + case INT3403_TYPE_SENSOR: + result = int3403_sensor_add(priv); + break; + case INT3403_TYPE_CHARGER: + case INT3403_TYPE_BATTERY: + result = int3403_cdev_add(priv); + break; + default: + result = -EINVAL; + } + + if (result) + goto err; + return result; + +err: + return result; +} + +static int int3403_remove(struct platform_device *pdev) +{ + struct int3403_priv *priv = platform_get_drvdata(pdev); + + switch (priv->type) { + case INT3403_TYPE_SENSOR: + int3403_sensor_remove(priv); + break; + case INT3403_TYPE_CHARGER: + case INT3403_TYPE_BATTERY: + int3403_cdev_remove(priv); + break; + default: + break; + } + + return 0; +} + +static const struct acpi_device_id int3403_device_ids[] = { + {"INT3403", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, int3403_device_ids); + +static struct platform_driver int3403_driver = { + .probe = int3403_add, + .remove = int3403_remove, + .driver = { + .name = "int3403 thermal", + .acpi_match_table = int3403_device_ids, + }, +}; + +module_platform_driver(int3403_driver); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); diff --git a/drivers/thermal/intel/int340x_thermal/int3406_thermal.c b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c new file mode 100644 index 000000000000..f69ab026ba24 --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c @@ -0,0 +1,213 @@ +/* + * INT3406 thermal driver for display participant device + * + * Copyright (C) 2016, Intel Corporation + * Authors: Aaron Lu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 + +struct int3406_thermal_data { + int upper_limit; + int lower_limit; + acpi_handle handle; + struct acpi_video_device_brightness *br; + struct backlight_device *raw_bd; + struct thermal_cooling_device *cooling_dev; +}; + +/* + * According to the ACPI spec, + * "Each brightness level is represented by a number between 0 and 100, + * and can be thought of as a percentage. For example, 50 can be 50% + * power consumption or 50% brightness, as defined by the OEM." + * + * As int3406 device uses this value to communicate with the native + * graphics driver, we make the assumption that it represents + * the percentage of brightness only + */ +#define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100) +#define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness) + +static int +int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, + unsigned long *state) +{ + struct int3406_thermal_data *d = cooling_dev->devdata; + + *state = d->upper_limit - d->lower_limit; + return 0; +} + +static int +int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, + unsigned long state) +{ + struct int3406_thermal_data *d = cooling_dev->devdata; + int acpi_level, raw_level; + + if (state > d->upper_limit - d->lower_limit) + return -EINVAL; + + acpi_level = d->br->levels[d->upper_limit - state]; + + raw_level = ACPI_TO_RAW(acpi_level, d); + + return backlight_device_set_brightness(d->raw_bd, raw_level); +} + +static int +int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, + unsigned long *state) +{ + struct int3406_thermal_data *d = cooling_dev->devdata; + int acpi_level; + int index; + + acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d); + + /* + * There is no 1:1 mapping between the firmware interface level + * with the raw interface level, we will have to find one that is + * right above it. + */ + for (index = d->lower_limit; index < d->upper_limit; index++) { + if (acpi_level <= d->br->levels[index]) + break; + } + + *state = d->upper_limit - index; + return 0; +} + +static const struct thermal_cooling_device_ops video_cooling_ops = { + .get_max_state = int3406_thermal_get_max_state, + .get_cur_state = int3406_thermal_get_cur_state, + .set_cur_state = int3406_thermal_set_cur_state, +}; + +static int int3406_thermal_get_index(int *array, int nr, int value) +{ + int i; + + for (i = 2; i < nr; i++) { + if (array[i] == value) + break; + } + return i == nr ? -ENOENT : i; +} + +static void int3406_thermal_get_limit(struct int3406_thermal_data *d) +{ + acpi_status status; + unsigned long long lower_limit, upper_limit; + + status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit); + if (ACPI_SUCCESS(status)) + d->lower_limit = int3406_thermal_get_index(d->br->levels, + d->br->count, lower_limit); + + status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit); + if (ACPI_SUCCESS(status)) + d->upper_limit = int3406_thermal_get_index(d->br->levels, + d->br->count, upper_limit); + + /* lower_limit and upper_limit should be always set */ + d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2; + d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1; +} + +static void int3406_notify(acpi_handle handle, u32 event, void *data) +{ + if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) + int3406_thermal_get_limit(data); +} + +static int int3406_thermal_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3406_thermal_data *d; + struct backlight_device *bd; + int ret; + + if (!ACPI_HANDLE(&pdev->dev)) + return -ENODEV; + + d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + d->handle = ACPI_HANDLE(&pdev->dev); + + bd = backlight_device_get_by_type(BACKLIGHT_RAW); + if (!bd) + return -ENODEV; + d->raw_bd = bd; + + ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL); + if (ret) + return ret; + + int3406_thermal_get_limit(d); + + d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), + d, &video_cooling_ops); + if (IS_ERR(d->cooling_dev)) + goto err; + + ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + int3406_notify, d); + if (ret) + goto err_cdev; + + platform_set_drvdata(pdev, d); + + return 0; + +err_cdev: + thermal_cooling_device_unregister(d->cooling_dev); +err: + kfree(d->br); + return -ENODEV; +} + +static int int3406_thermal_remove(struct platform_device *pdev) +{ + struct int3406_thermal_data *d = platform_get_drvdata(pdev); + + thermal_cooling_device_unregister(d->cooling_dev); + kfree(d->br); + return 0; +} + +static const struct acpi_device_id int3406_thermal_match[] = { + {"INT3406", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, int3406_thermal_match); + +static struct platform_driver int3406_thermal_driver = { + .probe = int3406_thermal_probe, + .remove = int3406_thermal_remove, + .driver = { + .name = "int3406 thermal", + .acpi_match_table = int3406_thermal_match, + }, +}; + +module_platform_driver(int3406_thermal_driver); + +MODULE_DESCRIPTION("INT3406 Thermal driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c new file mode 100644 index 000000000000..9ec27ac1856b --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c @@ -0,0 +1,295 @@ +/* + * int340x_thermal_zone.c + * Copyright (c) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#include +#include +#include +#include +#include +#include "int340x_thermal_zone.h" + +static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone, + int *temp) +{ + struct int34x_thermal_zone *d = zone->devdata; + unsigned long long tmp; + acpi_status status; + + if (d->override_ops && d->override_ops->get_temp) + return d->override_ops->get_temp(zone, temp); + + status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp); + if (ACPI_FAILURE(status)) + return -EIO; + + if (d->lpat_table) { + int conv_temp; + + conv_temp = acpi_lpat_raw_to_temp(d->lpat_table, (int)tmp); + if (conv_temp < 0) + return conv_temp; + + *temp = (unsigned long)conv_temp * 10; + } else + /* _TMP returns the temperature in tenths of degrees Kelvin */ + *temp = DECI_KELVIN_TO_MILLICELSIUS(tmp); + + return 0; +} + +static int int340x_thermal_get_trip_temp(struct thermal_zone_device *zone, + int trip, int *temp) +{ + struct int34x_thermal_zone *d = zone->devdata; + int i; + + if (d->override_ops && d->override_ops->get_trip_temp) + return d->override_ops->get_trip_temp(zone, trip, temp); + + if (trip < d->aux_trip_nr) + *temp = d->aux_trips[trip]; + else if (trip == d->crt_trip_id) + *temp = d->crt_temp; + else if (trip == d->psv_trip_id) + *temp = d->psv_temp; + else if (trip == d->hot_trip_id) + *temp = d->hot_temp; + else { + for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { + if (d->act_trips[i].valid && + d->act_trips[i].id == trip) { + *temp = d->act_trips[i].temp; + break; + } + } + if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT) + return -EINVAL; + } + + return 0; +} + +static int int340x_thermal_get_trip_type(struct thermal_zone_device *zone, + int trip, + enum thermal_trip_type *type) +{ + struct int34x_thermal_zone *d = zone->devdata; + int i; + + if (d->override_ops && d->override_ops->get_trip_type) + return d->override_ops->get_trip_type(zone, trip, type); + + if (trip < d->aux_trip_nr) + *type = THERMAL_TRIP_PASSIVE; + else if (trip == d->crt_trip_id) + *type = THERMAL_TRIP_CRITICAL; + else if (trip == d->hot_trip_id) + *type = THERMAL_TRIP_HOT; + else if (trip == d->psv_trip_id) + *type = THERMAL_TRIP_PASSIVE; + else { + for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { + if (d->act_trips[i].valid && + d->act_trips[i].id == trip) { + *type = THERMAL_TRIP_ACTIVE; + break; + } + } + if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT) + return -EINVAL; + } + + return 0; +} + +static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone, + int trip, int temp) +{ + struct int34x_thermal_zone *d = zone->devdata; + acpi_status status; + char name[10]; + + if (d->override_ops && d->override_ops->set_trip_temp) + return d->override_ops->set_trip_temp(zone, trip, temp); + + snprintf(name, sizeof(name), "PAT%d", trip); + status = acpi_execute_simple_method(d->adev->handle, name, + MILLICELSIUS_TO_DECI_KELVIN(temp)); + if (ACPI_FAILURE(status)) + return -EIO; + + d->aux_trips[trip] = temp; + + return 0; +} + + +static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone, + int trip, int *temp) +{ + struct int34x_thermal_zone *d = zone->devdata; + acpi_status status; + unsigned long long hyst; + + if (d->override_ops && d->override_ops->get_trip_hyst) + return d->override_ops->get_trip_hyst(zone, trip, temp); + + status = acpi_evaluate_integer(d->adev->handle, "GTSH", NULL, &hyst); + if (ACPI_FAILURE(status)) + *temp = 0; + else + *temp = hyst * 100; + + return 0; +} + +static struct thermal_zone_device_ops int340x_thermal_zone_ops = { + .get_temp = int340x_thermal_get_zone_temp, + .get_trip_temp = int340x_thermal_get_trip_temp, + .get_trip_type = int340x_thermal_get_trip_type, + .set_trip_temp = int340x_thermal_set_trip_temp, + .get_trip_hyst = int340x_thermal_get_trip_hyst, +}; + +static int int340x_thermal_get_trip_config(acpi_handle handle, char *name, + int *temp) +{ + unsigned long long r; + acpi_status status; + + status = acpi_evaluate_integer(handle, name, NULL, &r); + if (ACPI_FAILURE(status)) + return -EIO; + + *temp = DECI_KELVIN_TO_MILLICELSIUS(r); + + return 0; +} + +int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone) +{ + int trip_cnt = int34x_zone->aux_trip_nr; + int i; + + int34x_zone->crt_trip_id = -1; + if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_CRT", + &int34x_zone->crt_temp)) + int34x_zone->crt_trip_id = trip_cnt++; + + int34x_zone->hot_trip_id = -1; + if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_HOT", + &int34x_zone->hot_temp)) + int34x_zone->hot_trip_id = trip_cnt++; + + int34x_zone->psv_trip_id = -1; + if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_PSV", + &int34x_zone->psv_temp)) + int34x_zone->psv_trip_id = trip_cnt++; + + for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { + char name[5] = { '_', 'A', 'C', '0' + i, '\0' }; + + if (int340x_thermal_get_trip_config(int34x_zone->adev->handle, + name, + &int34x_zone->act_trips[i].temp)) + break; + + int34x_zone->act_trips[i].id = trip_cnt++; + int34x_zone->act_trips[i].valid = true; + } + + return trip_cnt; +} +EXPORT_SYMBOL_GPL(int340x_thermal_read_trips); + +static struct thermal_zone_params int340x_thermal_params = { + .governor_name = "user_space", + .no_hwmon = true, +}; + +struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, + struct thermal_zone_device_ops *override_ops) +{ + struct int34x_thermal_zone *int34x_thermal_zone; + acpi_status status; + unsigned long long trip_cnt; + int trip_mask = 0; + int ret; + + int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone), + GFP_KERNEL); + if (!int34x_thermal_zone) + return ERR_PTR(-ENOMEM); + + int34x_thermal_zone->adev = adev; + int34x_thermal_zone->override_ops = override_ops; + + status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt); + if (ACPI_FAILURE(status)) + trip_cnt = 0; + else { + int34x_thermal_zone->aux_trips = + kcalloc(trip_cnt, + sizeof(*int34x_thermal_zone->aux_trips), + GFP_KERNEL); + if (!int34x_thermal_zone->aux_trips) { + ret = -ENOMEM; + goto err_trip_alloc; + } + trip_mask = BIT(trip_cnt) - 1; + int34x_thermal_zone->aux_trip_nr = trip_cnt; + } + + trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone); + + int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table( + adev->handle); + + int34x_thermal_zone->zone = thermal_zone_device_register( + acpi_device_bid(adev), + trip_cnt, + trip_mask, int34x_thermal_zone, + &int340x_thermal_zone_ops, + &int340x_thermal_params, + 0, 0); + if (IS_ERR(int34x_thermal_zone->zone)) { + ret = PTR_ERR(int34x_thermal_zone->zone); + goto err_thermal_zone; + } + + return int34x_thermal_zone; + +err_thermal_zone: + acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table); + kfree(int34x_thermal_zone->aux_trips); +err_trip_alloc: + kfree(int34x_thermal_zone); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(int340x_thermal_zone_add); + +void int340x_thermal_zone_remove(struct int34x_thermal_zone + *int34x_thermal_zone) +{ + thermal_zone_device_unregister(int34x_thermal_zone->zone); + acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table); + kfree(int34x_thermal_zone->aux_trips); + kfree(int34x_thermal_zone); +} +EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove); + +MODULE_AUTHOR("Aaron Lu "); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_DESCRIPTION("Intel INT340x common thermal zone handler"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h new file mode 100644 index 000000000000..5f3ba4775c5c --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h @@ -0,0 +1,70 @@ +/* + * int340x_thermal_zone.h + * Copyright (c) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef __INT340X_THERMAL_ZONE_H__ +#define __INT340X_THERMAL_ZONE_H__ + +#include + +#define INT340X_THERMAL_MAX_ACT_TRIP_COUNT 10 + +struct active_trip { + int temp; + int id; + bool valid; +}; + +struct int34x_thermal_zone { + struct acpi_device *adev; + struct active_trip act_trips[INT340X_THERMAL_MAX_ACT_TRIP_COUNT]; + unsigned long *aux_trips; + int aux_trip_nr; + int psv_temp; + int psv_trip_id; + int crt_temp; + int crt_trip_id; + int hot_temp; + int hot_trip_id; + struct thermal_zone_device *zone; + struct thermal_zone_device_ops *override_ops; + void *priv_data; + struct acpi_lpat_conversion_table *lpat_table; +}; + +struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *, + struct thermal_zone_device_ops *override_ops); +void int340x_thermal_zone_remove(struct int34x_thermal_zone *); +int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone); + +static inline void int340x_thermal_zone_set_priv_data( + struct int34x_thermal_zone *tzone, void *priv_data) +{ + tzone->priv_data = priv_data; +} + +static inline void *int340x_thermal_zone_get_priv_data( + struct int34x_thermal_zone *tzone) +{ + return tzone->priv_data; +} + +static inline void int340x_thermal_zone_device_update( + struct int34x_thermal_zone *tzone, + enum thermal_notify_event event) +{ + thermal_zone_device_update(tzone->zone, event); +} + +#endif diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c new file mode 100644 index 000000000000..284cf2c5a8fd --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c @@ -0,0 +1,525 @@ +/* + * processor_thermal_device.c + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "int340x_thermal_zone.h" +#include "../intel_soc_dts_iosf.h" + +/* Broadwell-U/HSB thermal reporting device */ +#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603 +#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03 + +/* Skylake thermal reporting device */ +#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903 + +/* CannonLake thermal reporting device */ +#define PCI_DEVICE_ID_PROC_CNL_THERMAL 0x5a03 +#define PCI_DEVICE_ID_PROC_CFL_THERMAL 0x3E83 + +/* Braswell thermal reporting device */ +#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC + +/* Broxton thermal reporting device */ +#define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C +#define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C +#define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C +#define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C + +/* GeminiLake thermal reporting device */ +#define PCI_DEVICE_ID_PROC_GLK_THERMAL 0x318C + +struct power_config { + u32 index; + u32 min_uw; + u32 max_uw; + u32 tmin_us; + u32 tmax_us; + u32 step_uw; +}; + +struct proc_thermal_device { + struct device *dev; + struct acpi_device *adev; + struct power_config power_limits[2]; + struct int34x_thermal_zone *int340x_zone; + struct intel_soc_dts_sensors *soc_dts; +}; + +enum proc_thermal_emum_mode_type { + PROC_THERMAL_NONE, + PROC_THERMAL_PCI, + PROC_THERMAL_PLATFORM_DEV +}; + +/* + * We can have only one type of enumeration, PCI or Platform, + * not both. So we don't need instance specific data. + */ +static enum proc_thermal_emum_mode_type proc_thermal_emum_mode = + PROC_THERMAL_NONE; + +#define POWER_LIMIT_SHOW(index, suffix) \ +static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct pci_dev *pci_dev; \ + struct platform_device *pdev; \ + struct proc_thermal_device *proc_dev; \ +\ + if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { \ + pdev = to_platform_device(dev); \ + proc_dev = platform_get_drvdata(pdev); \ + } else { \ + pci_dev = to_pci_dev(dev); \ + proc_dev = pci_get_drvdata(pci_dev); \ + } \ + return sprintf(buf, "%lu\n",\ + (unsigned long)proc_dev->power_limits[index].suffix * 1000); \ +} + +POWER_LIMIT_SHOW(0, min_uw) +POWER_LIMIT_SHOW(0, max_uw) +POWER_LIMIT_SHOW(0, step_uw) +POWER_LIMIT_SHOW(0, tmin_us) +POWER_LIMIT_SHOW(0, tmax_us) + +POWER_LIMIT_SHOW(1, min_uw) +POWER_LIMIT_SHOW(1, max_uw) +POWER_LIMIT_SHOW(1, step_uw) +POWER_LIMIT_SHOW(1, tmin_us) +POWER_LIMIT_SHOW(1, tmax_us) + +static DEVICE_ATTR_RO(power_limit_0_min_uw); +static DEVICE_ATTR_RO(power_limit_0_max_uw); +static DEVICE_ATTR_RO(power_limit_0_step_uw); +static DEVICE_ATTR_RO(power_limit_0_tmin_us); +static DEVICE_ATTR_RO(power_limit_0_tmax_us); + +static DEVICE_ATTR_RO(power_limit_1_min_uw); +static DEVICE_ATTR_RO(power_limit_1_max_uw); +static DEVICE_ATTR_RO(power_limit_1_step_uw); +static DEVICE_ATTR_RO(power_limit_1_tmin_us); +static DEVICE_ATTR_RO(power_limit_1_tmax_us); + +static struct attribute *power_limit_attrs[] = { + &dev_attr_power_limit_0_min_uw.attr, + &dev_attr_power_limit_1_min_uw.attr, + &dev_attr_power_limit_0_max_uw.attr, + &dev_attr_power_limit_1_max_uw.attr, + &dev_attr_power_limit_0_step_uw.attr, + &dev_attr_power_limit_1_step_uw.attr, + &dev_attr_power_limit_0_tmin_us.attr, + &dev_attr_power_limit_1_tmin_us.attr, + &dev_attr_power_limit_0_tmax_us.attr, + &dev_attr_power_limit_1_tmax_us.attr, + NULL +}; + +static const struct attribute_group power_limit_attribute_group = { + .attrs = power_limit_attrs, + .name = "power_limits" +}; + +static int stored_tjmax; /* since it is fixed, we can have local storage */ + +static int get_tjmax(void) +{ + u32 eax, edx; + u32 val; + int err; + + err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); + if (err) + return err; + + val = (eax >> 16) & 0xff; + if (val) + return val; + + return -EINVAL; +} + +static int read_temp_msr(int *temp) +{ + int cpu; + u32 eax, edx; + int err; + unsigned long curr_temp_off = 0; + + *temp = 0; + + for_each_online_cpu(cpu) { + err = rdmsr_safe_on_cpu(cpu, MSR_IA32_THERM_STATUS, &eax, + &edx); + if (err) + goto err_ret; + else { + if (eax & 0x80000000) { + curr_temp_off = (eax >> 16) & 0x7f; + if (!*temp || curr_temp_off < *temp) + *temp = curr_temp_off; + } else { + err = -EINVAL; + goto err_ret; + } + } + } + + return 0; +err_ret: + return err; +} + +static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone, + int *temp) +{ + int ret; + + ret = read_temp_msr(temp); + if (!ret) + *temp = (stored_tjmax - *temp) * 1000; + + return ret; +} + +static struct thermal_zone_device_ops proc_thermal_local_ops = { + .get_temp = proc_thermal_get_zone_temp, +}; + +static int proc_thermal_read_ppcc(struct proc_thermal_device *proc_priv) +{ + int i; + acpi_status status; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *elements, *ppcc; + union acpi_object *p; + int ret = 0; + + status = acpi_evaluate_object(proc_priv->adev->handle, "PPCC", + NULL, &buf); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buf.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + dev_err(proc_priv->dev, "Invalid PPCC data\n"); + ret = -EFAULT; + goto free_buffer; + } + + if (!p->package.count) { + dev_err(proc_priv->dev, "Invalid PPCC package size\n"); + ret = -EFAULT; + goto free_buffer; + } + + for (i = 0; i < min((int)p->package.count - 1, 2); ++i) { + elements = &(p->package.elements[i+1]); + if (elements->type != ACPI_TYPE_PACKAGE || + elements->package.count != 6) { + ret = -EFAULT; + goto free_buffer; + } + ppcc = elements->package.elements; + proc_priv->power_limits[i].index = ppcc[0].integer.value; + proc_priv->power_limits[i].min_uw = ppcc[1].integer.value; + proc_priv->power_limits[i].max_uw = ppcc[2].integer.value; + proc_priv->power_limits[i].tmin_us = ppcc[3].integer.value; + proc_priv->power_limits[i].tmax_us = ppcc[4].integer.value; + proc_priv->power_limits[i].step_uw = ppcc[5].integer.value; + } + +free_buffer: + kfree(buf.pointer); + + return ret; +} + +#define PROC_POWER_CAPABILITY_CHANGED 0x83 +static void proc_thermal_notify(acpi_handle handle, u32 event, void *data) +{ + struct proc_thermal_device *proc_priv = data; + + if (!proc_priv) + return; + + switch (event) { + case PROC_POWER_CAPABILITY_CHANGED: + proc_thermal_read_ppcc(proc_priv); + int340x_thermal_zone_device_update(proc_priv->int340x_zone, + THERMAL_DEVICE_POWER_CAPABILITY_CHANGED); + break; + default: + dev_err(proc_priv->dev, "Unsupported event [0x%x]\n", event); + break; + } +} + + +static int proc_thermal_add(struct device *dev, + struct proc_thermal_device **priv) +{ + struct proc_thermal_device *proc_priv; + struct acpi_device *adev; + acpi_status status; + unsigned long long tmp; + struct thermal_zone_device_ops *ops = NULL; + int ret; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + + proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL); + if (!proc_priv) + return -ENOMEM; + + proc_priv->dev = dev; + proc_priv->adev = adev; + *priv = proc_priv; + + ret = proc_thermal_read_ppcc(proc_priv); + if (!ret) { + ret = sysfs_create_group(&dev->kobj, + &power_limit_attribute_group); + + } + if (ret) + return ret; + + status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp); + if (ACPI_FAILURE(status)) { + /* there is no _TMP method, add local method */ + stored_tjmax = get_tjmax(); + if (stored_tjmax > 0) + ops = &proc_thermal_local_ops; + } + + proc_priv->int340x_zone = int340x_thermal_zone_add(adev, ops); + if (IS_ERR(proc_priv->int340x_zone)) { + ret = PTR_ERR(proc_priv->int340x_zone); + goto remove_group; + } else + ret = 0; + + ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + proc_thermal_notify, + (void *)proc_priv); + if (ret) + goto remove_zone; + + return 0; + +remove_zone: + int340x_thermal_zone_remove(proc_priv->int340x_zone); +remove_group: + sysfs_remove_group(&proc_priv->dev->kobj, + &power_limit_attribute_group); + + return ret; +} + +static void proc_thermal_remove(struct proc_thermal_device *proc_priv) +{ + acpi_remove_notify_handler(proc_priv->adev->handle, + ACPI_DEVICE_NOTIFY, proc_thermal_notify); + int340x_thermal_zone_remove(proc_priv->int340x_zone); + sysfs_remove_group(&proc_priv->dev->kobj, + &power_limit_attribute_group); +} + +static int int3401_add(struct platform_device *pdev) +{ + struct proc_thermal_device *proc_priv; + int ret; + + if (proc_thermal_emum_mode == PROC_THERMAL_PCI) { + dev_err(&pdev->dev, "error: enumerated as PCI dev\n"); + return -ENODEV; + } + + ret = proc_thermal_add(&pdev->dev, &proc_priv); + if (ret) + return ret; + + platform_set_drvdata(pdev, proc_priv); + proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV; + + return 0; +} + +static int int3401_remove(struct platform_device *pdev) +{ + proc_thermal_remove(platform_get_drvdata(pdev)); + + return 0; +} + +static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid) +{ + struct proc_thermal_device *proc_priv; + struct pci_dev *pdev = devid; + + proc_priv = pci_get_drvdata(pdev); + + intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts); + + return IRQ_HANDLED; +} + +static int proc_thermal_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *unused) +{ + struct proc_thermal_device *proc_priv; + int ret; + + if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { + dev_err(&pdev->dev, "error: enumerated as platform dev\n"); + return -ENODEV; + } + + ret = pci_enable_device(pdev); + if (ret < 0) { + dev_err(&pdev->dev, "error: could not enable device\n"); + return ret; + } + + ret = proc_thermal_add(&pdev->dev, &proc_priv); + if (ret) { + pci_disable_device(pdev); + return ret; + } + + pci_set_drvdata(pdev, proc_priv); + proc_thermal_emum_mode = PROC_THERMAL_PCI; + + if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) { + /* + * Enumerate additional DTS sensors available via IOSF. + * But we are not treating as a failure condition, if + * there are no aux DTSs enabled or fails. This driver + * already exposes sensors, which can be accessed via + * ACPI/MSR. So we don't want to fail for auxiliary DTSs. + */ + proc_priv->soc_dts = intel_soc_dts_iosf_init( + INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0); + + if (proc_priv->soc_dts && pdev->irq) { + ret = pci_enable_msi(pdev); + if (!ret) { + ret = request_threaded_irq(pdev->irq, NULL, + proc_thermal_pci_msi_irq, + IRQF_ONESHOT, "proc_thermal", + pdev); + if (ret) { + intel_soc_dts_iosf_exit( + proc_priv->soc_dts); + pci_disable_msi(pdev); + proc_priv->soc_dts = NULL; + } + } + } else + dev_err(&pdev->dev, "No auxiliary DTSs enabled\n"); + } + + return 0; +} + +static void proc_thermal_pci_remove(struct pci_dev *pdev) +{ + struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev); + + if (proc_priv->soc_dts) { + intel_soc_dts_iosf_exit(proc_priv->soc_dts); + if (pdev->irq) { + free_irq(pdev->irq, pdev); + pci_disable_msi(pdev); + } + } + proc_thermal_remove(proc_priv); + pci_disable_device(pdev); +} + +static const struct pci_device_id proc_thermal_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT0_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT1_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTX_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CNL_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CFL_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)}, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids); + +static struct pci_driver proc_thermal_pci_driver = { + .name = "proc_thermal", + .probe = proc_thermal_pci_probe, + .remove = proc_thermal_pci_remove, + .id_table = proc_thermal_pci_ids, +}; + +static const struct acpi_device_id int3401_device_ids[] = { + {"INT3401", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, int3401_device_ids); + +static struct platform_driver int3401_driver = { + .probe = int3401_add, + .remove = int3401_remove, + .driver = { + .name = "int3401 thermal", + .acpi_match_table = int3401_device_ids, + }, +}; + +static int __init proc_thermal_init(void) +{ + int ret; + + ret = platform_driver_register(&int3401_driver); + if (ret) + return ret; + + ret = pci_register_driver(&proc_thermal_pci_driver); + + return ret; +} + +static void __exit proc_thermal_exit(void) +{ + platform_driver_unregister(&int3401_driver); + pci_unregister_driver(&proc_thermal_pci_driver); +} + +module_init(proc_thermal_init); +module_exit(proc_thermal_exit); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/intel_bxt_pmic_thermal.c b/drivers/thermal/intel/intel_bxt_pmic_thermal.c new file mode 100644 index 000000000000..94cfd0064c43 --- /dev/null +++ b/drivers/thermal/intel/intel_bxt_pmic_thermal.c @@ -0,0 +1,299 @@ +/* + * Intel Broxton PMIC thermal driver + * + * Copyright (C) 2016 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BXTWC_THRM0IRQ 0x4E04 +#define BXTWC_THRM1IRQ 0x4E05 +#define BXTWC_THRM2IRQ 0x4E06 +#define BXTWC_MTHRM0IRQ 0x4E12 +#define BXTWC_MTHRM1IRQ 0x4E13 +#define BXTWC_MTHRM2IRQ 0x4E14 +#define BXTWC_STHRM0IRQ 0x4F19 +#define BXTWC_STHRM1IRQ 0x4F1A +#define BXTWC_STHRM2IRQ 0x4F1B + +struct trip_config_map { + u16 irq_reg; + u16 irq_en; + u16 evt_stat; + u8 irq_mask; + u8 irq_en_mask; + u8 evt_mask; + u8 trip_num; +}; + +struct thermal_irq_map { + char handle[20]; + int num_trips; + const struct trip_config_map *trip_config; +}; + +struct pmic_thermal_data { + const struct thermal_irq_map *maps; + int num_maps; +}; + +static const struct trip_config_map bxtwc_str0_trip_config[] = { + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x01, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x01, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x01, + .trip_num = 0 + }, + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x10, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x10, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x10, + .trip_num = 1 + } +}; + +static const struct trip_config_map bxtwc_str1_trip_config[] = { + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x02, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x02, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x02, + .trip_num = 0 + }, + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x20, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x20, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x20, + .trip_num = 1 + }, +}; + +static const struct trip_config_map bxtwc_str2_trip_config[] = { + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x04, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x04, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x04, + .trip_num = 0 + }, + { + .irq_reg = BXTWC_THRM0IRQ, + .irq_mask = 0x40, + .irq_en = BXTWC_MTHRM0IRQ, + .irq_en_mask = 0x40, + .evt_stat = BXTWC_STHRM0IRQ, + .evt_mask = 0x40, + .trip_num = 1 + }, +}; + +static const struct trip_config_map bxtwc_str3_trip_config[] = { + { + .irq_reg = BXTWC_THRM2IRQ, + .irq_mask = 0x10, + .irq_en = BXTWC_MTHRM2IRQ, + .irq_en_mask = 0x10, + .evt_stat = BXTWC_STHRM2IRQ, + .evt_mask = 0x10, + .trip_num = 0 + }, +}; + +static const struct thermal_irq_map bxtwc_thermal_irq_map[] = { + { + .handle = "STR0", + .trip_config = bxtwc_str0_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str0_trip_config), + }, + { + .handle = "STR1", + .trip_config = bxtwc_str1_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str1_trip_config), + }, + { + .handle = "STR2", + .trip_config = bxtwc_str2_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str2_trip_config), + }, + { + .handle = "STR3", + .trip_config = bxtwc_str3_trip_config, + .num_trips = ARRAY_SIZE(bxtwc_str3_trip_config), + }, +}; + +static const struct pmic_thermal_data bxtwc_thermal_data = { + .maps = bxtwc_thermal_irq_map, + .num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map), +}; + +static irqreturn_t pmic_thermal_irq_handler(int irq, void *data) +{ + struct platform_device *pdev = data; + struct thermal_zone_device *tzd; + struct pmic_thermal_data *td; + struct intel_soc_pmic *pmic; + struct regmap *regmap; + u8 reg_val, mask, irq_stat; + u16 reg, evt_stat_reg; + int i, j, ret; + + pmic = dev_get_drvdata(pdev->dev.parent); + regmap = pmic->regmap; + td = (struct pmic_thermal_data *) + platform_get_device_id(pdev)->driver_data; + + /* Resolve thermal irqs */ + for (i = 0; i < td->num_maps; i++) { + for (j = 0; j < td->maps[i].num_trips; j++) { + reg = td->maps[i].trip_config[j].irq_reg; + mask = td->maps[i].trip_config[j].irq_mask; + /* + * Read the irq register to resolve whether the + * interrupt was triggered for this sensor + */ + if (regmap_read(regmap, reg, &ret)) + return IRQ_HANDLED; + + reg_val = (u8)ret; + irq_stat = ((u8)ret & mask); + + if (!irq_stat) + continue; + + /* + * Read the status register to find out what + * event occurred i.e a high or a low + */ + evt_stat_reg = td->maps[i].trip_config[j].evt_stat; + if (regmap_read(regmap, evt_stat_reg, &ret)) + return IRQ_HANDLED; + + tzd = thermal_zone_get_zone_by_name(td->maps[i].handle); + if (!IS_ERR(tzd)) + thermal_zone_device_update(tzd, + THERMAL_EVENT_UNSPECIFIED); + + /* Clear the appropriate irq */ + regmap_write(regmap, reg, reg_val & mask); + } + } + + return IRQ_HANDLED; +} + +static int pmic_thermal_probe(struct platform_device *pdev) +{ + struct regmap_irq_chip_data *regmap_irq_chip; + struct pmic_thermal_data *thermal_data; + int ret, irq, virq, i, j, pmic_irq_count; + struct intel_soc_pmic *pmic; + struct regmap *regmap; + struct device *dev; + u16 reg; + u8 mask; + + dev = &pdev->dev; + pmic = dev_get_drvdata(pdev->dev.parent); + if (!pmic) { + dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n"); + return -ENODEV; + } + + thermal_data = (struct pmic_thermal_data *) + platform_get_device_id(pdev)->driver_data; + if (!thermal_data) { + dev_err(dev, "No thermal data initialized!!\n"); + return -ENODEV; + } + + regmap = pmic->regmap; + regmap_irq_chip = pmic->irq_chip_data; + + pmic_irq_count = 0; + while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) { + virq = regmap_irq_get_virq(regmap_irq_chip, irq); + if (virq < 0) { + dev_err(dev, "failed to get virq by irq %d\n", irq); + return virq; + } + + ret = devm_request_threaded_irq(&pdev->dev, virq, + NULL, pmic_thermal_irq_handler, + IRQF_ONESHOT, "pmic_thermal", pdev); + + if (ret) { + dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); + return ret; + } + pmic_irq_count++; + } + + /* Enable thermal interrupts */ + for (i = 0; i < thermal_data->num_maps; i++) { + for (j = 0; j < thermal_data->maps[i].num_trips; j++) { + reg = thermal_data->maps[i].trip_config[j].irq_en; + mask = thermal_data->maps[i].trip_config[j].irq_en_mask; + ret = regmap_update_bits(regmap, reg, mask, 0x00); + if (ret) + return ret; + } + } + + return 0; +} + +static const struct platform_device_id pmic_thermal_id_table[] = { + { + .name = "bxt_wcove_thermal", + .driver_data = (kernel_ulong_t)&bxtwc_thermal_data, + }, + {}, +}; + +static struct platform_driver pmic_thermal_driver = { + .probe = pmic_thermal_probe, + .driver = { + .name = "pmic_thermal", + }, + .id_table = pmic_thermal_id_table, +}; + +MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table); +module_platform_driver(pmic_thermal_driver); + +MODULE_AUTHOR("Yegnesh S Iyer "); +MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/intel_pch_thermal.c b/drivers/thermal/intel/intel_pch_thermal.c new file mode 100644 index 000000000000..8a7f69b4b022 --- /dev/null +++ b/drivers/thermal/intel/intel_pch_thermal.c @@ -0,0 +1,432 @@ +/* intel_pch_thermal.c - Intel PCH Thermal driver + * + * Copyright (c) 2015, Intel Corporation. + * + * Authors: + * Tushar Dave + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Intel PCH thermal Device IDs */ +#define PCH_THERMAL_DID_HSW_1 0x9C24 /* Haswell PCH */ +#define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */ +#define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */ +#define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */ +#define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */ +#define PCH_THERMAL_DID_CNL 0x9Df9 /* CNL PCH */ +#define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */ + +/* Wildcat Point-LP PCH Thermal registers */ +#define WPT_TEMP 0x0000 /* Temperature */ +#define WPT_TSC 0x04 /* Thermal Sensor Control */ +#define WPT_TSS 0x06 /* Thermal Sensor Status */ +#define WPT_TSEL 0x08 /* Thermal Sensor Enable and Lock */ +#define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */ +#define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */ +#define WPT_CTT 0x0010 /* Catastrophic Trip Point */ +#define WPT_TAHV 0x0014 /* Thermal Alert High Value */ +#define WPT_TALV 0x0018 /* Thermal Alert Low Value */ +#define WPT_TL 0x00000040 /* Throttle Value */ +#define WPT_PHL 0x0060 /* PCH Hot Level */ +#define WPT_PHLC 0x62 /* PHL Control */ +#define WPT_TAS 0x80 /* Thermal Alert Status */ +#define WPT_TSPIEN 0x82 /* PCI Interrupt Event Enables */ +#define WPT_TSGPEN 0x84 /* General Purpose Event Enables */ + +/* Wildcat Point-LP PCH Thermal Register bit definitions */ +#define WPT_TEMP_TSR 0x01ff /* Temp TS Reading */ +#define WPT_TSC_CPDE 0x01 /* Catastrophic Power-Down Enable */ +#define WPT_TSS_TSDSS 0x10 /* Thermal Sensor Dynamic Shutdown Status */ +#define WPT_TSS_GPES 0x08 /* GPE status */ +#define WPT_TSEL_ETS 0x01 /* Enable TS */ +#define WPT_TSEL_PLDB 0x80 /* TSEL Policy Lock-Down Bit */ +#define WPT_TL_TOL 0x000001FF /* T0 Level */ +#define WPT_TL_T1L 0x1ff00000 /* T1 Level */ +#define WPT_TL_TTEN 0x20000000 /* TT Enable */ + +static char driver_name[] = "Intel PCH thermal driver"; + +struct pch_thermal_device { + void __iomem *hw_base; + const struct pch_dev_ops *ops; + struct pci_dev *pdev; + struct thermal_zone_device *tzd; + int crt_trip_id; + unsigned long crt_temp; + int hot_trip_id; + unsigned long hot_temp; + int psv_trip_id; + unsigned long psv_temp; + bool bios_enabled; +}; + +#ifdef CONFIG_ACPI + +/* + * On some platforms, there is a companion ACPI device, which adds + * passive trip temperature using _PSV method. There is no specific + * passive temperature setting in MMIO interface of this PCI device. + */ +static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, + int *nr_trips) +{ + struct acpi_device *adev; + + ptd->psv_trip_id = -1; + + adev = ACPI_COMPANION(&ptd->pdev->dev); + if (adev) { + unsigned long long r; + acpi_status status; + + status = acpi_evaluate_integer(adev->handle, "_PSV", NULL, + &r); + if (ACPI_SUCCESS(status)) { + unsigned long trip_temp; + + trip_temp = DECI_KELVIN_TO_MILLICELSIUS(r); + if (trip_temp) { + ptd->psv_temp = trip_temp; + ptd->psv_trip_id = *nr_trips; + ++(*nr_trips); + } + } + } +} +#else +static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, + int *nr_trips) +{ + ptd->psv_trip_id = -1; + +} +#endif + +static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips) +{ + u8 tsel; + u16 trip_temp; + + *nr_trips = 0; + + /* Check if BIOS has already enabled thermal sensor */ + if (WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL)) { + ptd->bios_enabled = true; + goto read_trips; + } + + tsel = readb(ptd->hw_base + WPT_TSEL); + /* + * When TSEL's Policy Lock-Down bit is 1, TSEL become RO. + * If so, thermal sensor cannot enable. Bail out. + */ + if (tsel & WPT_TSEL_PLDB) { + dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n"); + return -ENODEV; + } + + writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL); + if (!(WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL))) { + dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n"); + return -ENODEV; + } + +read_trips: + ptd->crt_trip_id = -1; + trip_temp = readw(ptd->hw_base + WPT_CTT); + trip_temp &= 0x1FF; + if (trip_temp) { + /* Resolution of 1/2 degree C and an offset of -50C */ + ptd->crt_temp = trip_temp * 1000 / 2 - 50000; + ptd->crt_trip_id = 0; + ++(*nr_trips); + } + + ptd->hot_trip_id = -1; + trip_temp = readw(ptd->hw_base + WPT_PHL); + trip_temp &= 0x1FF; + if (trip_temp) { + /* Resolution of 1/2 degree C and an offset of -50C */ + ptd->hot_temp = trip_temp * 1000 / 2 - 50000; + ptd->hot_trip_id = *nr_trips; + ++(*nr_trips); + } + + pch_wpt_add_acpi_psv_trip(ptd, nr_trips); + + return 0; +} + +static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp) +{ + u16 wpt_temp; + + wpt_temp = WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP); + + /* Resolution of 1/2 degree C and an offset of -50C */ + *temp = (wpt_temp * 1000 / 2 - 50000); + + return 0; +} + +static int pch_wpt_suspend(struct pch_thermal_device *ptd) +{ + u8 tsel; + + if (ptd->bios_enabled) + return 0; + + tsel = readb(ptd->hw_base + WPT_TSEL); + + writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL); + + return 0; +} + +static int pch_wpt_resume(struct pch_thermal_device *ptd) +{ + u8 tsel; + + if (ptd->bios_enabled) + return 0; + + tsel = readb(ptd->hw_base + WPT_TSEL); + + writeb(tsel | WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL); + + return 0; +} + +struct pch_dev_ops { + int (*hw_init)(struct pch_thermal_device *ptd, int *nr_trips); + int (*get_temp)(struct pch_thermal_device *ptd, int *temp); + int (*suspend)(struct pch_thermal_device *ptd); + int (*resume)(struct pch_thermal_device *ptd); +}; + + +/* dev ops for Wildcat Point */ +static const struct pch_dev_ops pch_dev_ops_wpt = { + .hw_init = pch_wpt_init, + .get_temp = pch_wpt_get_temp, + .suspend = pch_wpt_suspend, + .resume = pch_wpt_resume, +}; + +static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp) +{ + struct pch_thermal_device *ptd = tzd->devdata; + + return ptd->ops->get_temp(ptd, temp); +} + +static int pch_get_trip_type(struct thermal_zone_device *tzd, int trip, + enum thermal_trip_type *type) +{ + struct pch_thermal_device *ptd = tzd->devdata; + + if (ptd->crt_trip_id == trip) + *type = THERMAL_TRIP_CRITICAL; + else if (ptd->hot_trip_id == trip) + *type = THERMAL_TRIP_HOT; + else if (ptd->psv_trip_id == trip) + *type = THERMAL_TRIP_PASSIVE; + else + return -EINVAL; + + return 0; +} + +static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *temp) +{ + struct pch_thermal_device *ptd = tzd->devdata; + + if (ptd->crt_trip_id == trip) + *temp = ptd->crt_temp; + else if (ptd->hot_trip_id == trip) + *temp = ptd->hot_temp; + else if (ptd->psv_trip_id == trip) + *temp = ptd->psv_temp; + else + return -EINVAL; + + return 0; +} + +static struct thermal_zone_device_ops tzd_ops = { + .get_temp = pch_thermal_get_temp, + .get_trip_type = pch_get_trip_type, + .get_trip_temp = pch_get_trip_temp, +}; + +enum board_ids { + board_hsw, + board_wpt, + board_skl, + board_cnl, +}; + +static const struct board_info { + const char *name; + const struct pch_dev_ops *ops; +} board_info[] = { + [board_hsw] = { + .name = "pch_haswell", + .ops = &pch_dev_ops_wpt, + }, + [board_wpt] = { + .name = "pch_wildcat_point", + .ops = &pch_dev_ops_wpt, + }, + [board_skl] = { + .name = "pch_skylake", + .ops = &pch_dev_ops_wpt, + }, + [board_cnl] = { + .name = "pch_cannonlake", + .ops = &pch_dev_ops_wpt, + }, +}; + +static int intel_pch_thermal_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + enum board_ids board_id = id->driver_data; + const struct board_info *bi = &board_info[board_id]; + struct pch_thermal_device *ptd; + int err; + int nr_trips; + + ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL); + if (!ptd) + return -ENOMEM; + + ptd->ops = bi->ops; + + pci_set_drvdata(pdev, ptd); + ptd->pdev = pdev; + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "failed to enable pci device\n"); + return err; + } + + err = pci_request_regions(pdev, driver_name); + if (err) { + dev_err(&pdev->dev, "failed to request pci region\n"); + goto error_disable; + } + + ptd->hw_base = pci_ioremap_bar(pdev, 0); + if (!ptd->hw_base) { + err = -ENOMEM; + dev_err(&pdev->dev, "failed to map mem base\n"); + goto error_release; + } + + err = ptd->ops->hw_init(ptd, &nr_trips); + if (err) + goto error_cleanup; + + ptd->tzd = thermal_zone_device_register(bi->name, nr_trips, 0, ptd, + &tzd_ops, NULL, 0, 0); + if (IS_ERR(ptd->tzd)) { + dev_err(&pdev->dev, "Failed to register thermal zone %s\n", + bi->name); + err = PTR_ERR(ptd->tzd); + goto error_cleanup; + } + + return 0; + +error_cleanup: + iounmap(ptd->hw_base); +error_release: + pci_release_regions(pdev); +error_disable: + pci_disable_device(pdev); + dev_err(&pdev->dev, "pci device failed to probe\n"); + return err; +} + +static void intel_pch_thermal_remove(struct pci_dev *pdev) +{ + struct pch_thermal_device *ptd = pci_get_drvdata(pdev); + + thermal_zone_device_unregister(ptd->tzd); + iounmap(ptd->hw_base); + pci_set_drvdata(pdev, NULL); + pci_release_region(pdev, 0); + pci_disable_device(pdev); +} + +static int intel_pch_thermal_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct pch_thermal_device *ptd = pci_get_drvdata(pdev); + + return ptd->ops->suspend(ptd); +} + +static int intel_pch_thermal_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct pch_thermal_device *ptd = pci_get_drvdata(pdev); + + return ptd->ops->resume(ptd); +} + +static const struct pci_device_id intel_pch_thermal_id[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1), + .driver_data = board_hsw, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2), + .driver_data = board_hsw, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT), + .driver_data = board_wpt, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL), + .driver_data = board_skl, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H), + .driver_data = board_skl, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL), + .driver_data = board_cnl, }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H), + .driver_data = board_cnl, }, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id); + +static const struct dev_pm_ops intel_pch_pm_ops = { + .suspend = intel_pch_thermal_suspend, + .resume = intel_pch_thermal_resume, +}; + +static struct pci_driver intel_pch_thermal_driver = { + .name = "intel_pch_thermal", + .id_table = intel_pch_thermal_id, + .probe = intel_pch_thermal_probe, + .remove = intel_pch_thermal_remove, + .driver.pm = &intel_pch_pm_ops, +}; + +module_pci_driver(intel_pch_thermal_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel PCH Thermal driver"); diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c new file mode 100644 index 000000000000..cde891c54cde --- /dev/null +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -0,0 +1,815 @@ +/* + * intel_powerclamp.c - package c-state idle injection + * + * Copyright (c) 2012, Intel Corporation. + * + * Authors: + * Arjan van de Ven + * Jacob Pan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * + * TODO: + * 1. better handle wakeup from external interrupts, currently a fixed + * compensation is added to clamping duration when excessive amount + * of wakeups are observed during idle time. the reason is that in + * case of external interrupts without need for ack, clamping down + * cpu in non-irq context does not reduce irq. for majority of the + * cases, clamping down cpu does help reduce irq as well, we should + * be able to differentiate the two cases and give a quantitative + * solution for the irqs that we can control. perhaps based on + * get_cpu_iowait_time_us() + * + * 2. synchronization with other hw blocks + * + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define MAX_TARGET_RATIO (50U) +/* For each undisturbed clamping period (no extra wake ups during idle time), + * we increment the confidence counter for the given target ratio. + * CONFIDENCE_OK defines the level where runtime calibration results are + * valid. + */ +#define CONFIDENCE_OK (3) +/* Default idle injection duration, driver adjust sleep time to meet target + * idle ratio. Similar to frequency modulation. + */ +#define DEFAULT_DURATION_JIFFIES (6) + +static unsigned int target_mwait; +static struct dentry *debug_dir; + +/* user selected target */ +static unsigned int set_target_ratio; +static unsigned int current_ratio; +static bool should_skip; +static bool reduce_irq; +static atomic_t idle_wakeup_counter; +static unsigned int control_cpu; /* The cpu assigned to collect stat and update + * control parameters. default to BSP but BSP + * can be offlined. + */ +static bool clamping; + +static const struct sched_param sparam = { + .sched_priority = MAX_USER_RT_PRIO / 2, +}; +struct powerclamp_worker_data { + struct kthread_worker *worker; + struct kthread_work balancing_work; + struct kthread_delayed_work idle_injection_work; + unsigned int cpu; + unsigned int count; + unsigned int guard; + unsigned int window_size_now; + unsigned int target_ratio; + unsigned int duration_jiffies; + bool clamping; +}; + +static struct powerclamp_worker_data * __percpu worker_data; +static struct thermal_cooling_device *cooling_dev; +static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu + * clamping kthread worker + */ + +static unsigned int duration; +static unsigned int pkg_cstate_ratio_cur; +static unsigned int window_size; + +static int duration_set(const char *arg, const struct kernel_param *kp) +{ + int ret = 0; + unsigned long new_duration; + + ret = kstrtoul(arg, 10, &new_duration); + if (ret) + goto exit; + if (new_duration > 25 || new_duration < 6) { + pr_err("Out of recommended range %lu, between 6-25ms\n", + new_duration); + ret = -EINVAL; + } + + duration = clamp(new_duration, 6ul, 25ul); + smp_mb(); + +exit: + + return ret; +} + +static const struct kernel_param_ops duration_ops = { + .set = duration_set, + .get = param_get_int, +}; + + +module_param_cb(duration, &duration_ops, &duration, 0644); +MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec."); + +struct powerclamp_calibration_data { + unsigned long confidence; /* used for calibration, basically a counter + * gets incremented each time a clamping + * period is completed without extra wakeups + * once that counter is reached given level, + * compensation is deemed usable. + */ + unsigned long steady_comp; /* steady state compensation used when + * no extra wakeups occurred. + */ + unsigned long dynamic_comp; /* compensate excessive wakeup from idle + * mostly from external interrupts. + */ +}; + +static struct powerclamp_calibration_data cal_data[MAX_TARGET_RATIO]; + +static int window_size_set(const char *arg, const struct kernel_param *kp) +{ + int ret = 0; + unsigned long new_window_size; + + ret = kstrtoul(arg, 10, &new_window_size); + if (ret) + goto exit_win; + if (new_window_size > 10 || new_window_size < 2) { + pr_err("Out of recommended window size %lu, between 2-10\n", + new_window_size); + ret = -EINVAL; + } + + window_size = clamp(new_window_size, 2ul, 10ul); + smp_mb(); + +exit_win: + + return ret; +} + +static const struct kernel_param_ops window_size_ops = { + .set = window_size_set, + .get = param_get_int, +}; + +module_param_cb(window_size, &window_size_ops, &window_size, 0644); +MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n" + "\tpowerclamp controls idle ratio within this window. larger\n" + "\twindow size results in slower response time but more smooth\n" + "\tclamping results. default to 2."); + +static void find_target_mwait(void) +{ + unsigned int eax, ebx, ecx, edx; + unsigned int highest_cstate = 0; + unsigned int highest_subcstate = 0; + int i; + + if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF) + return; + + cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx); + + if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) || + !(ecx & CPUID5_ECX_INTERRUPT_BREAK)) + return; + + edx >>= MWAIT_SUBSTATE_SIZE; + for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) { + if (edx & MWAIT_SUBSTATE_MASK) { + highest_cstate = i; + highest_subcstate = edx & MWAIT_SUBSTATE_MASK; + } + } + target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) | + (highest_subcstate - 1); + +} + +struct pkg_cstate_info { + bool skip; + int msr_index; + int cstate_id; +}; + +#define PKG_CSTATE_INIT(id) { \ + .msr_index = MSR_PKG_C##id##_RESIDENCY, \ + .cstate_id = id \ + } + +static struct pkg_cstate_info pkg_cstates[] = { + PKG_CSTATE_INIT(2), + PKG_CSTATE_INIT(3), + PKG_CSTATE_INIT(6), + PKG_CSTATE_INIT(7), + PKG_CSTATE_INIT(8), + PKG_CSTATE_INIT(9), + PKG_CSTATE_INIT(10), + {NULL}, +}; + +static bool has_pkg_state_counter(void) +{ + u64 val; + struct pkg_cstate_info *info = pkg_cstates; + + /* check if any one of the counter msrs exists */ + while (info->msr_index) { + if (!rdmsrl_safe(info->msr_index, &val)) + return true; + info++; + } + + return false; +} + +static u64 pkg_state_counter(void) +{ + u64 val; + u64 count = 0; + struct pkg_cstate_info *info = pkg_cstates; + + while (info->msr_index) { + if (!info->skip) { + if (!rdmsrl_safe(info->msr_index, &val)) + count += val; + else + info->skip = true; + } + info++; + } + + return count; +} + +static unsigned int get_compensation(int ratio) +{ + unsigned int comp = 0; + + /* we only use compensation if all adjacent ones are good */ + if (ratio == 1 && + cal_data[ratio].confidence >= CONFIDENCE_OK && + cal_data[ratio + 1].confidence >= CONFIDENCE_OK && + cal_data[ratio + 2].confidence >= CONFIDENCE_OK) { + comp = (cal_data[ratio].steady_comp + + cal_data[ratio + 1].steady_comp + + cal_data[ratio + 2].steady_comp) / 3; + } else if (ratio == MAX_TARGET_RATIO - 1 && + cal_data[ratio].confidence >= CONFIDENCE_OK && + cal_data[ratio - 1].confidence >= CONFIDENCE_OK && + cal_data[ratio - 2].confidence >= CONFIDENCE_OK) { + comp = (cal_data[ratio].steady_comp + + cal_data[ratio - 1].steady_comp + + cal_data[ratio - 2].steady_comp) / 3; + } else if (cal_data[ratio].confidence >= CONFIDENCE_OK && + cal_data[ratio - 1].confidence >= CONFIDENCE_OK && + cal_data[ratio + 1].confidence >= CONFIDENCE_OK) { + comp = (cal_data[ratio].steady_comp + + cal_data[ratio - 1].steady_comp + + cal_data[ratio + 1].steady_comp) / 3; + } + + /* REVISIT: simple penalty of double idle injection */ + if (reduce_irq) + comp = ratio; + /* do not exceed limit */ + if (comp + ratio >= MAX_TARGET_RATIO) + comp = MAX_TARGET_RATIO - ratio - 1; + + return comp; +} + +static void adjust_compensation(int target_ratio, unsigned int win) +{ + int delta; + struct powerclamp_calibration_data *d = &cal_data[target_ratio]; + + /* + * adjust compensations if confidence level has not been reached or + * there are too many wakeups during the last idle injection period, we + * cannot trust the data for compensation. + */ + if (d->confidence >= CONFIDENCE_OK || + atomic_read(&idle_wakeup_counter) > + win * num_online_cpus()) + return; + + delta = set_target_ratio - current_ratio; + /* filter out bad data */ + if (delta >= 0 && delta <= (1+target_ratio/10)) { + if (d->steady_comp) + d->steady_comp = + roundup(delta+d->steady_comp, 2)/2; + else + d->steady_comp = delta; + d->confidence++; + } +} + +static bool powerclamp_adjust_controls(unsigned int target_ratio, + unsigned int guard, unsigned int win) +{ + static u64 msr_last, tsc_last; + u64 msr_now, tsc_now; + u64 val64; + + /* check result for the last window */ + msr_now = pkg_state_counter(); + tsc_now = rdtsc(); + + /* calculate pkg cstate vs tsc ratio */ + if (!msr_last || !tsc_last) + current_ratio = 1; + else if (tsc_now-tsc_last) { + val64 = 100*(msr_now-msr_last); + do_div(val64, (tsc_now-tsc_last)); + current_ratio = val64; + } + + /* update record */ + msr_last = msr_now; + tsc_last = tsc_now; + + adjust_compensation(target_ratio, win); + /* + * too many external interrupts, set flag such + * that we can take measure later. + */ + reduce_irq = atomic_read(&idle_wakeup_counter) >= + 2 * win * num_online_cpus(); + + atomic_set(&idle_wakeup_counter, 0); + /* if we are above target+guard, skip */ + return set_target_ratio + guard <= current_ratio; +} + +static void clamp_balancing_func(struct kthread_work *work) +{ + struct powerclamp_worker_data *w_data; + int sleeptime; + unsigned long target_jiffies; + unsigned int compensated_ratio; + int interval; /* jiffies to sleep for each attempt */ + + w_data = container_of(work, struct powerclamp_worker_data, + balancing_work); + + /* + * make sure user selected ratio does not take effect until + * the next round. adjust target_ratio if user has changed + * target such that we can converge quickly. + */ + w_data->target_ratio = READ_ONCE(set_target_ratio); + w_data->guard = 1 + w_data->target_ratio / 20; + w_data->window_size_now = window_size; + w_data->duration_jiffies = msecs_to_jiffies(duration); + w_data->count++; + + /* + * systems may have different ability to enter package level + * c-states, thus we need to compensate the injected idle ratio + * to achieve the actual target reported by the HW. + */ + compensated_ratio = w_data->target_ratio + + get_compensation(w_data->target_ratio); + if (compensated_ratio <= 0) + compensated_ratio = 1; + interval = w_data->duration_jiffies * 100 / compensated_ratio; + + /* align idle time */ + target_jiffies = roundup(jiffies, interval); + sleeptime = target_jiffies - jiffies; + if (sleeptime <= 0) + sleeptime = 1; + + if (clamping && w_data->clamping && cpu_online(w_data->cpu)) + kthread_queue_delayed_work(w_data->worker, + &w_data->idle_injection_work, + sleeptime); +} + +static void clamp_idle_injection_func(struct kthread_work *work) +{ + struct powerclamp_worker_data *w_data; + + w_data = container_of(work, struct powerclamp_worker_data, + idle_injection_work.work); + + /* + * only elected controlling cpu can collect stats and update + * control parameters. + */ + if (w_data->cpu == control_cpu && + !(w_data->count % w_data->window_size_now)) { + should_skip = + powerclamp_adjust_controls(w_data->target_ratio, + w_data->guard, + w_data->window_size_now); + smp_mb(); + } + + if (should_skip) + goto balance; + + play_idle(jiffies_to_msecs(w_data->duration_jiffies)); + +balance: + if (clamping && w_data->clamping && cpu_online(w_data->cpu)) + kthread_queue_work(w_data->worker, &w_data->balancing_work); +} + +/* + * 1 HZ polling while clamping is active, useful for userspace + * to monitor actual idle ratio. + */ +static void poll_pkg_cstate(struct work_struct *dummy); +static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate); +static void poll_pkg_cstate(struct work_struct *dummy) +{ + static u64 msr_last; + static u64 tsc_last; + + u64 msr_now; + u64 tsc_now; + u64 val64; + + msr_now = pkg_state_counter(); + tsc_now = rdtsc(); + + /* calculate pkg cstate vs tsc ratio */ + if (!msr_last || !tsc_last) + pkg_cstate_ratio_cur = 1; + else { + if (tsc_now - tsc_last) { + val64 = 100 * (msr_now - msr_last); + do_div(val64, (tsc_now - tsc_last)); + pkg_cstate_ratio_cur = val64; + } + } + + /* update record */ + msr_last = msr_now; + tsc_last = tsc_now; + + if (true == clamping) + schedule_delayed_work(&poll_pkg_cstate_work, HZ); +} + +static void start_power_clamp_worker(unsigned long cpu) +{ + struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu); + struct kthread_worker *worker; + + worker = kthread_create_worker_on_cpu(cpu, 0, "kidle_inject/%ld", cpu); + if (IS_ERR(worker)) + return; + + w_data->worker = worker; + w_data->count = 0; + w_data->cpu = cpu; + w_data->clamping = true; + set_bit(cpu, cpu_clamping_mask); + sched_setscheduler(worker->task, SCHED_FIFO, &sparam); + kthread_init_work(&w_data->balancing_work, clamp_balancing_func); + kthread_init_delayed_work(&w_data->idle_injection_work, + clamp_idle_injection_func); + kthread_queue_work(w_data->worker, &w_data->balancing_work); +} + +static void stop_power_clamp_worker(unsigned long cpu) +{ + struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu); + + if (!w_data->worker) + return; + + w_data->clamping = false; + /* + * Make sure that all works that get queued after this point see + * the clamping disabled. The counter part is not needed because + * there is an implicit memory barrier when the queued work + * is proceed. + */ + smp_wmb(); + kthread_cancel_work_sync(&w_data->balancing_work); + kthread_cancel_delayed_work_sync(&w_data->idle_injection_work); + /* + * The balancing work still might be queued here because + * the handling of the "clapming" variable, cancel, and queue + * operations are not synchronized via a lock. But it is not + * a big deal. The balancing work is fast and destroy kthread + * will wait for it. + */ + clear_bit(w_data->cpu, cpu_clamping_mask); + kthread_destroy_worker(w_data->worker); + + w_data->worker = NULL; +} + +static int start_power_clamp(void) +{ + unsigned long cpu; + + set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1); + /* prevent cpu hotplug */ + get_online_cpus(); + + /* prefer BSP */ + control_cpu = 0; + if (!cpu_online(control_cpu)) + control_cpu = smp_processor_id(); + + clamping = true; + schedule_delayed_work(&poll_pkg_cstate_work, 0); + + /* start one kthread worker per online cpu */ + for_each_online_cpu(cpu) { + start_power_clamp_worker(cpu); + } + put_online_cpus(); + + return 0; +} + +static void end_power_clamp(void) +{ + int i; + + /* + * Block requeuing in all the kthread workers. They will flush and + * stop faster. + */ + clamping = false; + if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) { + for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) { + pr_debug("clamping worker for cpu %d alive, destroy\n", + i); + stop_power_clamp_worker(i); + } + } +} + +static int powerclamp_cpu_online(unsigned int cpu) +{ + if (clamping == false) + return 0; + start_power_clamp_worker(cpu); + /* prefer BSP as controlling CPU */ + if (cpu == 0) { + control_cpu = 0; + smp_mb(); + } + return 0; +} + +static int powerclamp_cpu_predown(unsigned int cpu) +{ + if (clamping == false) + return 0; + + stop_power_clamp_worker(cpu); + if (cpu != control_cpu) + return 0; + + control_cpu = cpumask_first(cpu_online_mask); + if (control_cpu == cpu) + control_cpu = cpumask_next(cpu, cpu_online_mask); + smp_mb(); + return 0; +} + +static int powerclamp_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = MAX_TARGET_RATIO; + + return 0; +} + +static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + if (true == clamping) + *state = pkg_cstate_ratio_cur; + else + /* to save power, do not poll idle ratio while not clamping */ + *state = -1; /* indicates invalid state */ + + return 0; +} + +static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long new_target_ratio) +{ + int ret = 0; + + new_target_ratio = clamp(new_target_ratio, 0UL, + (unsigned long) (MAX_TARGET_RATIO-1)); + if (set_target_ratio == 0 && new_target_ratio > 0) { + pr_info("Start idle injection to reduce power\n"); + set_target_ratio = new_target_ratio; + ret = start_power_clamp(); + goto exit_set; + } else if (set_target_ratio > 0 && new_target_ratio == 0) { + pr_info("Stop forced idle injection\n"); + end_power_clamp(); + set_target_ratio = 0; + } else /* adjust currently running */ { + set_target_ratio = new_target_ratio; + /* make new set_target_ratio visible to other cpus */ + smp_mb(); + } + +exit_set: + return ret; +} + +/* bind to generic thermal layer as cooling device*/ +static struct thermal_cooling_device_ops powerclamp_cooling_ops = { + .get_max_state = powerclamp_get_max_state, + .get_cur_state = powerclamp_get_cur_state, + .set_cur_state = powerclamp_set_cur_state, +}; + +static const struct x86_cpu_id __initconst intel_powerclamp_ids[] = { + { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_MWAIT }, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); + +static int __init powerclamp_probe(void) +{ + + if (!x86_match_cpu(intel_powerclamp_ids)) { + pr_err("CPU does not support MWAIT\n"); + return -ENODEV; + } + + /* The goal for idle time alignment is to achieve package cstate. */ + if (!has_pkg_state_counter()) { + pr_info("No package C-state available\n"); + return -ENODEV; + } + + /* find the deepest mwait value */ + find_target_mwait(); + + return 0; +} + +static int powerclamp_debug_show(struct seq_file *m, void *unused) +{ + int i = 0; + + seq_printf(m, "controlling cpu: %d\n", control_cpu); + seq_printf(m, "pct confidence steady dynamic (compensation)\n"); + for (i = 0; i < MAX_TARGET_RATIO; i++) { + seq_printf(m, "%d\t%lu\t%lu\t%lu\n", + i, + cal_data[i].confidence, + cal_data[i].steady_comp, + cal_data[i].dynamic_comp); + } + + return 0; +} + +static int powerclamp_debug_open(struct inode *inode, + struct file *file) +{ + return single_open(file, powerclamp_debug_show, inode->i_private); +} + +static const struct file_operations powerclamp_debug_fops = { + .open = powerclamp_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static inline void powerclamp_create_debug_files(void) +{ + debug_dir = debugfs_create_dir("intel_powerclamp", NULL); + if (!debug_dir) + return; + + if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir, + cal_data, &powerclamp_debug_fops)) + goto file_error; + + return; + +file_error: + debugfs_remove_recursive(debug_dir); +} + +static enum cpuhp_state hp_state; + +static int __init powerclamp_init(void) +{ + int retval; + int bitmap_size; + + bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long); + cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL); + if (!cpu_clamping_mask) + return -ENOMEM; + + /* probe cpu features and ids here */ + retval = powerclamp_probe(); + if (retval) + goto exit_free; + + /* set default limit, maybe adjusted during runtime based on feedback */ + window_size = 2; + retval = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, + "thermal/intel_powerclamp:online", + powerclamp_cpu_online, + powerclamp_cpu_predown); + if (retval < 0) + goto exit_free; + + hp_state = retval; + + worker_data = alloc_percpu(struct powerclamp_worker_data); + if (!worker_data) { + retval = -ENOMEM; + goto exit_unregister; + } + + cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL, + &powerclamp_cooling_ops); + if (IS_ERR(cooling_dev)) { + retval = -ENODEV; + goto exit_free_thread; + } + + if (!duration) + duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES); + + powerclamp_create_debug_files(); + + return 0; + +exit_free_thread: + free_percpu(worker_data); +exit_unregister: + cpuhp_remove_state_nocalls(hp_state); +exit_free: + kfree(cpu_clamping_mask); + return retval; +} +module_init(powerclamp_init); + +static void __exit powerclamp_exit(void) +{ + end_power_clamp(); + cpuhp_remove_state_nocalls(hp_state); + free_percpu(worker_data); + thermal_cooling_device_unregister(cooling_dev); + kfree(cpu_clamping_mask); + + cancel_delayed_work_sync(&poll_pkg_cstate_work); + debugfs_remove_recursive(debug_dir); +} +module_exit(powerclamp_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Arjan van de Ven "); +MODULE_AUTHOR("Jacob Pan "); +MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs"); diff --git a/drivers/thermal/intel/intel_quark_dts_thermal.c b/drivers/thermal/intel/intel_quark_dts_thermal.c new file mode 100644 index 000000000000..5d33b350da1c --- /dev/null +++ b/drivers/thermal/intel/intel_quark_dts_thermal.c @@ -0,0 +1,471 @@ +/* + * intel_quark_dts_thermal.c + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * Contact Information: + * Ong Boon Leong + * Intel Malaysia, Penang + * + * BSD LICENSE + * + * Copyright(c) 2015 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Quark DTS thermal driver is implemented by referencing + * intel_soc_dts_thermal.c. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#define X86_FAMILY_QUARK 0x5 +#define X86_MODEL_QUARK_X1000 0x9 + +/* DTS reset is programmed via QRK_MBI_UNIT_SOC */ +#define QRK_DTS_REG_OFFSET_RESET 0x34 +#define QRK_DTS_RESET_BIT BIT(0) + +/* DTS enable is programmed via QRK_MBI_UNIT_RMU */ +#define QRK_DTS_REG_OFFSET_ENABLE 0xB0 +#define QRK_DTS_ENABLE_BIT BIT(15) + +/* Temperature Register is read via QRK_MBI_UNIT_RMU */ +#define QRK_DTS_REG_OFFSET_TEMP 0xB1 +#define QRK_DTS_MASK_TEMP 0xFF +#define QRK_DTS_OFFSET_TEMP 0 +#define QRK_DTS_OFFSET_REL_TEMP 16 +#define QRK_DTS_TEMP_BASE 50 + +/* Programmable Trip Point Register is configured via QRK_MBI_UNIT_RMU */ +#define QRK_DTS_REG_OFFSET_PTPS 0xB2 +#define QRK_DTS_MASK_TP_THRES 0xFF +#define QRK_DTS_SHIFT_TP 8 +#define QRK_DTS_ID_TP_CRITICAL 0 +#define QRK_DTS_SAFE_TP_THRES 105 + +/* Thermal Sensor Register Lock */ +#define QRK_DTS_REG_OFFSET_LOCK 0x71 +#define QRK_DTS_LOCK_BIT BIT(5) + +/* Quark DTS has 2 trip points: hot & catastrophic */ +#define QRK_MAX_DTS_TRIPS 2 +/* If DTS not locked, all trip points are configurable */ +#define QRK_DTS_WR_MASK_SET 0x3 +/* If DTS locked, all trip points are not configurable */ +#define QRK_DTS_WR_MASK_CLR 0 + +#define DEFAULT_POLL_DELAY 2000 + +struct soc_sensor_entry { + bool locked; + u32 store_ptps; + u32 store_dts_enable; + enum thermal_device_mode mode; + struct thermal_zone_device *tzone; +}; + +static struct soc_sensor_entry *soc_dts; + +static int polling_delay = DEFAULT_POLL_DELAY; +module_param(polling_delay, int, 0644); +MODULE_PARM_DESC(polling_delay, + "Polling interval for checking trip points (in milliseconds)"); + +static DEFINE_MUTEX(dts_update_mutex); + +static int soc_dts_enable(struct thermal_zone_device *tzd) +{ + u32 out; + struct soc_sensor_entry *aux_entry = tzd->devdata; + int ret; + + ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_ENABLE, &out); + if (ret) + return ret; + + if (out & QRK_DTS_ENABLE_BIT) { + aux_entry->mode = THERMAL_DEVICE_ENABLED; + return 0; + } + + if (!aux_entry->locked) { + out |= QRK_DTS_ENABLE_BIT; + ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, + QRK_DTS_REG_OFFSET_ENABLE, out); + if (ret) + return ret; + + aux_entry->mode = THERMAL_DEVICE_ENABLED; + } else { + aux_entry->mode = THERMAL_DEVICE_DISABLED; + pr_info("DTS is locked. Cannot enable DTS\n"); + ret = -EPERM; + } + + return ret; +} + +static int soc_dts_disable(struct thermal_zone_device *tzd) +{ + u32 out; + struct soc_sensor_entry *aux_entry = tzd->devdata; + int ret; + + ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_ENABLE, &out); + if (ret) + return ret; + + if (!(out & QRK_DTS_ENABLE_BIT)) { + aux_entry->mode = THERMAL_DEVICE_DISABLED; + return 0; + } + + if (!aux_entry->locked) { + out &= ~QRK_DTS_ENABLE_BIT; + ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, + QRK_DTS_REG_OFFSET_ENABLE, out); + + if (ret) + return ret; + + aux_entry->mode = THERMAL_DEVICE_DISABLED; + } else { + aux_entry->mode = THERMAL_DEVICE_ENABLED; + pr_info("DTS is locked. Cannot disable DTS\n"); + ret = -EPERM; + } + + return ret; +} + +static int _get_trip_temp(int trip, int *temp) +{ + int status; + u32 out; + + mutex_lock(&dts_update_mutex); + status = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_PTPS, &out); + mutex_unlock(&dts_update_mutex); + + if (status) + return status; + + /* + * Thermal Sensor Programmable Trip Point Register has 8-bit + * fields for critical (catastrophic) and hot set trip point + * thresholds. The threshold value is always offset by its + * temperature base (50 degree Celsius). + */ + *temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES; + *temp -= QRK_DTS_TEMP_BASE; + + return 0; +} + +static inline int sys_get_trip_temp(struct thermal_zone_device *tzd, + int trip, int *temp) +{ + return _get_trip_temp(trip, temp); +} + +static inline int sys_get_crit_temp(struct thermal_zone_device *tzd, int *temp) +{ + return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp); +} + +static int update_trip_temp(struct soc_sensor_entry *aux_entry, + int trip, int temp) +{ + u32 out; + u32 temp_out; + u32 store_ptps; + int ret; + + mutex_lock(&dts_update_mutex); + if (aux_entry->locked) { + ret = -EPERM; + goto failed; + } + + ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_PTPS, &store_ptps); + if (ret) + goto failed; + + /* + * Protection against unsafe trip point thresdhold value. + * As Quark X1000 data-sheet does not provide any recommendation + * regarding the safe trip point threshold value to use, we choose + * the safe value according to the threshold value set by UEFI BIOS. + */ + if (temp > QRK_DTS_SAFE_TP_THRES) + temp = QRK_DTS_SAFE_TP_THRES; + + /* + * Thermal Sensor Programmable Trip Point Register has 8-bit + * fields for critical (catastrophic) and hot set trip point + * thresholds. The threshold value is always offset by its + * temperature base (50 degree Celsius). + */ + temp_out = temp + QRK_DTS_TEMP_BASE; + out = (store_ptps & ~(QRK_DTS_MASK_TP_THRES << + (trip * QRK_DTS_SHIFT_TP))); + out |= (temp_out & QRK_DTS_MASK_TP_THRES) << + (trip * QRK_DTS_SHIFT_TP); + + ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, + QRK_DTS_REG_OFFSET_PTPS, out); + +failed: + mutex_unlock(&dts_update_mutex); + return ret; +} + +static inline int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, + int temp) +{ + return update_trip_temp(tzd->devdata, trip, temp); +} + +static int sys_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + if (trip) + *type = THERMAL_TRIP_HOT; + else + *type = THERMAL_TRIP_CRITICAL; + + return 0; +} + +static int sys_get_curr_temp(struct thermal_zone_device *tzd, + int *temp) +{ + u32 out; + int ret; + + mutex_lock(&dts_update_mutex); + ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_TEMP, &out); + mutex_unlock(&dts_update_mutex); + + if (ret) + return ret; + + /* + * Thermal Sensor Temperature Register has 8-bit field + * for temperature value (offset by temperature base + * 50 degree Celsius). + */ + out = (out >> QRK_DTS_OFFSET_TEMP) & QRK_DTS_MASK_TEMP; + *temp = out - QRK_DTS_TEMP_BASE; + + return 0; +} + +static int sys_get_mode(struct thermal_zone_device *tzd, + enum thermal_device_mode *mode) +{ + struct soc_sensor_entry *aux_entry = tzd->devdata; + *mode = aux_entry->mode; + return 0; +} + +static int sys_set_mode(struct thermal_zone_device *tzd, + enum thermal_device_mode mode) +{ + int ret; + + mutex_lock(&dts_update_mutex); + if (mode == THERMAL_DEVICE_ENABLED) + ret = soc_dts_enable(tzd); + else + ret = soc_dts_disable(tzd); + mutex_unlock(&dts_update_mutex); + + return ret; +} + +static struct thermal_zone_device_ops tzone_ops = { + .get_temp = sys_get_curr_temp, + .get_trip_temp = sys_get_trip_temp, + .get_trip_type = sys_get_trip_type, + .set_trip_temp = sys_set_trip_temp, + .get_crit_temp = sys_get_crit_temp, + .get_mode = sys_get_mode, + .set_mode = sys_set_mode, +}; + +static void free_soc_dts(struct soc_sensor_entry *aux_entry) +{ + if (aux_entry) { + if (!aux_entry->locked) { + mutex_lock(&dts_update_mutex); + iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, + QRK_DTS_REG_OFFSET_ENABLE, + aux_entry->store_dts_enable); + + iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, + QRK_DTS_REG_OFFSET_PTPS, + aux_entry->store_ptps); + mutex_unlock(&dts_update_mutex); + } + thermal_zone_device_unregister(aux_entry->tzone); + kfree(aux_entry); + } +} + +static struct soc_sensor_entry *alloc_soc_dts(void) +{ + struct soc_sensor_entry *aux_entry; + int err; + u32 out; + int wr_mask; + + aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL); + if (!aux_entry) { + err = -ENOMEM; + return ERR_PTR(-ENOMEM); + } + + /* Check if DTS register is locked */ + err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_LOCK, &out); + if (err) + goto err_ret; + + if (out & QRK_DTS_LOCK_BIT) { + aux_entry->locked = true; + wr_mask = QRK_DTS_WR_MASK_CLR; + } else { + aux_entry->locked = false; + wr_mask = QRK_DTS_WR_MASK_SET; + } + + /* Store DTS default state if DTS registers are not locked */ + if (!aux_entry->locked) { + /* Store DTS default enable for restore on exit */ + err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_ENABLE, + &aux_entry->store_dts_enable); + if (err) + goto err_ret; + + /* Store DTS default PTPS register for restore on exit */ + err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, + QRK_DTS_REG_OFFSET_PTPS, + &aux_entry->store_ptps); + if (err) + goto err_ret; + } + + aux_entry->tzone = thermal_zone_device_register("quark_dts", + QRK_MAX_DTS_TRIPS, + wr_mask, + aux_entry, &tzone_ops, NULL, 0, polling_delay); + if (IS_ERR(aux_entry->tzone)) { + err = PTR_ERR(aux_entry->tzone); + goto err_ret; + } + + mutex_lock(&dts_update_mutex); + err = soc_dts_enable(aux_entry->tzone); + mutex_unlock(&dts_update_mutex); + if (err) + goto err_aux_status; + + return aux_entry; + +err_aux_status: + thermal_zone_device_unregister(aux_entry->tzone); +err_ret: + kfree(aux_entry); + return ERR_PTR(err); +} + +static const struct x86_cpu_id qrk_thermal_ids[] __initconst = { + { X86_VENDOR_INTEL, X86_FAMILY_QUARK, X86_MODEL_QUARK_X1000 }, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids); + +static int __init intel_quark_thermal_init(void) +{ + int err = 0; + + if (!x86_match_cpu(qrk_thermal_ids) || !iosf_mbi_available()) + return -ENODEV; + + soc_dts = alloc_soc_dts(); + if (IS_ERR(soc_dts)) { + err = PTR_ERR(soc_dts); + goto err_free; + } + + return 0; + +err_free: + free_soc_dts(soc_dts); + return err; +} + +static void __exit intel_quark_thermal_exit(void) +{ + free_soc_dts(soc_dts); +} + +module_init(intel_quark_thermal_init) +module_exit(intel_quark_thermal_exit) + +MODULE_DESCRIPTION("Intel Quark DTS Thermal Driver"); +MODULE_AUTHOR("Ong Boon Leong "); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/thermal/intel/intel_soc_dts_iosf.c b/drivers/thermal/intel/intel_soc_dts_iosf.c new file mode 100644 index 000000000000..e0813dfaa278 --- /dev/null +++ b/drivers/thermal/intel/intel_soc_dts_iosf.c @@ -0,0 +1,478 @@ +/* + * intel_soc_dts_iosf.c + * Copyright (c) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include "intel_soc_dts_iosf.h" + +#define SOC_DTS_OFFSET_ENABLE 0xB0 +#define SOC_DTS_OFFSET_TEMP 0xB1 + +#define SOC_DTS_OFFSET_PTPS 0xB2 +#define SOC_DTS_OFFSET_PTTS 0xB3 +#define SOC_DTS_OFFSET_PTTSS 0xB4 +#define SOC_DTS_OFFSET_PTMC 0x80 +#define SOC_DTS_TE_AUX0 0xB5 +#define SOC_DTS_TE_AUX1 0xB6 + +#define SOC_DTS_AUX0_ENABLE_BIT BIT(0) +#define SOC_DTS_AUX1_ENABLE_BIT BIT(1) +#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16) +#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17) +#define SOC_DTS_TE_SCI_ENABLE BIT(9) +#define SOC_DTS_TE_SMI_ENABLE BIT(10) +#define SOC_DTS_TE_MSI_ENABLE BIT(11) +#define SOC_DTS_TE_APICA_ENABLE BIT(14) +#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4) + +/* DTS encoding for TJ MAX temperature */ +#define SOC_DTS_TJMAX_ENCODING 0x7F + +/* Only 2 out of 4 is allowed for OSPM */ +#define SOC_MAX_DTS_TRIPS 2 + +/* Mask for two trips in status bits */ +#define SOC_DTS_TRIP_MASK 0x03 + +/* DTS0 and DTS 1 */ +#define SOC_MAX_DTS_SENSORS 2 + +static int get_tj_max(u32 *tj_max) +{ + u32 eax, edx; + u32 val; + int err; + + err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); + if (err) + goto err_ret; + else { + val = (eax >> 16) & 0xff; + if (val) + *tj_max = val * 1000; + else { + err = -EINVAL; + goto err_ret; + } + } + + return 0; +err_ret: + *tj_max = 0; + + return err; +} + +static int sys_get_trip_temp(struct thermal_zone_device *tzd, int trip, + int *temp) +{ + int status; + u32 out; + struct intel_soc_dts_sensor_entry *dts; + struct intel_soc_dts_sensors *sensors; + + dts = tzd->devdata; + sensors = dts->sensors; + mutex_lock(&sensors->dts_update_lock); + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_PTPS, &out); + mutex_unlock(&sensors->dts_update_lock); + if (status) + return status; + + out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING; + if (!out) + *temp = 0; + else + *temp = sensors->tj_max - out * 1000; + + return 0; +} + +static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts, + int thres_index, int temp, + enum thermal_trip_type trip_type) +{ + int status; + u32 temp_out; + u32 out; + u32 store_ptps; + u32 store_ptmc; + u32 store_te_out; + u32 te_out; + u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE; + struct intel_soc_dts_sensors *sensors = dts->sensors; + + if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI) + int_enable_bit |= SOC_DTS_TE_MSI_ENABLE; + + temp_out = (sensors->tj_max - temp) / 1000; + + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_PTPS, &store_ptps); + if (status) + return status; + + out = (store_ptps & ~(0xFF << (thres_index * 8))); + out |= (temp_out & 0xFF) << (thres_index * 8); + status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_PTPS, out); + if (status) + return status; + + pr_debug("update_trip_temp PTPS = %x\n", out); + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_PTMC, &out); + if (status) + goto err_restore_ptps; + + store_ptmc = out; + + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_TE_AUX0 + thres_index, + &te_out); + if (status) + goto err_restore_ptmc; + + store_te_out = te_out; + /* Enable for CPU module 0 and module 1 */ + out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT | + SOC_DTS_CPU_MODULE1_ENABLE_BIT); + if (temp) { + if (thres_index) + out |= SOC_DTS_AUX1_ENABLE_BIT; + else + out |= SOC_DTS_AUX0_ENABLE_BIT; + te_out |= int_enable_bit; + } else { + if (thres_index) + out &= ~SOC_DTS_AUX1_ENABLE_BIT; + else + out &= ~SOC_DTS_AUX0_ENABLE_BIT; + te_out &= ~int_enable_bit; + } + status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_PTMC, out); + if (status) + goto err_restore_te_out; + + status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_TE_AUX0 + thres_index, + te_out); + if (status) + goto err_restore_te_out; + + dts->trip_types[thres_index] = trip_type; + + return 0; +err_restore_te_out: + iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_PTMC, store_te_out); +err_restore_ptmc: + iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_PTMC, store_ptmc); +err_restore_ptps: + iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_PTPS, store_ptps); + /* Nothing we can do if restore fails */ + + return status; +} + +static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, + int temp) +{ + struct intel_soc_dts_sensor_entry *dts = tzd->devdata; + struct intel_soc_dts_sensors *sensors = dts->sensors; + int status; + + if (temp > sensors->tj_max) + return -EINVAL; + + mutex_lock(&sensors->dts_update_lock); + status = update_trip_temp(tzd->devdata, trip, temp, + dts->trip_types[trip]); + mutex_unlock(&sensors->dts_update_lock); + + return status; +} + +static int sys_get_trip_type(struct thermal_zone_device *tzd, + int trip, enum thermal_trip_type *type) +{ + struct intel_soc_dts_sensor_entry *dts; + + dts = tzd->devdata; + + *type = dts->trip_types[trip]; + + return 0; +} + +static int sys_get_curr_temp(struct thermal_zone_device *tzd, + int *temp) +{ + int status; + u32 out; + struct intel_soc_dts_sensor_entry *dts; + struct intel_soc_dts_sensors *sensors; + + dts = tzd->devdata; + sensors = dts->sensors; + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_TEMP, &out); + if (status) + return status; + + out = (out & dts->temp_mask) >> dts->temp_shift; + out -= SOC_DTS_TJMAX_ENCODING; + *temp = sensors->tj_max - out * 1000; + + return 0; +} + +static struct thermal_zone_device_ops tzone_ops = { + .get_temp = sys_get_curr_temp, + .get_trip_temp = sys_get_trip_temp, + .get_trip_type = sys_get_trip_type, + .set_trip_temp = sys_set_trip_temp, +}; + +static int soc_dts_enable(int id) +{ + u32 out; + int ret; + + ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_ENABLE, &out); + if (ret) + return ret; + + if (!(out & BIT(id))) { + out |= BIT(id); + ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_ENABLE, out); + if (ret) + return ret; + } + + return ret; +} + +static void remove_dts_thermal_zone(struct intel_soc_dts_sensor_entry *dts) +{ + if (dts) { + iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_ENABLE, dts->store_status); + thermal_zone_device_unregister(dts->tzone); + } +} + +static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts, + bool notification_support, int trip_cnt, + int read_only_trip_cnt) +{ + char name[10]; + int trip_count = 0; + int trip_mask = 0; + u32 store_ptps; + int ret; + int i; + + /* Store status to restor on exit */ + ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_ENABLE, &dts->store_status); + if (ret) + goto err_ret; + + dts->id = id; + dts->temp_mask = 0x00FF << (id * 8); + dts->temp_shift = id * 8; + if (notification_support) { + trip_count = min(SOC_MAX_DTS_TRIPS, trip_cnt); + trip_mask = BIT(trip_count - read_only_trip_cnt) - 1; + } + + /* Check if the writable trip we provide is not used by BIOS */ + ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_PTPS, &store_ptps); + if (ret) + trip_mask = 0; + else { + for (i = 0; i < trip_count; ++i) { + if (trip_mask & BIT(i)) + if (store_ptps & (0xff << (i * 8))) + trip_mask &= ~BIT(i); + } + } + dts->trip_mask = trip_mask; + dts->trip_count = trip_count; + snprintf(name, sizeof(name), "soc_dts%d", id); + dts->tzone = thermal_zone_device_register(name, + trip_count, + trip_mask, + dts, &tzone_ops, + NULL, 0, 0); + if (IS_ERR(dts->tzone)) { + ret = PTR_ERR(dts->tzone); + goto err_ret; + } + + ret = soc_dts_enable(id); + if (ret) + goto err_enable; + + return 0; +err_enable: + thermal_zone_device_unregister(dts->tzone); +err_ret: + return ret; +} + +int intel_soc_dts_iosf_add_read_only_critical_trip( + struct intel_soc_dts_sensors *sensors, int critical_offset) +{ + int i, j; + + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { + for (j = 0; j < sensors->soc_dts[i].trip_count; ++j) { + if (!(sensors->soc_dts[i].trip_mask & BIT(j))) { + return update_trip_temp(&sensors->soc_dts[i], j, + sensors->tj_max - critical_offset, + THERMAL_TRIP_CRITICAL); + } + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_add_read_only_critical_trip); + +void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors) +{ + u32 sticky_out; + int status; + u32 ptmc_out; + unsigned long flags; + + spin_lock_irqsave(&sensors->intr_notify_lock, flags); + + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_PTMC, &ptmc_out); + ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT; + status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_PTMC, ptmc_out); + + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_PTTSS, &sticky_out); + pr_debug("status %d PTTSS %x\n", status, sticky_out); + if (sticky_out & SOC_DTS_TRIP_MASK) { + int i; + /* reset sticky bit */ + status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, + SOC_DTS_OFFSET_PTTSS, sticky_out); + spin_unlock_irqrestore(&sensors->intr_notify_lock, flags); + + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { + pr_debug("TZD update for zone %d\n", i); + thermal_zone_device_update(sensors->soc_dts[i].tzone, + THERMAL_EVENT_UNSPECIFIED); + } + } else + spin_unlock_irqrestore(&sensors->intr_notify_lock, flags); +} +EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_interrupt_handler); + +struct intel_soc_dts_sensors *intel_soc_dts_iosf_init( + enum intel_soc_dts_interrupt_type intr_type, int trip_count, + int read_only_trip_count) +{ + struct intel_soc_dts_sensors *sensors; + bool notification; + u32 tj_max; + int ret; + int i; + + if (!iosf_mbi_available()) + return ERR_PTR(-ENODEV); + + if (!trip_count || read_only_trip_count > trip_count) + return ERR_PTR(-EINVAL); + + if (get_tj_max(&tj_max)) + return ERR_PTR(-EINVAL); + + sensors = kzalloc(sizeof(*sensors), GFP_KERNEL); + if (!sensors) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&sensors->intr_notify_lock); + mutex_init(&sensors->dts_update_lock); + sensors->intr_type = intr_type; + sensors->tj_max = tj_max; + if (intr_type == INTEL_SOC_DTS_INTERRUPT_NONE) + notification = false; + else + notification = true; + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { + sensors->soc_dts[i].sensors = sensors; + ret = add_dts_thermal_zone(i, &sensors->soc_dts[i], + notification, trip_count, + read_only_trip_count); + if (ret) + goto err_free; + } + + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { + ret = update_trip_temp(&sensors->soc_dts[i], 0, 0, + THERMAL_TRIP_PASSIVE); + if (ret) + goto err_remove_zone; + + ret = update_trip_temp(&sensors->soc_dts[i], 1, 0, + THERMAL_TRIP_PASSIVE); + if (ret) + goto err_remove_zone; + } + + return sensors; +err_remove_zone: + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) + remove_dts_thermal_zone(&sensors->soc_dts[i]); + +err_free: + kfree(sensors); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_init); + +void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors) +{ + int i; + + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { + update_trip_temp(&sensors->soc_dts[i], 0, 0, 0); + update_trip_temp(&sensors->soc_dts[i], 1, 0, 0); + remove_dts_thermal_zone(&sensors->soc_dts[i]); + } + kfree(sensors); +} +EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/intel_soc_dts_iosf.h b/drivers/thermal/intel/intel_soc_dts_iosf.h new file mode 100644 index 000000000000..625e37bf93dc --- /dev/null +++ b/drivers/thermal/intel/intel_soc_dts_iosf.h @@ -0,0 +1,62 @@ +/* + * intel_soc_dts_iosf.h + * Copyright (c) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef _INTEL_SOC_DTS_IOSF_CORE_H +#define _INTEL_SOC_DTS_IOSF_CORE_H + +#include + +/* DTS0 and DTS 1 */ +#define SOC_MAX_DTS_SENSORS 2 + +enum intel_soc_dts_interrupt_type { + INTEL_SOC_DTS_INTERRUPT_NONE, + INTEL_SOC_DTS_INTERRUPT_APIC, + INTEL_SOC_DTS_INTERRUPT_MSI, + INTEL_SOC_DTS_INTERRUPT_SCI, + INTEL_SOC_DTS_INTERRUPT_SMI, +}; + +struct intel_soc_dts_sensors; + +struct intel_soc_dts_sensor_entry { + int id; + u32 temp_mask; + u32 temp_shift; + u32 store_status; + u32 trip_mask; + u32 trip_count; + enum thermal_trip_type trip_types[2]; + struct thermal_zone_device *tzone; + struct intel_soc_dts_sensors *sensors; +}; + +struct intel_soc_dts_sensors { + u32 tj_max; + spinlock_t intr_notify_lock; + struct mutex dts_update_lock; + enum intel_soc_dts_interrupt_type intr_type; + struct intel_soc_dts_sensor_entry soc_dts[SOC_MAX_DTS_SENSORS]; +}; + +struct intel_soc_dts_sensors *intel_soc_dts_iosf_init( + enum intel_soc_dts_interrupt_type intr_type, int trip_count, + int read_only_trip_count); +void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors); +void intel_soc_dts_iosf_interrupt_handler( + struct intel_soc_dts_sensors *sensors); +int intel_soc_dts_iosf_add_read_only_critical_trip( + struct intel_soc_dts_sensors *sensors, int critical_offset); +#endif diff --git a/drivers/thermal/intel/intel_soc_dts_thermal.c b/drivers/thermal/intel/intel_soc_dts_thermal.c new file mode 100644 index 000000000000..d748527d7a38 --- /dev/null +++ b/drivers/thermal/intel/intel_soc_dts_thermal.c @@ -0,0 +1,132 @@ +/* + * intel_soc_dts_thermal.c + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "intel_soc_dts_iosf.h" + +#define CRITICAL_OFFSET_FROM_TJ_MAX 5000 + +static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX; +module_param(crit_offset, int, 0644); +MODULE_PARM_DESC(crit_offset, + "Critical Temperature offset from tj max in millidegree Celsius."); + +/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */ +#define BYT_SOC_DTS_APIC_IRQ 86 + +static int soc_dts_thres_gsi; +static int soc_dts_thres_irq; +static struct intel_soc_dts_sensors *soc_dts; + +static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data) +{ + pr_debug("proc_thermal_interrupt\n"); + intel_soc_dts_iosf_interrupt_handler(soc_dts); + + return IRQ_HANDLED; +} + +static const struct x86_cpu_id soc_thermal_ids[] = { + { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT, 0, + BYT_SOC_DTS_APIC_IRQ}, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids); + +static int __init intel_soc_thermal_init(void) +{ + int err = 0; + const struct x86_cpu_id *match_cpu; + + match_cpu = x86_match_cpu(soc_thermal_ids); + if (!match_cpu) + return -ENODEV; + + /* Create a zone with 2 trips with marked as read only */ + soc_dts = intel_soc_dts_iosf_init(INTEL_SOC_DTS_INTERRUPT_APIC, 2, 1); + if (IS_ERR(soc_dts)) { + err = PTR_ERR(soc_dts); + return err; + } + + soc_dts_thres_gsi = (int)match_cpu->driver_data; + if (soc_dts_thres_gsi) { + /* + * Note the flags here MUST match the firmware defaults, rather + * then the request_irq flags, otherwise we get an EBUSY error. + */ + soc_dts_thres_irq = acpi_register_gsi(NULL, soc_dts_thres_gsi, + ACPI_LEVEL_SENSITIVE, + ACPI_ACTIVE_LOW); + if (soc_dts_thres_irq < 0) { + pr_warn("intel_soc_dts: Could not get IRQ for GSI %d, err %d\n", + soc_dts_thres_gsi, soc_dts_thres_irq); + soc_dts_thres_irq = 0; + } + } + + if (soc_dts_thres_irq) { + err = request_threaded_irq(soc_dts_thres_irq, NULL, + soc_irq_thread_fn, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "soc_dts", soc_dts); + if (err) { + /* + * Do not just error out because the user space thermal + * daemon such as DPTF may use polling instead of being + * interrupt driven. + */ + pr_warn("request_threaded_irq ret %d\n", err); + } + } + + err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts, + crit_offset); + if (err) + goto error_trips; + + return 0; + +error_trips: + if (soc_dts_thres_irq) { + free_irq(soc_dts_thres_irq, soc_dts); + acpi_unregister_gsi(soc_dts_thres_gsi); + } + intel_soc_dts_iosf_exit(soc_dts); + + return err; +} + +static void __exit intel_soc_thermal_exit(void) +{ + if (soc_dts_thres_irq) { + free_irq(soc_dts_thres_irq, soc_dts); + acpi_unregister_gsi(soc_dts_thres_gsi); + } + intel_soc_dts_iosf_exit(soc_dts); +} + +module_init(intel_soc_thermal_init) +module_exit(intel_soc_thermal_exit) + +MODULE_DESCRIPTION("Intel SoC DTS Thermal Driver"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel/x86_pkg_temp_thermal.c b/drivers/thermal/intel/x86_pkg_temp_thermal.c new file mode 100644 index 000000000000..1ef937d799e4 --- /dev/null +++ b/drivers/thermal/intel/x86_pkg_temp_thermal.c @@ -0,0 +1,558 @@ +/* + * x86_pkg_temp_thermal driver + * Copyright (c) 2013, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc. + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +* Rate control delay: Idea is to introduce denounce effect +* This should be long enough to avoid reduce events, when +* threshold is set to a temperature, which is constantly +* violated, but at the short enough to take any action. +* The action can be remove threshold or change it to next +* interesting setting. Based on experiments, in around +* every 5 seconds under load will give us a significant +* temperature change. +*/ +#define PKG_TEMP_THERMAL_NOTIFY_DELAY 5000 +static int notify_delay_ms = PKG_TEMP_THERMAL_NOTIFY_DELAY; +module_param(notify_delay_ms, int, 0644); +MODULE_PARM_DESC(notify_delay_ms, + "User space notification delay in milli seconds."); + +/* Number of trip points in thermal zone. Currently it can't +* be more than 2. MSR can allow setting and getting notifications +* for only 2 thresholds. This define enforces this, if there +* is some wrong values returned by cpuid for number of thresholds. +*/ +#define MAX_NUMBER_OF_TRIPS 2 + +struct pkg_device { + int cpu; + bool work_scheduled; + u32 tj_max; + u32 msr_pkg_therm_low; + u32 msr_pkg_therm_high; + struct delayed_work work; + struct thermal_zone_device *tzone; + struct cpumask cpumask; +}; + +static struct thermal_zone_params pkg_temp_tz_params = { + .no_hwmon = true, +}; + +/* Keep track of how many package pointers we allocated in init() */ +static int max_packages __read_mostly; +/* Array of package pointers */ +static struct pkg_device **packages; +/* Serializes interrupt notification, work and hotplug */ +static DEFINE_SPINLOCK(pkg_temp_lock); +/* Protects zone operation in the work function against hotplug removal */ +static DEFINE_MUTEX(thermal_zone_mutex); + +/* The dynamically assigned cpu hotplug state for module_exit() */ +static enum cpuhp_state pkg_thermal_hp_state __read_mostly; + +/* Debug counters to show using debugfs */ +static struct dentry *debugfs; +static unsigned int pkg_interrupt_cnt; +static unsigned int pkg_work_cnt; + +static int pkg_temp_debugfs_init(void) +{ + struct dentry *d; + + debugfs = debugfs_create_dir("pkg_temp_thermal", NULL); + if (!debugfs) + return -ENOENT; + + d = debugfs_create_u32("pkg_thres_interrupt", S_IRUGO, debugfs, + &pkg_interrupt_cnt); + if (!d) + goto err_out; + + d = debugfs_create_u32("pkg_thres_work", S_IRUGO, debugfs, + &pkg_work_cnt); + if (!d) + goto err_out; + + return 0; + +err_out: + debugfs_remove_recursive(debugfs); + return -ENOENT; +} + +/* + * Protection: + * + * - cpu hotplug: Read serialized by cpu hotplug lock + * Write must hold pkg_temp_lock + * + * - Other callsites: Must hold pkg_temp_lock + */ +static struct pkg_device *pkg_temp_thermal_get_dev(unsigned int cpu) +{ + int pkgid = topology_logical_package_id(cpu); + + if (pkgid >= 0 && pkgid < max_packages) + return packages[pkgid]; + return NULL; +} + +/* +* tj-max is is interesting because threshold is set relative to this +* temperature. +*/ +static int get_tj_max(int cpu, u32 *tj_max) +{ + u32 eax, edx, val; + int err; + + err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); + if (err) + return err; + + val = (eax >> 16) & 0xff; + *tj_max = val * 1000; + + return val ? 0 : -EINVAL; +} + +static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp) +{ + struct pkg_device *pkgdev = tzd->devdata; + u32 eax, edx; + + rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_STATUS, &eax, &edx); + if (eax & 0x80000000) { + *temp = pkgdev->tj_max - ((eax >> 16) & 0x7f) * 1000; + pr_debug("sys_get_curr_temp %d\n", *temp); + return 0; + } + return -EINVAL; +} + +static int sys_get_trip_temp(struct thermal_zone_device *tzd, + int trip, int *temp) +{ + struct pkg_device *pkgdev = tzd->devdata; + unsigned long thres_reg_value; + u32 mask, shift, eax, edx; + int ret; + + if (trip >= MAX_NUMBER_OF_TRIPS) + return -EINVAL; + + if (trip) { + mask = THERM_MASK_THRESHOLD1; + shift = THERM_SHIFT_THRESHOLD1; + } else { + mask = THERM_MASK_THRESHOLD0; + shift = THERM_SHIFT_THRESHOLD0; + } + + ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, + &eax, &edx); + if (ret < 0) + return ret; + + thres_reg_value = (eax & mask) >> shift; + if (thres_reg_value) + *temp = pkgdev->tj_max - thres_reg_value * 1000; + else + *temp = 0; + pr_debug("sys_get_trip_temp %d\n", *temp); + + return 0; +} + +static int +sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp) +{ + struct pkg_device *pkgdev = tzd->devdata; + u32 l, h, mask, shift, intr; + int ret; + + if (trip >= MAX_NUMBER_OF_TRIPS || temp >= pkgdev->tj_max) + return -EINVAL; + + ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, + &l, &h); + if (ret < 0) + return ret; + + if (trip) { + mask = THERM_MASK_THRESHOLD1; + shift = THERM_SHIFT_THRESHOLD1; + intr = THERM_INT_THRESHOLD1_ENABLE; + } else { + mask = THERM_MASK_THRESHOLD0; + shift = THERM_SHIFT_THRESHOLD0; + intr = THERM_INT_THRESHOLD0_ENABLE; + } + l &= ~mask; + /* + * When users space sets a trip temperature == 0, which is indication + * that, it is no longer interested in receiving notifications. + */ + if (!temp) { + l &= ~intr; + } else { + l |= (pkgdev->tj_max - temp)/1000 << shift; + l |= intr; + } + + return wrmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); +} + +static int sys_get_trip_type(struct thermal_zone_device *thermal, int trip, + enum thermal_trip_type *type) +{ + *type = THERMAL_TRIP_PASSIVE; + return 0; +} + +/* Thermal zone callback registry */ +static struct thermal_zone_device_ops tzone_ops = { + .get_temp = sys_get_curr_temp, + .get_trip_temp = sys_get_trip_temp, + .get_trip_type = sys_get_trip_type, + .set_trip_temp = sys_set_trip_temp, +}; + +static bool pkg_thermal_rate_control(void) +{ + return true; +} + +/* Enable threshold interrupt on local package/cpu */ +static inline void enable_pkg_thres_interrupt(void) +{ + u8 thres_0, thres_1; + u32 l, h; + + rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); + /* only enable/disable if it had valid threshold value */ + thres_0 = (l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0; + thres_1 = (l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1; + if (thres_0) + l |= THERM_INT_THRESHOLD0_ENABLE; + if (thres_1) + l |= THERM_INT_THRESHOLD1_ENABLE; + wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); +} + +/* Disable threshold interrupt on local package/cpu */ +static inline void disable_pkg_thres_interrupt(void) +{ + u32 l, h; + + rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); + + l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE); + wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); +} + +static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work) +{ + struct thermal_zone_device *tzone = NULL; + int cpu = smp_processor_id(); + struct pkg_device *pkgdev; + u64 msr_val, wr_val; + + mutex_lock(&thermal_zone_mutex); + spin_lock_irq(&pkg_temp_lock); + ++pkg_work_cnt; + + pkgdev = pkg_temp_thermal_get_dev(cpu); + if (!pkgdev) { + spin_unlock_irq(&pkg_temp_lock); + mutex_unlock(&thermal_zone_mutex); + return; + } + pkgdev->work_scheduled = false; + + rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val); + wr_val = msr_val & ~(THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1); + if (wr_val != msr_val) { + wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, wr_val); + tzone = pkgdev->tzone; + } + + enable_pkg_thres_interrupt(); + spin_unlock_irq(&pkg_temp_lock); + + /* + * If tzone is not NULL, then thermal_zone_mutex will prevent the + * concurrent removal in the cpu offline callback. + */ + if (tzone) + thermal_zone_device_update(tzone, THERMAL_EVENT_UNSPECIFIED); + + mutex_unlock(&thermal_zone_mutex); +} + +static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work) +{ + unsigned long ms = msecs_to_jiffies(notify_delay_ms); + + schedule_delayed_work_on(cpu, work, ms); +} + +static int pkg_thermal_notify(u64 msr_val) +{ + int cpu = smp_processor_id(); + struct pkg_device *pkgdev; + unsigned long flags; + + spin_lock_irqsave(&pkg_temp_lock, flags); + ++pkg_interrupt_cnt; + + disable_pkg_thres_interrupt(); + + /* Work is per package, so scheduling it once is enough. */ + pkgdev = pkg_temp_thermal_get_dev(cpu); + if (pkgdev && !pkgdev->work_scheduled) { + pkgdev->work_scheduled = true; + pkg_thermal_schedule_work(pkgdev->cpu, &pkgdev->work); + } + + spin_unlock_irqrestore(&pkg_temp_lock, flags); + return 0; +} + +static int pkg_temp_thermal_device_add(unsigned int cpu) +{ + int pkgid = topology_logical_package_id(cpu); + u32 tj_max, eax, ebx, ecx, edx; + struct pkg_device *pkgdev; + int thres_count, err; + + if (pkgid >= max_packages) + return -ENOMEM; + + cpuid(6, &eax, &ebx, &ecx, &edx); + thres_count = ebx & 0x07; + if (!thres_count) + return -ENODEV; + + thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS); + + err = get_tj_max(cpu, &tj_max); + if (err) + return err; + + pkgdev = kzalloc(sizeof(*pkgdev), GFP_KERNEL); + if (!pkgdev) + return -ENOMEM; + + INIT_DELAYED_WORK(&pkgdev->work, pkg_temp_thermal_threshold_work_fn); + pkgdev->cpu = cpu; + pkgdev->tj_max = tj_max; + pkgdev->tzone = thermal_zone_device_register("x86_pkg_temp", + thres_count, + (thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01, + pkgdev, &tzone_ops, &pkg_temp_tz_params, 0, 0); + if (IS_ERR(pkgdev->tzone)) { + err = PTR_ERR(pkgdev->tzone); + kfree(pkgdev); + return err; + } + /* Store MSR value for package thermal interrupt, to restore at exit */ + rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, pkgdev->msr_pkg_therm_low, + pkgdev->msr_pkg_therm_high); + + cpumask_set_cpu(cpu, &pkgdev->cpumask); + spin_lock_irq(&pkg_temp_lock); + packages[pkgid] = pkgdev; + spin_unlock_irq(&pkg_temp_lock); + return 0; +} + +static int pkg_thermal_cpu_offline(unsigned int cpu) +{ + struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu); + bool lastcpu, was_target; + int target; + + if (!pkgdev) + return 0; + + target = cpumask_any_but(&pkgdev->cpumask, cpu); + cpumask_clear_cpu(cpu, &pkgdev->cpumask); + lastcpu = target >= nr_cpu_ids; + /* + * Remove the sysfs files, if this is the last cpu in the package + * before doing further cleanups. + */ + if (lastcpu) { + struct thermal_zone_device *tzone = pkgdev->tzone; + + /* + * We must protect against a work function calling + * thermal_zone_update, after/while unregister. We null out + * the pointer under the zone mutex, so the worker function + * won't try to call. + */ + mutex_lock(&thermal_zone_mutex); + pkgdev->tzone = NULL; + mutex_unlock(&thermal_zone_mutex); + + thermal_zone_device_unregister(tzone); + } + + /* Protect against work and interrupts */ + spin_lock_irq(&pkg_temp_lock); + + /* + * Check whether this cpu was the current target and store the new + * one. When we drop the lock, then the interrupt notify function + * will see the new target. + */ + was_target = pkgdev->cpu == cpu; + pkgdev->cpu = target; + + /* + * If this is the last CPU in the package remove the package + * reference from the array and restore the interrupt MSR. When we + * drop the lock neither the interrupt notify function nor the + * worker will see the package anymore. + */ + if (lastcpu) { + packages[topology_logical_package_id(cpu)] = NULL; + /* After this point nothing touches the MSR anymore. */ + wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, + pkgdev->msr_pkg_therm_low, pkgdev->msr_pkg_therm_high); + } + + /* + * Check whether there is work scheduled and whether the work is + * targeted at the outgoing CPU. + */ + if (pkgdev->work_scheduled && was_target) { + /* + * To cancel the work we need to drop the lock, otherwise + * we might deadlock if the work needs to be flushed. + */ + spin_unlock_irq(&pkg_temp_lock); + cancel_delayed_work_sync(&pkgdev->work); + spin_lock_irq(&pkg_temp_lock); + /* + * If this is not the last cpu in the package and the work + * did not run after we dropped the lock above, then we + * need to reschedule the work, otherwise the interrupt + * stays disabled forever. + */ + if (!lastcpu && pkgdev->work_scheduled) + pkg_thermal_schedule_work(target, &pkgdev->work); + } + + spin_unlock_irq(&pkg_temp_lock); + + /* Final cleanup if this is the last cpu */ + if (lastcpu) + kfree(pkgdev); + return 0; +} + +static int pkg_thermal_cpu_online(unsigned int cpu) +{ + struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu); + struct cpuinfo_x86 *c = &cpu_data(cpu); + + /* Paranoia check */ + if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS)) + return -ENODEV; + + /* If the package exists, nothing to do */ + if (pkgdev) { + cpumask_set_cpu(cpu, &pkgdev->cpumask); + return 0; + } + return pkg_temp_thermal_device_add(cpu); +} + +static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = { + { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS }, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids); + +static int __init pkg_temp_thermal_init(void) +{ + int ret; + + if (!x86_match_cpu(pkg_temp_thermal_ids)) + return -ENODEV; + + max_packages = topology_max_packages(); + packages = kcalloc(max_packages, sizeof(struct pkg_device *), + GFP_KERNEL); + if (!packages) + return -ENOMEM; + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal/x86_pkg:online", + pkg_thermal_cpu_online, pkg_thermal_cpu_offline); + if (ret < 0) + goto err; + + /* Store the state for module exit */ + pkg_thermal_hp_state = ret; + + platform_thermal_package_notify = pkg_thermal_notify; + platform_thermal_package_rate_control = pkg_thermal_rate_control; + + /* Don't care if it fails */ + pkg_temp_debugfs_init(); + return 0; + +err: + kfree(packages); + return ret; +} +module_init(pkg_temp_thermal_init) + +static void __exit pkg_temp_thermal_exit(void) +{ + platform_thermal_package_notify = NULL; + platform_thermal_package_rate_control = NULL; + + cpuhp_remove_state(pkg_thermal_hp_state); + debugfs_remove_recursive(debugfs); + kfree(packages); +} +module_exit(pkg_temp_thermal_exit) + +MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel_bxt_pmic_thermal.c b/drivers/thermal/intel_bxt_pmic_thermal.c deleted file mode 100644 index 94cfd0064c43..000000000000 --- a/drivers/thermal/intel_bxt_pmic_thermal.c +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Intel Broxton PMIC thermal driver - * - * Copyright (C) 2016 Intel Corporation. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License version - * 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define BXTWC_THRM0IRQ 0x4E04 -#define BXTWC_THRM1IRQ 0x4E05 -#define BXTWC_THRM2IRQ 0x4E06 -#define BXTWC_MTHRM0IRQ 0x4E12 -#define BXTWC_MTHRM1IRQ 0x4E13 -#define BXTWC_MTHRM2IRQ 0x4E14 -#define BXTWC_STHRM0IRQ 0x4F19 -#define BXTWC_STHRM1IRQ 0x4F1A -#define BXTWC_STHRM2IRQ 0x4F1B - -struct trip_config_map { - u16 irq_reg; - u16 irq_en; - u16 evt_stat; - u8 irq_mask; - u8 irq_en_mask; - u8 evt_mask; - u8 trip_num; -}; - -struct thermal_irq_map { - char handle[20]; - int num_trips; - const struct trip_config_map *trip_config; -}; - -struct pmic_thermal_data { - const struct thermal_irq_map *maps; - int num_maps; -}; - -static const struct trip_config_map bxtwc_str0_trip_config[] = { - { - .irq_reg = BXTWC_THRM0IRQ, - .irq_mask = 0x01, - .irq_en = BXTWC_MTHRM0IRQ, - .irq_en_mask = 0x01, - .evt_stat = BXTWC_STHRM0IRQ, - .evt_mask = 0x01, - .trip_num = 0 - }, - { - .irq_reg = BXTWC_THRM0IRQ, - .irq_mask = 0x10, - .irq_en = BXTWC_MTHRM0IRQ, - .irq_en_mask = 0x10, - .evt_stat = BXTWC_STHRM0IRQ, - .evt_mask = 0x10, - .trip_num = 1 - } -}; - -static const struct trip_config_map bxtwc_str1_trip_config[] = { - { - .irq_reg = BXTWC_THRM0IRQ, - .irq_mask = 0x02, - .irq_en = BXTWC_MTHRM0IRQ, - .irq_en_mask = 0x02, - .evt_stat = BXTWC_STHRM0IRQ, - .evt_mask = 0x02, - .trip_num = 0 - }, - { - .irq_reg = BXTWC_THRM0IRQ, - .irq_mask = 0x20, - .irq_en = BXTWC_MTHRM0IRQ, - .irq_en_mask = 0x20, - .evt_stat = BXTWC_STHRM0IRQ, - .evt_mask = 0x20, - .trip_num = 1 - }, -}; - -static const struct trip_config_map bxtwc_str2_trip_config[] = { - { - .irq_reg = BXTWC_THRM0IRQ, - .irq_mask = 0x04, - .irq_en = BXTWC_MTHRM0IRQ, - .irq_en_mask = 0x04, - .evt_stat = BXTWC_STHRM0IRQ, - .evt_mask = 0x04, - .trip_num = 0 - }, - { - .irq_reg = BXTWC_THRM0IRQ, - .irq_mask = 0x40, - .irq_en = BXTWC_MTHRM0IRQ, - .irq_en_mask = 0x40, - .evt_stat = BXTWC_STHRM0IRQ, - .evt_mask = 0x40, - .trip_num = 1 - }, -}; - -static const struct trip_config_map bxtwc_str3_trip_config[] = { - { - .irq_reg = BXTWC_THRM2IRQ, - .irq_mask = 0x10, - .irq_en = BXTWC_MTHRM2IRQ, - .irq_en_mask = 0x10, - .evt_stat = BXTWC_STHRM2IRQ, - .evt_mask = 0x10, - .trip_num = 0 - }, -}; - -static const struct thermal_irq_map bxtwc_thermal_irq_map[] = { - { - .handle = "STR0", - .trip_config = bxtwc_str0_trip_config, - .num_trips = ARRAY_SIZE(bxtwc_str0_trip_config), - }, - { - .handle = "STR1", - .trip_config = bxtwc_str1_trip_config, - .num_trips = ARRAY_SIZE(bxtwc_str1_trip_config), - }, - { - .handle = "STR2", - .trip_config = bxtwc_str2_trip_config, - .num_trips = ARRAY_SIZE(bxtwc_str2_trip_config), - }, - { - .handle = "STR3", - .trip_config = bxtwc_str3_trip_config, - .num_trips = ARRAY_SIZE(bxtwc_str3_trip_config), - }, -}; - -static const struct pmic_thermal_data bxtwc_thermal_data = { - .maps = bxtwc_thermal_irq_map, - .num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map), -}; - -static irqreturn_t pmic_thermal_irq_handler(int irq, void *data) -{ - struct platform_device *pdev = data; - struct thermal_zone_device *tzd; - struct pmic_thermal_data *td; - struct intel_soc_pmic *pmic; - struct regmap *regmap; - u8 reg_val, mask, irq_stat; - u16 reg, evt_stat_reg; - int i, j, ret; - - pmic = dev_get_drvdata(pdev->dev.parent); - regmap = pmic->regmap; - td = (struct pmic_thermal_data *) - platform_get_device_id(pdev)->driver_data; - - /* Resolve thermal irqs */ - for (i = 0; i < td->num_maps; i++) { - for (j = 0; j < td->maps[i].num_trips; j++) { - reg = td->maps[i].trip_config[j].irq_reg; - mask = td->maps[i].trip_config[j].irq_mask; - /* - * Read the irq register to resolve whether the - * interrupt was triggered for this sensor - */ - if (regmap_read(regmap, reg, &ret)) - return IRQ_HANDLED; - - reg_val = (u8)ret; - irq_stat = ((u8)ret & mask); - - if (!irq_stat) - continue; - - /* - * Read the status register to find out what - * event occurred i.e a high or a low - */ - evt_stat_reg = td->maps[i].trip_config[j].evt_stat; - if (regmap_read(regmap, evt_stat_reg, &ret)) - return IRQ_HANDLED; - - tzd = thermal_zone_get_zone_by_name(td->maps[i].handle); - if (!IS_ERR(tzd)) - thermal_zone_device_update(tzd, - THERMAL_EVENT_UNSPECIFIED); - - /* Clear the appropriate irq */ - regmap_write(regmap, reg, reg_val & mask); - } - } - - return IRQ_HANDLED; -} - -static int pmic_thermal_probe(struct platform_device *pdev) -{ - struct regmap_irq_chip_data *regmap_irq_chip; - struct pmic_thermal_data *thermal_data; - int ret, irq, virq, i, j, pmic_irq_count; - struct intel_soc_pmic *pmic; - struct regmap *regmap; - struct device *dev; - u16 reg; - u8 mask; - - dev = &pdev->dev; - pmic = dev_get_drvdata(pdev->dev.parent); - if (!pmic) { - dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n"); - return -ENODEV; - } - - thermal_data = (struct pmic_thermal_data *) - platform_get_device_id(pdev)->driver_data; - if (!thermal_data) { - dev_err(dev, "No thermal data initialized!!\n"); - return -ENODEV; - } - - regmap = pmic->regmap; - regmap_irq_chip = pmic->irq_chip_data; - - pmic_irq_count = 0; - while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) { - virq = regmap_irq_get_virq(regmap_irq_chip, irq); - if (virq < 0) { - dev_err(dev, "failed to get virq by irq %d\n", irq); - return virq; - } - - ret = devm_request_threaded_irq(&pdev->dev, virq, - NULL, pmic_thermal_irq_handler, - IRQF_ONESHOT, "pmic_thermal", pdev); - - if (ret) { - dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); - return ret; - } - pmic_irq_count++; - } - - /* Enable thermal interrupts */ - for (i = 0; i < thermal_data->num_maps; i++) { - for (j = 0; j < thermal_data->maps[i].num_trips; j++) { - reg = thermal_data->maps[i].trip_config[j].irq_en; - mask = thermal_data->maps[i].trip_config[j].irq_en_mask; - ret = regmap_update_bits(regmap, reg, mask, 0x00); - if (ret) - return ret; - } - } - - return 0; -} - -static const struct platform_device_id pmic_thermal_id_table[] = { - { - .name = "bxt_wcove_thermal", - .driver_data = (kernel_ulong_t)&bxtwc_thermal_data, - }, - {}, -}; - -static struct platform_driver pmic_thermal_driver = { - .probe = pmic_thermal_probe, - .driver = { - .name = "pmic_thermal", - }, - .id_table = pmic_thermal_id_table, -}; - -MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table); -module_platform_driver(pmic_thermal_driver); - -MODULE_AUTHOR("Yegnesh S Iyer "); -MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel_pch_thermal.c b/drivers/thermal/intel_pch_thermal.c deleted file mode 100644 index 8a7f69b4b022..000000000000 --- a/drivers/thermal/intel_pch_thermal.c +++ /dev/null @@ -1,432 +0,0 @@ -/* intel_pch_thermal.c - Intel PCH Thermal driver - * - * Copyright (c) 2015, Intel Corporation. - * - * Authors: - * Tushar Dave - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -/* Intel PCH thermal Device IDs */ -#define PCH_THERMAL_DID_HSW_1 0x9C24 /* Haswell PCH */ -#define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */ -#define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */ -#define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */ -#define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */ -#define PCH_THERMAL_DID_CNL 0x9Df9 /* CNL PCH */ -#define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */ - -/* Wildcat Point-LP PCH Thermal registers */ -#define WPT_TEMP 0x0000 /* Temperature */ -#define WPT_TSC 0x04 /* Thermal Sensor Control */ -#define WPT_TSS 0x06 /* Thermal Sensor Status */ -#define WPT_TSEL 0x08 /* Thermal Sensor Enable and Lock */ -#define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */ -#define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */ -#define WPT_CTT 0x0010 /* Catastrophic Trip Point */ -#define WPT_TAHV 0x0014 /* Thermal Alert High Value */ -#define WPT_TALV 0x0018 /* Thermal Alert Low Value */ -#define WPT_TL 0x00000040 /* Throttle Value */ -#define WPT_PHL 0x0060 /* PCH Hot Level */ -#define WPT_PHLC 0x62 /* PHL Control */ -#define WPT_TAS 0x80 /* Thermal Alert Status */ -#define WPT_TSPIEN 0x82 /* PCI Interrupt Event Enables */ -#define WPT_TSGPEN 0x84 /* General Purpose Event Enables */ - -/* Wildcat Point-LP PCH Thermal Register bit definitions */ -#define WPT_TEMP_TSR 0x01ff /* Temp TS Reading */ -#define WPT_TSC_CPDE 0x01 /* Catastrophic Power-Down Enable */ -#define WPT_TSS_TSDSS 0x10 /* Thermal Sensor Dynamic Shutdown Status */ -#define WPT_TSS_GPES 0x08 /* GPE status */ -#define WPT_TSEL_ETS 0x01 /* Enable TS */ -#define WPT_TSEL_PLDB 0x80 /* TSEL Policy Lock-Down Bit */ -#define WPT_TL_TOL 0x000001FF /* T0 Level */ -#define WPT_TL_T1L 0x1ff00000 /* T1 Level */ -#define WPT_TL_TTEN 0x20000000 /* TT Enable */ - -static char driver_name[] = "Intel PCH thermal driver"; - -struct pch_thermal_device { - void __iomem *hw_base; - const struct pch_dev_ops *ops; - struct pci_dev *pdev; - struct thermal_zone_device *tzd; - int crt_trip_id; - unsigned long crt_temp; - int hot_trip_id; - unsigned long hot_temp; - int psv_trip_id; - unsigned long psv_temp; - bool bios_enabled; -}; - -#ifdef CONFIG_ACPI - -/* - * On some platforms, there is a companion ACPI device, which adds - * passive trip temperature using _PSV method. There is no specific - * passive temperature setting in MMIO interface of this PCI device. - */ -static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, - int *nr_trips) -{ - struct acpi_device *adev; - - ptd->psv_trip_id = -1; - - adev = ACPI_COMPANION(&ptd->pdev->dev); - if (adev) { - unsigned long long r; - acpi_status status; - - status = acpi_evaluate_integer(adev->handle, "_PSV", NULL, - &r); - if (ACPI_SUCCESS(status)) { - unsigned long trip_temp; - - trip_temp = DECI_KELVIN_TO_MILLICELSIUS(r); - if (trip_temp) { - ptd->psv_temp = trip_temp; - ptd->psv_trip_id = *nr_trips; - ++(*nr_trips); - } - } - } -} -#else -static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, - int *nr_trips) -{ - ptd->psv_trip_id = -1; - -} -#endif - -static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips) -{ - u8 tsel; - u16 trip_temp; - - *nr_trips = 0; - - /* Check if BIOS has already enabled thermal sensor */ - if (WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL)) { - ptd->bios_enabled = true; - goto read_trips; - } - - tsel = readb(ptd->hw_base + WPT_TSEL); - /* - * When TSEL's Policy Lock-Down bit is 1, TSEL become RO. - * If so, thermal sensor cannot enable. Bail out. - */ - if (tsel & WPT_TSEL_PLDB) { - dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n"); - return -ENODEV; - } - - writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL); - if (!(WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL))) { - dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n"); - return -ENODEV; - } - -read_trips: - ptd->crt_trip_id = -1; - trip_temp = readw(ptd->hw_base + WPT_CTT); - trip_temp &= 0x1FF; - if (trip_temp) { - /* Resolution of 1/2 degree C and an offset of -50C */ - ptd->crt_temp = trip_temp * 1000 / 2 - 50000; - ptd->crt_trip_id = 0; - ++(*nr_trips); - } - - ptd->hot_trip_id = -1; - trip_temp = readw(ptd->hw_base + WPT_PHL); - trip_temp &= 0x1FF; - if (trip_temp) { - /* Resolution of 1/2 degree C and an offset of -50C */ - ptd->hot_temp = trip_temp * 1000 / 2 - 50000; - ptd->hot_trip_id = *nr_trips; - ++(*nr_trips); - } - - pch_wpt_add_acpi_psv_trip(ptd, nr_trips); - - return 0; -} - -static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp) -{ - u16 wpt_temp; - - wpt_temp = WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP); - - /* Resolution of 1/2 degree C and an offset of -50C */ - *temp = (wpt_temp * 1000 / 2 - 50000); - - return 0; -} - -static int pch_wpt_suspend(struct pch_thermal_device *ptd) -{ - u8 tsel; - - if (ptd->bios_enabled) - return 0; - - tsel = readb(ptd->hw_base + WPT_TSEL); - - writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL); - - return 0; -} - -static int pch_wpt_resume(struct pch_thermal_device *ptd) -{ - u8 tsel; - - if (ptd->bios_enabled) - return 0; - - tsel = readb(ptd->hw_base + WPT_TSEL); - - writeb(tsel | WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL); - - return 0; -} - -struct pch_dev_ops { - int (*hw_init)(struct pch_thermal_device *ptd, int *nr_trips); - int (*get_temp)(struct pch_thermal_device *ptd, int *temp); - int (*suspend)(struct pch_thermal_device *ptd); - int (*resume)(struct pch_thermal_device *ptd); -}; - - -/* dev ops for Wildcat Point */ -static const struct pch_dev_ops pch_dev_ops_wpt = { - .hw_init = pch_wpt_init, - .get_temp = pch_wpt_get_temp, - .suspend = pch_wpt_suspend, - .resume = pch_wpt_resume, -}; - -static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp) -{ - struct pch_thermal_device *ptd = tzd->devdata; - - return ptd->ops->get_temp(ptd, temp); -} - -static int pch_get_trip_type(struct thermal_zone_device *tzd, int trip, - enum thermal_trip_type *type) -{ - struct pch_thermal_device *ptd = tzd->devdata; - - if (ptd->crt_trip_id == trip) - *type = THERMAL_TRIP_CRITICAL; - else if (ptd->hot_trip_id == trip) - *type = THERMAL_TRIP_HOT; - else if (ptd->psv_trip_id == trip) - *type = THERMAL_TRIP_PASSIVE; - else - return -EINVAL; - - return 0; -} - -static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *temp) -{ - struct pch_thermal_device *ptd = tzd->devdata; - - if (ptd->crt_trip_id == trip) - *temp = ptd->crt_temp; - else if (ptd->hot_trip_id == trip) - *temp = ptd->hot_temp; - else if (ptd->psv_trip_id == trip) - *temp = ptd->psv_temp; - else - return -EINVAL; - - return 0; -} - -static struct thermal_zone_device_ops tzd_ops = { - .get_temp = pch_thermal_get_temp, - .get_trip_type = pch_get_trip_type, - .get_trip_temp = pch_get_trip_temp, -}; - -enum board_ids { - board_hsw, - board_wpt, - board_skl, - board_cnl, -}; - -static const struct board_info { - const char *name; - const struct pch_dev_ops *ops; -} board_info[] = { - [board_hsw] = { - .name = "pch_haswell", - .ops = &pch_dev_ops_wpt, - }, - [board_wpt] = { - .name = "pch_wildcat_point", - .ops = &pch_dev_ops_wpt, - }, - [board_skl] = { - .name = "pch_skylake", - .ops = &pch_dev_ops_wpt, - }, - [board_cnl] = { - .name = "pch_cannonlake", - .ops = &pch_dev_ops_wpt, - }, -}; - -static int intel_pch_thermal_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - enum board_ids board_id = id->driver_data; - const struct board_info *bi = &board_info[board_id]; - struct pch_thermal_device *ptd; - int err; - int nr_trips; - - ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL); - if (!ptd) - return -ENOMEM; - - ptd->ops = bi->ops; - - pci_set_drvdata(pdev, ptd); - ptd->pdev = pdev; - - err = pci_enable_device(pdev); - if (err) { - dev_err(&pdev->dev, "failed to enable pci device\n"); - return err; - } - - err = pci_request_regions(pdev, driver_name); - if (err) { - dev_err(&pdev->dev, "failed to request pci region\n"); - goto error_disable; - } - - ptd->hw_base = pci_ioremap_bar(pdev, 0); - if (!ptd->hw_base) { - err = -ENOMEM; - dev_err(&pdev->dev, "failed to map mem base\n"); - goto error_release; - } - - err = ptd->ops->hw_init(ptd, &nr_trips); - if (err) - goto error_cleanup; - - ptd->tzd = thermal_zone_device_register(bi->name, nr_trips, 0, ptd, - &tzd_ops, NULL, 0, 0); - if (IS_ERR(ptd->tzd)) { - dev_err(&pdev->dev, "Failed to register thermal zone %s\n", - bi->name); - err = PTR_ERR(ptd->tzd); - goto error_cleanup; - } - - return 0; - -error_cleanup: - iounmap(ptd->hw_base); -error_release: - pci_release_regions(pdev); -error_disable: - pci_disable_device(pdev); - dev_err(&pdev->dev, "pci device failed to probe\n"); - return err; -} - -static void intel_pch_thermal_remove(struct pci_dev *pdev) -{ - struct pch_thermal_device *ptd = pci_get_drvdata(pdev); - - thermal_zone_device_unregister(ptd->tzd); - iounmap(ptd->hw_base); - pci_set_drvdata(pdev, NULL); - pci_release_region(pdev, 0); - pci_disable_device(pdev); -} - -static int intel_pch_thermal_suspend(struct device *device) -{ - struct pci_dev *pdev = to_pci_dev(device); - struct pch_thermal_device *ptd = pci_get_drvdata(pdev); - - return ptd->ops->suspend(ptd); -} - -static int intel_pch_thermal_resume(struct device *device) -{ - struct pci_dev *pdev = to_pci_dev(device); - struct pch_thermal_device *ptd = pci_get_drvdata(pdev); - - return ptd->ops->resume(ptd); -} - -static const struct pci_device_id intel_pch_thermal_id[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1), - .driver_data = board_hsw, }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2), - .driver_data = board_hsw, }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT), - .driver_data = board_wpt, }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL), - .driver_data = board_skl, }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H), - .driver_data = board_skl, }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL), - .driver_data = board_cnl, }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H), - .driver_data = board_cnl, }, - { 0, }, -}; -MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id); - -static const struct dev_pm_ops intel_pch_pm_ops = { - .suspend = intel_pch_thermal_suspend, - .resume = intel_pch_thermal_resume, -}; - -static struct pci_driver intel_pch_thermal_driver = { - .name = "intel_pch_thermal", - .id_table = intel_pch_thermal_id, - .probe = intel_pch_thermal_probe, - .remove = intel_pch_thermal_remove, - .driver.pm = &intel_pch_pm_ops, -}; - -module_pci_driver(intel_pch_thermal_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel PCH Thermal driver"); diff --git a/drivers/thermal/intel_powerclamp.c b/drivers/thermal/intel_powerclamp.c deleted file mode 100644 index cde891c54cde..000000000000 --- a/drivers/thermal/intel_powerclamp.c +++ /dev/null @@ -1,815 +0,0 @@ -/* - * intel_powerclamp.c - package c-state idle injection - * - * Copyright (c) 2012, Intel Corporation. - * - * Authors: - * Arjan van de Ven - * Jacob Pan - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - * - * - * TODO: - * 1. better handle wakeup from external interrupts, currently a fixed - * compensation is added to clamping duration when excessive amount - * of wakeups are observed during idle time. the reason is that in - * case of external interrupts without need for ack, clamping down - * cpu in non-irq context does not reduce irq. for majority of the - * cases, clamping down cpu does help reduce irq as well, we should - * be able to differentiate the two cases and give a quantitative - * solution for the irqs that we can control. perhaps based on - * get_cpu_iowait_time_us() - * - * 2. synchronization with other hw blocks - * - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#define MAX_TARGET_RATIO (50U) -/* For each undisturbed clamping period (no extra wake ups during idle time), - * we increment the confidence counter for the given target ratio. - * CONFIDENCE_OK defines the level where runtime calibration results are - * valid. - */ -#define CONFIDENCE_OK (3) -/* Default idle injection duration, driver adjust sleep time to meet target - * idle ratio. Similar to frequency modulation. - */ -#define DEFAULT_DURATION_JIFFIES (6) - -static unsigned int target_mwait; -static struct dentry *debug_dir; - -/* user selected target */ -static unsigned int set_target_ratio; -static unsigned int current_ratio; -static bool should_skip; -static bool reduce_irq; -static atomic_t idle_wakeup_counter; -static unsigned int control_cpu; /* The cpu assigned to collect stat and update - * control parameters. default to BSP but BSP - * can be offlined. - */ -static bool clamping; - -static const struct sched_param sparam = { - .sched_priority = MAX_USER_RT_PRIO / 2, -}; -struct powerclamp_worker_data { - struct kthread_worker *worker; - struct kthread_work balancing_work; - struct kthread_delayed_work idle_injection_work; - unsigned int cpu; - unsigned int count; - unsigned int guard; - unsigned int window_size_now; - unsigned int target_ratio; - unsigned int duration_jiffies; - bool clamping; -}; - -static struct powerclamp_worker_data * __percpu worker_data; -static struct thermal_cooling_device *cooling_dev; -static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu - * clamping kthread worker - */ - -static unsigned int duration; -static unsigned int pkg_cstate_ratio_cur; -static unsigned int window_size; - -static int duration_set(const char *arg, const struct kernel_param *kp) -{ - int ret = 0; - unsigned long new_duration; - - ret = kstrtoul(arg, 10, &new_duration); - if (ret) - goto exit; - if (new_duration > 25 || new_duration < 6) { - pr_err("Out of recommended range %lu, between 6-25ms\n", - new_duration); - ret = -EINVAL; - } - - duration = clamp(new_duration, 6ul, 25ul); - smp_mb(); - -exit: - - return ret; -} - -static const struct kernel_param_ops duration_ops = { - .set = duration_set, - .get = param_get_int, -}; - - -module_param_cb(duration, &duration_ops, &duration, 0644); -MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec."); - -struct powerclamp_calibration_data { - unsigned long confidence; /* used for calibration, basically a counter - * gets incremented each time a clamping - * period is completed without extra wakeups - * once that counter is reached given level, - * compensation is deemed usable. - */ - unsigned long steady_comp; /* steady state compensation used when - * no extra wakeups occurred. - */ - unsigned long dynamic_comp; /* compensate excessive wakeup from idle - * mostly from external interrupts. - */ -}; - -static struct powerclamp_calibration_data cal_data[MAX_TARGET_RATIO]; - -static int window_size_set(const char *arg, const struct kernel_param *kp) -{ - int ret = 0; - unsigned long new_window_size; - - ret = kstrtoul(arg, 10, &new_window_size); - if (ret) - goto exit_win; - if (new_window_size > 10 || new_window_size < 2) { - pr_err("Out of recommended window size %lu, between 2-10\n", - new_window_size); - ret = -EINVAL; - } - - window_size = clamp(new_window_size, 2ul, 10ul); - smp_mb(); - -exit_win: - - return ret; -} - -static const struct kernel_param_ops window_size_ops = { - .set = window_size_set, - .get = param_get_int, -}; - -module_param_cb(window_size, &window_size_ops, &window_size, 0644); -MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n" - "\tpowerclamp controls idle ratio within this window. larger\n" - "\twindow size results in slower response time but more smooth\n" - "\tclamping results. default to 2."); - -static void find_target_mwait(void) -{ - unsigned int eax, ebx, ecx, edx; - unsigned int highest_cstate = 0; - unsigned int highest_subcstate = 0; - int i; - - if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF) - return; - - cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx); - - if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) || - !(ecx & CPUID5_ECX_INTERRUPT_BREAK)) - return; - - edx >>= MWAIT_SUBSTATE_SIZE; - for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) { - if (edx & MWAIT_SUBSTATE_MASK) { - highest_cstate = i; - highest_subcstate = edx & MWAIT_SUBSTATE_MASK; - } - } - target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) | - (highest_subcstate - 1); - -} - -struct pkg_cstate_info { - bool skip; - int msr_index; - int cstate_id; -}; - -#define PKG_CSTATE_INIT(id) { \ - .msr_index = MSR_PKG_C##id##_RESIDENCY, \ - .cstate_id = id \ - } - -static struct pkg_cstate_info pkg_cstates[] = { - PKG_CSTATE_INIT(2), - PKG_CSTATE_INIT(3), - PKG_CSTATE_INIT(6), - PKG_CSTATE_INIT(7), - PKG_CSTATE_INIT(8), - PKG_CSTATE_INIT(9), - PKG_CSTATE_INIT(10), - {NULL}, -}; - -static bool has_pkg_state_counter(void) -{ - u64 val; - struct pkg_cstate_info *info = pkg_cstates; - - /* check if any one of the counter msrs exists */ - while (info->msr_index) { - if (!rdmsrl_safe(info->msr_index, &val)) - return true; - info++; - } - - return false; -} - -static u64 pkg_state_counter(void) -{ - u64 val; - u64 count = 0; - struct pkg_cstate_info *info = pkg_cstates; - - while (info->msr_index) { - if (!info->skip) { - if (!rdmsrl_safe(info->msr_index, &val)) - count += val; - else - info->skip = true; - } - info++; - } - - return count; -} - -static unsigned int get_compensation(int ratio) -{ - unsigned int comp = 0; - - /* we only use compensation if all adjacent ones are good */ - if (ratio == 1 && - cal_data[ratio].confidence >= CONFIDENCE_OK && - cal_data[ratio + 1].confidence >= CONFIDENCE_OK && - cal_data[ratio + 2].confidence >= CONFIDENCE_OK) { - comp = (cal_data[ratio].steady_comp + - cal_data[ratio + 1].steady_comp + - cal_data[ratio + 2].steady_comp) / 3; - } else if (ratio == MAX_TARGET_RATIO - 1 && - cal_data[ratio].confidence >= CONFIDENCE_OK && - cal_data[ratio - 1].confidence >= CONFIDENCE_OK && - cal_data[ratio - 2].confidence >= CONFIDENCE_OK) { - comp = (cal_data[ratio].steady_comp + - cal_data[ratio - 1].steady_comp + - cal_data[ratio - 2].steady_comp) / 3; - } else if (cal_data[ratio].confidence >= CONFIDENCE_OK && - cal_data[ratio - 1].confidence >= CONFIDENCE_OK && - cal_data[ratio + 1].confidence >= CONFIDENCE_OK) { - comp = (cal_data[ratio].steady_comp + - cal_data[ratio - 1].steady_comp + - cal_data[ratio + 1].steady_comp) / 3; - } - - /* REVISIT: simple penalty of double idle injection */ - if (reduce_irq) - comp = ratio; - /* do not exceed limit */ - if (comp + ratio >= MAX_TARGET_RATIO) - comp = MAX_TARGET_RATIO - ratio - 1; - - return comp; -} - -static void adjust_compensation(int target_ratio, unsigned int win) -{ - int delta; - struct powerclamp_calibration_data *d = &cal_data[target_ratio]; - - /* - * adjust compensations if confidence level has not been reached or - * there are too many wakeups during the last idle injection period, we - * cannot trust the data for compensation. - */ - if (d->confidence >= CONFIDENCE_OK || - atomic_read(&idle_wakeup_counter) > - win * num_online_cpus()) - return; - - delta = set_target_ratio - current_ratio; - /* filter out bad data */ - if (delta >= 0 && delta <= (1+target_ratio/10)) { - if (d->steady_comp) - d->steady_comp = - roundup(delta+d->steady_comp, 2)/2; - else - d->steady_comp = delta; - d->confidence++; - } -} - -static bool powerclamp_adjust_controls(unsigned int target_ratio, - unsigned int guard, unsigned int win) -{ - static u64 msr_last, tsc_last; - u64 msr_now, tsc_now; - u64 val64; - - /* check result for the last window */ - msr_now = pkg_state_counter(); - tsc_now = rdtsc(); - - /* calculate pkg cstate vs tsc ratio */ - if (!msr_last || !tsc_last) - current_ratio = 1; - else if (tsc_now-tsc_last) { - val64 = 100*(msr_now-msr_last); - do_div(val64, (tsc_now-tsc_last)); - current_ratio = val64; - } - - /* update record */ - msr_last = msr_now; - tsc_last = tsc_now; - - adjust_compensation(target_ratio, win); - /* - * too many external interrupts, set flag such - * that we can take measure later. - */ - reduce_irq = atomic_read(&idle_wakeup_counter) >= - 2 * win * num_online_cpus(); - - atomic_set(&idle_wakeup_counter, 0); - /* if we are above target+guard, skip */ - return set_target_ratio + guard <= current_ratio; -} - -static void clamp_balancing_func(struct kthread_work *work) -{ - struct powerclamp_worker_data *w_data; - int sleeptime; - unsigned long target_jiffies; - unsigned int compensated_ratio; - int interval; /* jiffies to sleep for each attempt */ - - w_data = container_of(work, struct powerclamp_worker_data, - balancing_work); - - /* - * make sure user selected ratio does not take effect until - * the next round. adjust target_ratio if user has changed - * target such that we can converge quickly. - */ - w_data->target_ratio = READ_ONCE(set_target_ratio); - w_data->guard = 1 + w_data->target_ratio / 20; - w_data->window_size_now = window_size; - w_data->duration_jiffies = msecs_to_jiffies(duration); - w_data->count++; - - /* - * systems may have different ability to enter package level - * c-states, thus we need to compensate the injected idle ratio - * to achieve the actual target reported by the HW. - */ - compensated_ratio = w_data->target_ratio + - get_compensation(w_data->target_ratio); - if (compensated_ratio <= 0) - compensated_ratio = 1; - interval = w_data->duration_jiffies * 100 / compensated_ratio; - - /* align idle time */ - target_jiffies = roundup(jiffies, interval); - sleeptime = target_jiffies - jiffies; - if (sleeptime <= 0) - sleeptime = 1; - - if (clamping && w_data->clamping && cpu_online(w_data->cpu)) - kthread_queue_delayed_work(w_data->worker, - &w_data->idle_injection_work, - sleeptime); -} - -static void clamp_idle_injection_func(struct kthread_work *work) -{ - struct powerclamp_worker_data *w_data; - - w_data = container_of(work, struct powerclamp_worker_data, - idle_injection_work.work); - - /* - * only elected controlling cpu can collect stats and update - * control parameters. - */ - if (w_data->cpu == control_cpu && - !(w_data->count % w_data->window_size_now)) { - should_skip = - powerclamp_adjust_controls(w_data->target_ratio, - w_data->guard, - w_data->window_size_now); - smp_mb(); - } - - if (should_skip) - goto balance; - - play_idle(jiffies_to_msecs(w_data->duration_jiffies)); - -balance: - if (clamping && w_data->clamping && cpu_online(w_data->cpu)) - kthread_queue_work(w_data->worker, &w_data->balancing_work); -} - -/* - * 1 HZ polling while clamping is active, useful for userspace - * to monitor actual idle ratio. - */ -static void poll_pkg_cstate(struct work_struct *dummy); -static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate); -static void poll_pkg_cstate(struct work_struct *dummy) -{ - static u64 msr_last; - static u64 tsc_last; - - u64 msr_now; - u64 tsc_now; - u64 val64; - - msr_now = pkg_state_counter(); - tsc_now = rdtsc(); - - /* calculate pkg cstate vs tsc ratio */ - if (!msr_last || !tsc_last) - pkg_cstate_ratio_cur = 1; - else { - if (tsc_now - tsc_last) { - val64 = 100 * (msr_now - msr_last); - do_div(val64, (tsc_now - tsc_last)); - pkg_cstate_ratio_cur = val64; - } - } - - /* update record */ - msr_last = msr_now; - tsc_last = tsc_now; - - if (true == clamping) - schedule_delayed_work(&poll_pkg_cstate_work, HZ); -} - -static void start_power_clamp_worker(unsigned long cpu) -{ - struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu); - struct kthread_worker *worker; - - worker = kthread_create_worker_on_cpu(cpu, 0, "kidle_inject/%ld", cpu); - if (IS_ERR(worker)) - return; - - w_data->worker = worker; - w_data->count = 0; - w_data->cpu = cpu; - w_data->clamping = true; - set_bit(cpu, cpu_clamping_mask); - sched_setscheduler(worker->task, SCHED_FIFO, &sparam); - kthread_init_work(&w_data->balancing_work, clamp_balancing_func); - kthread_init_delayed_work(&w_data->idle_injection_work, - clamp_idle_injection_func); - kthread_queue_work(w_data->worker, &w_data->balancing_work); -} - -static void stop_power_clamp_worker(unsigned long cpu) -{ - struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu); - - if (!w_data->worker) - return; - - w_data->clamping = false; - /* - * Make sure that all works that get queued after this point see - * the clamping disabled. The counter part is not needed because - * there is an implicit memory barrier when the queued work - * is proceed. - */ - smp_wmb(); - kthread_cancel_work_sync(&w_data->balancing_work); - kthread_cancel_delayed_work_sync(&w_data->idle_injection_work); - /* - * The balancing work still might be queued here because - * the handling of the "clapming" variable, cancel, and queue - * operations are not synchronized via a lock. But it is not - * a big deal. The balancing work is fast and destroy kthread - * will wait for it. - */ - clear_bit(w_data->cpu, cpu_clamping_mask); - kthread_destroy_worker(w_data->worker); - - w_data->worker = NULL; -} - -static int start_power_clamp(void) -{ - unsigned long cpu; - - set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1); - /* prevent cpu hotplug */ - get_online_cpus(); - - /* prefer BSP */ - control_cpu = 0; - if (!cpu_online(control_cpu)) - control_cpu = smp_processor_id(); - - clamping = true; - schedule_delayed_work(&poll_pkg_cstate_work, 0); - - /* start one kthread worker per online cpu */ - for_each_online_cpu(cpu) { - start_power_clamp_worker(cpu); - } - put_online_cpus(); - - return 0; -} - -static void end_power_clamp(void) -{ - int i; - - /* - * Block requeuing in all the kthread workers. They will flush and - * stop faster. - */ - clamping = false; - if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) { - for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) { - pr_debug("clamping worker for cpu %d alive, destroy\n", - i); - stop_power_clamp_worker(i); - } - } -} - -static int powerclamp_cpu_online(unsigned int cpu) -{ - if (clamping == false) - return 0; - start_power_clamp_worker(cpu); - /* prefer BSP as controlling CPU */ - if (cpu == 0) { - control_cpu = 0; - smp_mb(); - } - return 0; -} - -static int powerclamp_cpu_predown(unsigned int cpu) -{ - if (clamping == false) - return 0; - - stop_power_clamp_worker(cpu); - if (cpu != control_cpu) - return 0; - - control_cpu = cpumask_first(cpu_online_mask); - if (control_cpu == cpu) - control_cpu = cpumask_next(cpu, cpu_online_mask); - smp_mb(); - return 0; -} - -static int powerclamp_get_max_state(struct thermal_cooling_device *cdev, - unsigned long *state) -{ - *state = MAX_TARGET_RATIO; - - return 0; -} - -static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev, - unsigned long *state) -{ - if (true == clamping) - *state = pkg_cstate_ratio_cur; - else - /* to save power, do not poll idle ratio while not clamping */ - *state = -1; /* indicates invalid state */ - - return 0; -} - -static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev, - unsigned long new_target_ratio) -{ - int ret = 0; - - new_target_ratio = clamp(new_target_ratio, 0UL, - (unsigned long) (MAX_TARGET_RATIO-1)); - if (set_target_ratio == 0 && new_target_ratio > 0) { - pr_info("Start idle injection to reduce power\n"); - set_target_ratio = new_target_ratio; - ret = start_power_clamp(); - goto exit_set; - } else if (set_target_ratio > 0 && new_target_ratio == 0) { - pr_info("Stop forced idle injection\n"); - end_power_clamp(); - set_target_ratio = 0; - } else /* adjust currently running */ { - set_target_ratio = new_target_ratio; - /* make new set_target_ratio visible to other cpus */ - smp_mb(); - } - -exit_set: - return ret; -} - -/* bind to generic thermal layer as cooling device*/ -static struct thermal_cooling_device_ops powerclamp_cooling_ops = { - .get_max_state = powerclamp_get_max_state, - .get_cur_state = powerclamp_get_cur_state, - .set_cur_state = powerclamp_set_cur_state, -}; - -static const struct x86_cpu_id __initconst intel_powerclamp_ids[] = { - { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_MWAIT }, - {} -}; -MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); - -static int __init powerclamp_probe(void) -{ - - if (!x86_match_cpu(intel_powerclamp_ids)) { - pr_err("CPU does not support MWAIT\n"); - return -ENODEV; - } - - /* The goal for idle time alignment is to achieve package cstate. */ - if (!has_pkg_state_counter()) { - pr_info("No package C-state available\n"); - return -ENODEV; - } - - /* find the deepest mwait value */ - find_target_mwait(); - - return 0; -} - -static int powerclamp_debug_show(struct seq_file *m, void *unused) -{ - int i = 0; - - seq_printf(m, "controlling cpu: %d\n", control_cpu); - seq_printf(m, "pct confidence steady dynamic (compensation)\n"); - for (i = 0; i < MAX_TARGET_RATIO; i++) { - seq_printf(m, "%d\t%lu\t%lu\t%lu\n", - i, - cal_data[i].confidence, - cal_data[i].steady_comp, - cal_data[i].dynamic_comp); - } - - return 0; -} - -static int powerclamp_debug_open(struct inode *inode, - struct file *file) -{ - return single_open(file, powerclamp_debug_show, inode->i_private); -} - -static const struct file_operations powerclamp_debug_fops = { - .open = powerclamp_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static inline void powerclamp_create_debug_files(void) -{ - debug_dir = debugfs_create_dir("intel_powerclamp", NULL); - if (!debug_dir) - return; - - if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir, - cal_data, &powerclamp_debug_fops)) - goto file_error; - - return; - -file_error: - debugfs_remove_recursive(debug_dir); -} - -static enum cpuhp_state hp_state; - -static int __init powerclamp_init(void) -{ - int retval; - int bitmap_size; - - bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long); - cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL); - if (!cpu_clamping_mask) - return -ENOMEM; - - /* probe cpu features and ids here */ - retval = powerclamp_probe(); - if (retval) - goto exit_free; - - /* set default limit, maybe adjusted during runtime based on feedback */ - window_size = 2; - retval = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, - "thermal/intel_powerclamp:online", - powerclamp_cpu_online, - powerclamp_cpu_predown); - if (retval < 0) - goto exit_free; - - hp_state = retval; - - worker_data = alloc_percpu(struct powerclamp_worker_data); - if (!worker_data) { - retval = -ENOMEM; - goto exit_unregister; - } - - cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL, - &powerclamp_cooling_ops); - if (IS_ERR(cooling_dev)) { - retval = -ENODEV; - goto exit_free_thread; - } - - if (!duration) - duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES); - - powerclamp_create_debug_files(); - - return 0; - -exit_free_thread: - free_percpu(worker_data); -exit_unregister: - cpuhp_remove_state_nocalls(hp_state); -exit_free: - kfree(cpu_clamping_mask); - return retval; -} -module_init(powerclamp_init); - -static void __exit powerclamp_exit(void) -{ - end_power_clamp(); - cpuhp_remove_state_nocalls(hp_state); - free_percpu(worker_data); - thermal_cooling_device_unregister(cooling_dev); - kfree(cpu_clamping_mask); - - cancel_delayed_work_sync(&poll_pkg_cstate_work); - debugfs_remove_recursive(debug_dir); -} -module_exit(powerclamp_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Arjan van de Ven "); -MODULE_AUTHOR("Jacob Pan "); -MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs"); diff --git a/drivers/thermal/intel_quark_dts_thermal.c b/drivers/thermal/intel_quark_dts_thermal.c deleted file mode 100644 index 5d33b350da1c..000000000000 --- a/drivers/thermal/intel_quark_dts_thermal.c +++ /dev/null @@ -1,471 +0,0 @@ -/* - * intel_quark_dts_thermal.c - * - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * GPL LICENSE SUMMARY - * - * Copyright(c) 2015 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * Contact Information: - * Ong Boon Leong - * Intel Malaysia, Penang - * - * BSD LICENSE - * - * Copyright(c) 2015 Intel Corporation. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Intel Corporation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * Quark DTS thermal driver is implemented by referencing - * intel_soc_dts_thermal.c. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include - -#define X86_FAMILY_QUARK 0x5 -#define X86_MODEL_QUARK_X1000 0x9 - -/* DTS reset is programmed via QRK_MBI_UNIT_SOC */ -#define QRK_DTS_REG_OFFSET_RESET 0x34 -#define QRK_DTS_RESET_BIT BIT(0) - -/* DTS enable is programmed via QRK_MBI_UNIT_RMU */ -#define QRK_DTS_REG_OFFSET_ENABLE 0xB0 -#define QRK_DTS_ENABLE_BIT BIT(15) - -/* Temperature Register is read via QRK_MBI_UNIT_RMU */ -#define QRK_DTS_REG_OFFSET_TEMP 0xB1 -#define QRK_DTS_MASK_TEMP 0xFF -#define QRK_DTS_OFFSET_TEMP 0 -#define QRK_DTS_OFFSET_REL_TEMP 16 -#define QRK_DTS_TEMP_BASE 50 - -/* Programmable Trip Point Register is configured via QRK_MBI_UNIT_RMU */ -#define QRK_DTS_REG_OFFSET_PTPS 0xB2 -#define QRK_DTS_MASK_TP_THRES 0xFF -#define QRK_DTS_SHIFT_TP 8 -#define QRK_DTS_ID_TP_CRITICAL 0 -#define QRK_DTS_SAFE_TP_THRES 105 - -/* Thermal Sensor Register Lock */ -#define QRK_DTS_REG_OFFSET_LOCK 0x71 -#define QRK_DTS_LOCK_BIT BIT(5) - -/* Quark DTS has 2 trip points: hot & catastrophic */ -#define QRK_MAX_DTS_TRIPS 2 -/* If DTS not locked, all trip points are configurable */ -#define QRK_DTS_WR_MASK_SET 0x3 -/* If DTS locked, all trip points are not configurable */ -#define QRK_DTS_WR_MASK_CLR 0 - -#define DEFAULT_POLL_DELAY 2000 - -struct soc_sensor_entry { - bool locked; - u32 store_ptps; - u32 store_dts_enable; - enum thermal_device_mode mode; - struct thermal_zone_device *tzone; -}; - -static struct soc_sensor_entry *soc_dts; - -static int polling_delay = DEFAULT_POLL_DELAY; -module_param(polling_delay, int, 0644); -MODULE_PARM_DESC(polling_delay, - "Polling interval for checking trip points (in milliseconds)"); - -static DEFINE_MUTEX(dts_update_mutex); - -static int soc_dts_enable(struct thermal_zone_device *tzd) -{ - u32 out; - struct soc_sensor_entry *aux_entry = tzd->devdata; - int ret; - - ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_ENABLE, &out); - if (ret) - return ret; - - if (out & QRK_DTS_ENABLE_BIT) { - aux_entry->mode = THERMAL_DEVICE_ENABLED; - return 0; - } - - if (!aux_entry->locked) { - out |= QRK_DTS_ENABLE_BIT; - ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, - QRK_DTS_REG_OFFSET_ENABLE, out); - if (ret) - return ret; - - aux_entry->mode = THERMAL_DEVICE_ENABLED; - } else { - aux_entry->mode = THERMAL_DEVICE_DISABLED; - pr_info("DTS is locked. Cannot enable DTS\n"); - ret = -EPERM; - } - - return ret; -} - -static int soc_dts_disable(struct thermal_zone_device *tzd) -{ - u32 out; - struct soc_sensor_entry *aux_entry = tzd->devdata; - int ret; - - ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_ENABLE, &out); - if (ret) - return ret; - - if (!(out & QRK_DTS_ENABLE_BIT)) { - aux_entry->mode = THERMAL_DEVICE_DISABLED; - return 0; - } - - if (!aux_entry->locked) { - out &= ~QRK_DTS_ENABLE_BIT; - ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, - QRK_DTS_REG_OFFSET_ENABLE, out); - - if (ret) - return ret; - - aux_entry->mode = THERMAL_DEVICE_DISABLED; - } else { - aux_entry->mode = THERMAL_DEVICE_ENABLED; - pr_info("DTS is locked. Cannot disable DTS\n"); - ret = -EPERM; - } - - return ret; -} - -static int _get_trip_temp(int trip, int *temp) -{ - int status; - u32 out; - - mutex_lock(&dts_update_mutex); - status = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_PTPS, &out); - mutex_unlock(&dts_update_mutex); - - if (status) - return status; - - /* - * Thermal Sensor Programmable Trip Point Register has 8-bit - * fields for critical (catastrophic) and hot set trip point - * thresholds. The threshold value is always offset by its - * temperature base (50 degree Celsius). - */ - *temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES; - *temp -= QRK_DTS_TEMP_BASE; - - return 0; -} - -static inline int sys_get_trip_temp(struct thermal_zone_device *tzd, - int trip, int *temp) -{ - return _get_trip_temp(trip, temp); -} - -static inline int sys_get_crit_temp(struct thermal_zone_device *tzd, int *temp) -{ - return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp); -} - -static int update_trip_temp(struct soc_sensor_entry *aux_entry, - int trip, int temp) -{ - u32 out; - u32 temp_out; - u32 store_ptps; - int ret; - - mutex_lock(&dts_update_mutex); - if (aux_entry->locked) { - ret = -EPERM; - goto failed; - } - - ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_PTPS, &store_ptps); - if (ret) - goto failed; - - /* - * Protection against unsafe trip point thresdhold value. - * As Quark X1000 data-sheet does not provide any recommendation - * regarding the safe trip point threshold value to use, we choose - * the safe value according to the threshold value set by UEFI BIOS. - */ - if (temp > QRK_DTS_SAFE_TP_THRES) - temp = QRK_DTS_SAFE_TP_THRES; - - /* - * Thermal Sensor Programmable Trip Point Register has 8-bit - * fields for critical (catastrophic) and hot set trip point - * thresholds. The threshold value is always offset by its - * temperature base (50 degree Celsius). - */ - temp_out = temp + QRK_DTS_TEMP_BASE; - out = (store_ptps & ~(QRK_DTS_MASK_TP_THRES << - (trip * QRK_DTS_SHIFT_TP))); - out |= (temp_out & QRK_DTS_MASK_TP_THRES) << - (trip * QRK_DTS_SHIFT_TP); - - ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, - QRK_DTS_REG_OFFSET_PTPS, out); - -failed: - mutex_unlock(&dts_update_mutex); - return ret; -} - -static inline int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, - int temp) -{ - return update_trip_temp(tzd->devdata, trip, temp); -} - -static int sys_get_trip_type(struct thermal_zone_device *thermal, - int trip, enum thermal_trip_type *type) -{ - if (trip) - *type = THERMAL_TRIP_HOT; - else - *type = THERMAL_TRIP_CRITICAL; - - return 0; -} - -static int sys_get_curr_temp(struct thermal_zone_device *tzd, - int *temp) -{ - u32 out; - int ret; - - mutex_lock(&dts_update_mutex); - ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_TEMP, &out); - mutex_unlock(&dts_update_mutex); - - if (ret) - return ret; - - /* - * Thermal Sensor Temperature Register has 8-bit field - * for temperature value (offset by temperature base - * 50 degree Celsius). - */ - out = (out >> QRK_DTS_OFFSET_TEMP) & QRK_DTS_MASK_TEMP; - *temp = out - QRK_DTS_TEMP_BASE; - - return 0; -} - -static int sys_get_mode(struct thermal_zone_device *tzd, - enum thermal_device_mode *mode) -{ - struct soc_sensor_entry *aux_entry = tzd->devdata; - *mode = aux_entry->mode; - return 0; -} - -static int sys_set_mode(struct thermal_zone_device *tzd, - enum thermal_device_mode mode) -{ - int ret; - - mutex_lock(&dts_update_mutex); - if (mode == THERMAL_DEVICE_ENABLED) - ret = soc_dts_enable(tzd); - else - ret = soc_dts_disable(tzd); - mutex_unlock(&dts_update_mutex); - - return ret; -} - -static struct thermal_zone_device_ops tzone_ops = { - .get_temp = sys_get_curr_temp, - .get_trip_temp = sys_get_trip_temp, - .get_trip_type = sys_get_trip_type, - .set_trip_temp = sys_set_trip_temp, - .get_crit_temp = sys_get_crit_temp, - .get_mode = sys_get_mode, - .set_mode = sys_set_mode, -}; - -static void free_soc_dts(struct soc_sensor_entry *aux_entry) -{ - if (aux_entry) { - if (!aux_entry->locked) { - mutex_lock(&dts_update_mutex); - iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, - QRK_DTS_REG_OFFSET_ENABLE, - aux_entry->store_dts_enable); - - iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE, - QRK_DTS_REG_OFFSET_PTPS, - aux_entry->store_ptps); - mutex_unlock(&dts_update_mutex); - } - thermal_zone_device_unregister(aux_entry->tzone); - kfree(aux_entry); - } -} - -static struct soc_sensor_entry *alloc_soc_dts(void) -{ - struct soc_sensor_entry *aux_entry; - int err; - u32 out; - int wr_mask; - - aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL); - if (!aux_entry) { - err = -ENOMEM; - return ERR_PTR(-ENOMEM); - } - - /* Check if DTS register is locked */ - err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_LOCK, &out); - if (err) - goto err_ret; - - if (out & QRK_DTS_LOCK_BIT) { - aux_entry->locked = true; - wr_mask = QRK_DTS_WR_MASK_CLR; - } else { - aux_entry->locked = false; - wr_mask = QRK_DTS_WR_MASK_SET; - } - - /* Store DTS default state if DTS registers are not locked */ - if (!aux_entry->locked) { - /* Store DTS default enable for restore on exit */ - err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_ENABLE, - &aux_entry->store_dts_enable); - if (err) - goto err_ret; - - /* Store DTS default PTPS register for restore on exit */ - err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ, - QRK_DTS_REG_OFFSET_PTPS, - &aux_entry->store_ptps); - if (err) - goto err_ret; - } - - aux_entry->tzone = thermal_zone_device_register("quark_dts", - QRK_MAX_DTS_TRIPS, - wr_mask, - aux_entry, &tzone_ops, NULL, 0, polling_delay); - if (IS_ERR(aux_entry->tzone)) { - err = PTR_ERR(aux_entry->tzone); - goto err_ret; - } - - mutex_lock(&dts_update_mutex); - err = soc_dts_enable(aux_entry->tzone); - mutex_unlock(&dts_update_mutex); - if (err) - goto err_aux_status; - - return aux_entry; - -err_aux_status: - thermal_zone_device_unregister(aux_entry->tzone); -err_ret: - kfree(aux_entry); - return ERR_PTR(err); -} - -static const struct x86_cpu_id qrk_thermal_ids[] __initconst = { - { X86_VENDOR_INTEL, X86_FAMILY_QUARK, X86_MODEL_QUARK_X1000 }, - {} -}; -MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids); - -static int __init intel_quark_thermal_init(void) -{ - int err = 0; - - if (!x86_match_cpu(qrk_thermal_ids) || !iosf_mbi_available()) - return -ENODEV; - - soc_dts = alloc_soc_dts(); - if (IS_ERR(soc_dts)) { - err = PTR_ERR(soc_dts); - goto err_free; - } - - return 0; - -err_free: - free_soc_dts(soc_dts); - return err; -} - -static void __exit intel_quark_thermal_exit(void) -{ - free_soc_dts(soc_dts); -} - -module_init(intel_quark_thermal_init) -module_exit(intel_quark_thermal_exit) - -MODULE_DESCRIPTION("Intel Quark DTS Thermal Driver"); -MODULE_AUTHOR("Ong Boon Leong "); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/thermal/intel_soc_dts_iosf.c b/drivers/thermal/intel_soc_dts_iosf.c deleted file mode 100644 index e0813dfaa278..000000000000 --- a/drivers/thermal/intel_soc_dts_iosf.c +++ /dev/null @@ -1,478 +0,0 @@ -/* - * intel_soc_dts_iosf.c - * Copyright (c) 2015, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include "intel_soc_dts_iosf.h" - -#define SOC_DTS_OFFSET_ENABLE 0xB0 -#define SOC_DTS_OFFSET_TEMP 0xB1 - -#define SOC_DTS_OFFSET_PTPS 0xB2 -#define SOC_DTS_OFFSET_PTTS 0xB3 -#define SOC_DTS_OFFSET_PTTSS 0xB4 -#define SOC_DTS_OFFSET_PTMC 0x80 -#define SOC_DTS_TE_AUX0 0xB5 -#define SOC_DTS_TE_AUX1 0xB6 - -#define SOC_DTS_AUX0_ENABLE_BIT BIT(0) -#define SOC_DTS_AUX1_ENABLE_BIT BIT(1) -#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16) -#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17) -#define SOC_DTS_TE_SCI_ENABLE BIT(9) -#define SOC_DTS_TE_SMI_ENABLE BIT(10) -#define SOC_DTS_TE_MSI_ENABLE BIT(11) -#define SOC_DTS_TE_APICA_ENABLE BIT(14) -#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4) - -/* DTS encoding for TJ MAX temperature */ -#define SOC_DTS_TJMAX_ENCODING 0x7F - -/* Only 2 out of 4 is allowed for OSPM */ -#define SOC_MAX_DTS_TRIPS 2 - -/* Mask for two trips in status bits */ -#define SOC_DTS_TRIP_MASK 0x03 - -/* DTS0 and DTS 1 */ -#define SOC_MAX_DTS_SENSORS 2 - -static int get_tj_max(u32 *tj_max) -{ - u32 eax, edx; - u32 val; - int err; - - err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); - if (err) - goto err_ret; - else { - val = (eax >> 16) & 0xff; - if (val) - *tj_max = val * 1000; - else { - err = -EINVAL; - goto err_ret; - } - } - - return 0; -err_ret: - *tj_max = 0; - - return err; -} - -static int sys_get_trip_temp(struct thermal_zone_device *tzd, int trip, - int *temp) -{ - int status; - u32 out; - struct intel_soc_dts_sensor_entry *dts; - struct intel_soc_dts_sensors *sensors; - - dts = tzd->devdata; - sensors = dts->sensors; - mutex_lock(&sensors->dts_update_lock); - status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_PTPS, &out); - mutex_unlock(&sensors->dts_update_lock); - if (status) - return status; - - out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING; - if (!out) - *temp = 0; - else - *temp = sensors->tj_max - out * 1000; - - return 0; -} - -static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts, - int thres_index, int temp, - enum thermal_trip_type trip_type) -{ - int status; - u32 temp_out; - u32 out; - u32 store_ptps; - u32 store_ptmc; - u32 store_te_out; - u32 te_out; - u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE; - struct intel_soc_dts_sensors *sensors = dts->sensors; - - if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI) - int_enable_bit |= SOC_DTS_TE_MSI_ENABLE; - - temp_out = (sensors->tj_max - temp) / 1000; - - status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_PTPS, &store_ptps); - if (status) - return status; - - out = (store_ptps & ~(0xFF << (thres_index * 8))); - out |= (temp_out & 0xFF) << (thres_index * 8); - status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_PTPS, out); - if (status) - return status; - - pr_debug("update_trip_temp PTPS = %x\n", out); - status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_PTMC, &out); - if (status) - goto err_restore_ptps; - - store_ptmc = out; - - status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_TE_AUX0 + thres_index, - &te_out); - if (status) - goto err_restore_ptmc; - - store_te_out = te_out; - /* Enable for CPU module 0 and module 1 */ - out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT | - SOC_DTS_CPU_MODULE1_ENABLE_BIT); - if (temp) { - if (thres_index) - out |= SOC_DTS_AUX1_ENABLE_BIT; - else - out |= SOC_DTS_AUX0_ENABLE_BIT; - te_out |= int_enable_bit; - } else { - if (thres_index) - out &= ~SOC_DTS_AUX1_ENABLE_BIT; - else - out &= ~SOC_DTS_AUX0_ENABLE_BIT; - te_out &= ~int_enable_bit; - } - status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_PTMC, out); - if (status) - goto err_restore_te_out; - - status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_TE_AUX0 + thres_index, - te_out); - if (status) - goto err_restore_te_out; - - dts->trip_types[thres_index] = trip_type; - - return 0; -err_restore_te_out: - iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_PTMC, store_te_out); -err_restore_ptmc: - iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_PTMC, store_ptmc); -err_restore_ptps: - iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_PTPS, store_ptps); - /* Nothing we can do if restore fails */ - - return status; -} - -static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, - int temp) -{ - struct intel_soc_dts_sensor_entry *dts = tzd->devdata; - struct intel_soc_dts_sensors *sensors = dts->sensors; - int status; - - if (temp > sensors->tj_max) - return -EINVAL; - - mutex_lock(&sensors->dts_update_lock); - status = update_trip_temp(tzd->devdata, trip, temp, - dts->trip_types[trip]); - mutex_unlock(&sensors->dts_update_lock); - - return status; -} - -static int sys_get_trip_type(struct thermal_zone_device *tzd, - int trip, enum thermal_trip_type *type) -{ - struct intel_soc_dts_sensor_entry *dts; - - dts = tzd->devdata; - - *type = dts->trip_types[trip]; - - return 0; -} - -static int sys_get_curr_temp(struct thermal_zone_device *tzd, - int *temp) -{ - int status; - u32 out; - struct intel_soc_dts_sensor_entry *dts; - struct intel_soc_dts_sensors *sensors; - - dts = tzd->devdata; - sensors = dts->sensors; - status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_TEMP, &out); - if (status) - return status; - - out = (out & dts->temp_mask) >> dts->temp_shift; - out -= SOC_DTS_TJMAX_ENCODING; - *temp = sensors->tj_max - out * 1000; - - return 0; -} - -static struct thermal_zone_device_ops tzone_ops = { - .get_temp = sys_get_curr_temp, - .get_trip_temp = sys_get_trip_temp, - .get_trip_type = sys_get_trip_type, - .set_trip_temp = sys_set_trip_temp, -}; - -static int soc_dts_enable(int id) -{ - u32 out; - int ret; - - ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_ENABLE, &out); - if (ret) - return ret; - - if (!(out & BIT(id))) { - out |= BIT(id); - ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_ENABLE, out); - if (ret) - return ret; - } - - return ret; -} - -static void remove_dts_thermal_zone(struct intel_soc_dts_sensor_entry *dts) -{ - if (dts) { - iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_ENABLE, dts->store_status); - thermal_zone_device_unregister(dts->tzone); - } -} - -static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts, - bool notification_support, int trip_cnt, - int read_only_trip_cnt) -{ - char name[10]; - int trip_count = 0; - int trip_mask = 0; - u32 store_ptps; - int ret; - int i; - - /* Store status to restor on exit */ - ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_ENABLE, &dts->store_status); - if (ret) - goto err_ret; - - dts->id = id; - dts->temp_mask = 0x00FF << (id * 8); - dts->temp_shift = id * 8; - if (notification_support) { - trip_count = min(SOC_MAX_DTS_TRIPS, trip_cnt); - trip_mask = BIT(trip_count - read_only_trip_cnt) - 1; - } - - /* Check if the writable trip we provide is not used by BIOS */ - ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_PTPS, &store_ptps); - if (ret) - trip_mask = 0; - else { - for (i = 0; i < trip_count; ++i) { - if (trip_mask & BIT(i)) - if (store_ptps & (0xff << (i * 8))) - trip_mask &= ~BIT(i); - } - } - dts->trip_mask = trip_mask; - dts->trip_count = trip_count; - snprintf(name, sizeof(name), "soc_dts%d", id); - dts->tzone = thermal_zone_device_register(name, - trip_count, - trip_mask, - dts, &tzone_ops, - NULL, 0, 0); - if (IS_ERR(dts->tzone)) { - ret = PTR_ERR(dts->tzone); - goto err_ret; - } - - ret = soc_dts_enable(id); - if (ret) - goto err_enable; - - return 0; -err_enable: - thermal_zone_device_unregister(dts->tzone); -err_ret: - return ret; -} - -int intel_soc_dts_iosf_add_read_only_critical_trip( - struct intel_soc_dts_sensors *sensors, int critical_offset) -{ - int i, j; - - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { - for (j = 0; j < sensors->soc_dts[i].trip_count; ++j) { - if (!(sensors->soc_dts[i].trip_mask & BIT(j))) { - return update_trip_temp(&sensors->soc_dts[i], j, - sensors->tj_max - critical_offset, - THERMAL_TRIP_CRITICAL); - } - } - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_add_read_only_critical_trip); - -void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors) -{ - u32 sticky_out; - int status; - u32 ptmc_out; - unsigned long flags; - - spin_lock_irqsave(&sensors->intr_notify_lock, flags); - - status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_PTMC, &ptmc_out); - ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT; - status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_PTMC, ptmc_out); - - status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, - SOC_DTS_OFFSET_PTTSS, &sticky_out); - pr_debug("status %d PTTSS %x\n", status, sticky_out); - if (sticky_out & SOC_DTS_TRIP_MASK) { - int i; - /* reset sticky bit */ - status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, - SOC_DTS_OFFSET_PTTSS, sticky_out); - spin_unlock_irqrestore(&sensors->intr_notify_lock, flags); - - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { - pr_debug("TZD update for zone %d\n", i); - thermal_zone_device_update(sensors->soc_dts[i].tzone, - THERMAL_EVENT_UNSPECIFIED); - } - } else - spin_unlock_irqrestore(&sensors->intr_notify_lock, flags); -} -EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_interrupt_handler); - -struct intel_soc_dts_sensors *intel_soc_dts_iosf_init( - enum intel_soc_dts_interrupt_type intr_type, int trip_count, - int read_only_trip_count) -{ - struct intel_soc_dts_sensors *sensors; - bool notification; - u32 tj_max; - int ret; - int i; - - if (!iosf_mbi_available()) - return ERR_PTR(-ENODEV); - - if (!trip_count || read_only_trip_count > trip_count) - return ERR_PTR(-EINVAL); - - if (get_tj_max(&tj_max)) - return ERR_PTR(-EINVAL); - - sensors = kzalloc(sizeof(*sensors), GFP_KERNEL); - if (!sensors) - return ERR_PTR(-ENOMEM); - - spin_lock_init(&sensors->intr_notify_lock); - mutex_init(&sensors->dts_update_lock); - sensors->intr_type = intr_type; - sensors->tj_max = tj_max; - if (intr_type == INTEL_SOC_DTS_INTERRUPT_NONE) - notification = false; - else - notification = true; - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { - sensors->soc_dts[i].sensors = sensors; - ret = add_dts_thermal_zone(i, &sensors->soc_dts[i], - notification, trip_count, - read_only_trip_count); - if (ret) - goto err_free; - } - - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { - ret = update_trip_temp(&sensors->soc_dts[i], 0, 0, - THERMAL_TRIP_PASSIVE); - if (ret) - goto err_remove_zone; - - ret = update_trip_temp(&sensors->soc_dts[i], 1, 0, - THERMAL_TRIP_PASSIVE); - if (ret) - goto err_remove_zone; - } - - return sensors; -err_remove_zone: - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) - remove_dts_thermal_zone(&sensors->soc_dts[i]); - -err_free: - kfree(sensors); - return ERR_PTR(ret); -} -EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_init); - -void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors) -{ - int i; - - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { - update_trip_temp(&sensors->soc_dts[i], 0, 0, 0); - update_trip_temp(&sensors->soc_dts[i], 1, 0, 0); - remove_dts_thermal_zone(&sensors->soc_dts[i]); - } - kfree(sensors); -} -EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/intel_soc_dts_iosf.h b/drivers/thermal/intel_soc_dts_iosf.h deleted file mode 100644 index 625e37bf93dc..000000000000 --- a/drivers/thermal/intel_soc_dts_iosf.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * intel_soc_dts_iosf.h - * Copyright (c) 2015, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ - -#ifndef _INTEL_SOC_DTS_IOSF_CORE_H -#define _INTEL_SOC_DTS_IOSF_CORE_H - -#include - -/* DTS0 and DTS 1 */ -#define SOC_MAX_DTS_SENSORS 2 - -enum intel_soc_dts_interrupt_type { - INTEL_SOC_DTS_INTERRUPT_NONE, - INTEL_SOC_DTS_INTERRUPT_APIC, - INTEL_SOC_DTS_INTERRUPT_MSI, - INTEL_SOC_DTS_INTERRUPT_SCI, - INTEL_SOC_DTS_INTERRUPT_SMI, -}; - -struct intel_soc_dts_sensors; - -struct intel_soc_dts_sensor_entry { - int id; - u32 temp_mask; - u32 temp_shift; - u32 store_status; - u32 trip_mask; - u32 trip_count; - enum thermal_trip_type trip_types[2]; - struct thermal_zone_device *tzone; - struct intel_soc_dts_sensors *sensors; -}; - -struct intel_soc_dts_sensors { - u32 tj_max; - spinlock_t intr_notify_lock; - struct mutex dts_update_lock; - enum intel_soc_dts_interrupt_type intr_type; - struct intel_soc_dts_sensor_entry soc_dts[SOC_MAX_DTS_SENSORS]; -}; - -struct intel_soc_dts_sensors *intel_soc_dts_iosf_init( - enum intel_soc_dts_interrupt_type intr_type, int trip_count, - int read_only_trip_count); -void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors); -void intel_soc_dts_iosf_interrupt_handler( - struct intel_soc_dts_sensors *sensors); -int intel_soc_dts_iosf_add_read_only_critical_trip( - struct intel_soc_dts_sensors *sensors, int critical_offset); -#endif diff --git a/drivers/thermal/intel_soc_dts_thermal.c b/drivers/thermal/intel_soc_dts_thermal.c deleted file mode 100644 index d748527d7a38..000000000000 --- a/drivers/thermal/intel_soc_dts_thermal.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * intel_soc_dts_thermal.c - * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include "intel_soc_dts_iosf.h" - -#define CRITICAL_OFFSET_FROM_TJ_MAX 5000 - -static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX; -module_param(crit_offset, int, 0644); -MODULE_PARM_DESC(crit_offset, - "Critical Temperature offset from tj max in millidegree Celsius."); - -/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */ -#define BYT_SOC_DTS_APIC_IRQ 86 - -static int soc_dts_thres_gsi; -static int soc_dts_thres_irq; -static struct intel_soc_dts_sensors *soc_dts; - -static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data) -{ - pr_debug("proc_thermal_interrupt\n"); - intel_soc_dts_iosf_interrupt_handler(soc_dts); - - return IRQ_HANDLED; -} - -static const struct x86_cpu_id soc_thermal_ids[] = { - { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT, 0, - BYT_SOC_DTS_APIC_IRQ}, - {} -}; -MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids); - -static int __init intel_soc_thermal_init(void) -{ - int err = 0; - const struct x86_cpu_id *match_cpu; - - match_cpu = x86_match_cpu(soc_thermal_ids); - if (!match_cpu) - return -ENODEV; - - /* Create a zone with 2 trips with marked as read only */ - soc_dts = intel_soc_dts_iosf_init(INTEL_SOC_DTS_INTERRUPT_APIC, 2, 1); - if (IS_ERR(soc_dts)) { - err = PTR_ERR(soc_dts); - return err; - } - - soc_dts_thres_gsi = (int)match_cpu->driver_data; - if (soc_dts_thres_gsi) { - /* - * Note the flags here MUST match the firmware defaults, rather - * then the request_irq flags, otherwise we get an EBUSY error. - */ - soc_dts_thres_irq = acpi_register_gsi(NULL, soc_dts_thres_gsi, - ACPI_LEVEL_SENSITIVE, - ACPI_ACTIVE_LOW); - if (soc_dts_thres_irq < 0) { - pr_warn("intel_soc_dts: Could not get IRQ for GSI %d, err %d\n", - soc_dts_thres_gsi, soc_dts_thres_irq); - soc_dts_thres_irq = 0; - } - } - - if (soc_dts_thres_irq) { - err = request_threaded_irq(soc_dts_thres_irq, NULL, - soc_irq_thread_fn, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - "soc_dts", soc_dts); - if (err) { - /* - * Do not just error out because the user space thermal - * daemon such as DPTF may use polling instead of being - * interrupt driven. - */ - pr_warn("request_threaded_irq ret %d\n", err); - } - } - - err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts, - crit_offset); - if (err) - goto error_trips; - - return 0; - -error_trips: - if (soc_dts_thres_irq) { - free_irq(soc_dts_thres_irq, soc_dts); - acpi_unregister_gsi(soc_dts_thres_gsi); - } - intel_soc_dts_iosf_exit(soc_dts); - - return err; -} - -static void __exit intel_soc_thermal_exit(void) -{ - if (soc_dts_thres_irq) { - free_irq(soc_dts_thres_irq, soc_dts); - acpi_unregister_gsi(soc_dts_thres_gsi); - } - intel_soc_dts_iosf_exit(soc_dts); -} - -module_init(intel_soc_thermal_init) -module_exit(intel_soc_thermal_exit) - -MODULE_DESCRIPTION("Intel SoC DTS Thermal Driver"); -MODULE_AUTHOR("Srinivas Pandruvada "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/x86_pkg_temp_thermal.c b/drivers/thermal/x86_pkg_temp_thermal.c deleted file mode 100644 index 1ef937d799e4..000000000000 --- a/drivers/thermal/x86_pkg_temp_thermal.c +++ /dev/null @@ -1,558 +0,0 @@ -/* - * x86_pkg_temp_thermal driver - * Copyright (c) 2013, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc. - * - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* -* Rate control delay: Idea is to introduce denounce effect -* This should be long enough to avoid reduce events, when -* threshold is set to a temperature, which is constantly -* violated, but at the short enough to take any action. -* The action can be remove threshold or change it to next -* interesting setting. Based on experiments, in around -* every 5 seconds under load will give us a significant -* temperature change. -*/ -#define PKG_TEMP_THERMAL_NOTIFY_DELAY 5000 -static int notify_delay_ms = PKG_TEMP_THERMAL_NOTIFY_DELAY; -module_param(notify_delay_ms, int, 0644); -MODULE_PARM_DESC(notify_delay_ms, - "User space notification delay in milli seconds."); - -/* Number of trip points in thermal zone. Currently it can't -* be more than 2. MSR can allow setting and getting notifications -* for only 2 thresholds. This define enforces this, if there -* is some wrong values returned by cpuid for number of thresholds. -*/ -#define MAX_NUMBER_OF_TRIPS 2 - -struct pkg_device { - int cpu; - bool work_scheduled; - u32 tj_max; - u32 msr_pkg_therm_low; - u32 msr_pkg_therm_high; - struct delayed_work work; - struct thermal_zone_device *tzone; - struct cpumask cpumask; -}; - -static struct thermal_zone_params pkg_temp_tz_params = { - .no_hwmon = true, -}; - -/* Keep track of how many package pointers we allocated in init() */ -static int max_packages __read_mostly; -/* Array of package pointers */ -static struct pkg_device **packages; -/* Serializes interrupt notification, work and hotplug */ -static DEFINE_SPINLOCK(pkg_temp_lock); -/* Protects zone operation in the work function against hotplug removal */ -static DEFINE_MUTEX(thermal_zone_mutex); - -/* The dynamically assigned cpu hotplug state for module_exit() */ -static enum cpuhp_state pkg_thermal_hp_state __read_mostly; - -/* Debug counters to show using debugfs */ -static struct dentry *debugfs; -static unsigned int pkg_interrupt_cnt; -static unsigned int pkg_work_cnt; - -static int pkg_temp_debugfs_init(void) -{ - struct dentry *d; - - debugfs = debugfs_create_dir("pkg_temp_thermal", NULL); - if (!debugfs) - return -ENOENT; - - d = debugfs_create_u32("pkg_thres_interrupt", S_IRUGO, debugfs, - &pkg_interrupt_cnt); - if (!d) - goto err_out; - - d = debugfs_create_u32("pkg_thres_work", S_IRUGO, debugfs, - &pkg_work_cnt); - if (!d) - goto err_out; - - return 0; - -err_out: - debugfs_remove_recursive(debugfs); - return -ENOENT; -} - -/* - * Protection: - * - * - cpu hotplug: Read serialized by cpu hotplug lock - * Write must hold pkg_temp_lock - * - * - Other callsites: Must hold pkg_temp_lock - */ -static struct pkg_device *pkg_temp_thermal_get_dev(unsigned int cpu) -{ - int pkgid = topology_logical_package_id(cpu); - - if (pkgid >= 0 && pkgid < max_packages) - return packages[pkgid]; - return NULL; -} - -/* -* tj-max is is interesting because threshold is set relative to this -* temperature. -*/ -static int get_tj_max(int cpu, u32 *tj_max) -{ - u32 eax, edx, val; - int err; - - err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); - if (err) - return err; - - val = (eax >> 16) & 0xff; - *tj_max = val * 1000; - - return val ? 0 : -EINVAL; -} - -static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp) -{ - struct pkg_device *pkgdev = tzd->devdata; - u32 eax, edx; - - rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_STATUS, &eax, &edx); - if (eax & 0x80000000) { - *temp = pkgdev->tj_max - ((eax >> 16) & 0x7f) * 1000; - pr_debug("sys_get_curr_temp %d\n", *temp); - return 0; - } - return -EINVAL; -} - -static int sys_get_trip_temp(struct thermal_zone_device *tzd, - int trip, int *temp) -{ - struct pkg_device *pkgdev = tzd->devdata; - unsigned long thres_reg_value; - u32 mask, shift, eax, edx; - int ret; - - if (trip >= MAX_NUMBER_OF_TRIPS) - return -EINVAL; - - if (trip) { - mask = THERM_MASK_THRESHOLD1; - shift = THERM_SHIFT_THRESHOLD1; - } else { - mask = THERM_MASK_THRESHOLD0; - shift = THERM_SHIFT_THRESHOLD0; - } - - ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, - &eax, &edx); - if (ret < 0) - return ret; - - thres_reg_value = (eax & mask) >> shift; - if (thres_reg_value) - *temp = pkgdev->tj_max - thres_reg_value * 1000; - else - *temp = 0; - pr_debug("sys_get_trip_temp %d\n", *temp); - - return 0; -} - -static int -sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp) -{ - struct pkg_device *pkgdev = tzd->devdata; - u32 l, h, mask, shift, intr; - int ret; - - if (trip >= MAX_NUMBER_OF_TRIPS || temp >= pkgdev->tj_max) - return -EINVAL; - - ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, - &l, &h); - if (ret < 0) - return ret; - - if (trip) { - mask = THERM_MASK_THRESHOLD1; - shift = THERM_SHIFT_THRESHOLD1; - intr = THERM_INT_THRESHOLD1_ENABLE; - } else { - mask = THERM_MASK_THRESHOLD0; - shift = THERM_SHIFT_THRESHOLD0; - intr = THERM_INT_THRESHOLD0_ENABLE; - } - l &= ~mask; - /* - * When users space sets a trip temperature == 0, which is indication - * that, it is no longer interested in receiving notifications. - */ - if (!temp) { - l &= ~intr; - } else { - l |= (pkgdev->tj_max - temp)/1000 << shift; - l |= intr; - } - - return wrmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); -} - -static int sys_get_trip_type(struct thermal_zone_device *thermal, int trip, - enum thermal_trip_type *type) -{ - *type = THERMAL_TRIP_PASSIVE; - return 0; -} - -/* Thermal zone callback registry */ -static struct thermal_zone_device_ops tzone_ops = { - .get_temp = sys_get_curr_temp, - .get_trip_temp = sys_get_trip_temp, - .get_trip_type = sys_get_trip_type, - .set_trip_temp = sys_set_trip_temp, -}; - -static bool pkg_thermal_rate_control(void) -{ - return true; -} - -/* Enable threshold interrupt on local package/cpu */ -static inline void enable_pkg_thres_interrupt(void) -{ - u8 thres_0, thres_1; - u32 l, h; - - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); - /* only enable/disable if it had valid threshold value */ - thres_0 = (l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0; - thres_1 = (l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1; - if (thres_0) - l |= THERM_INT_THRESHOLD0_ENABLE; - if (thres_1) - l |= THERM_INT_THRESHOLD1_ENABLE; - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); -} - -/* Disable threshold interrupt on local package/cpu */ -static inline void disable_pkg_thres_interrupt(void) -{ - u32 l, h; - - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); - - l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE); - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); -} - -static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work) -{ - struct thermal_zone_device *tzone = NULL; - int cpu = smp_processor_id(); - struct pkg_device *pkgdev; - u64 msr_val, wr_val; - - mutex_lock(&thermal_zone_mutex); - spin_lock_irq(&pkg_temp_lock); - ++pkg_work_cnt; - - pkgdev = pkg_temp_thermal_get_dev(cpu); - if (!pkgdev) { - spin_unlock_irq(&pkg_temp_lock); - mutex_unlock(&thermal_zone_mutex); - return; - } - pkgdev->work_scheduled = false; - - rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val); - wr_val = msr_val & ~(THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1); - if (wr_val != msr_val) { - wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, wr_val); - tzone = pkgdev->tzone; - } - - enable_pkg_thres_interrupt(); - spin_unlock_irq(&pkg_temp_lock); - - /* - * If tzone is not NULL, then thermal_zone_mutex will prevent the - * concurrent removal in the cpu offline callback. - */ - if (tzone) - thermal_zone_device_update(tzone, THERMAL_EVENT_UNSPECIFIED); - - mutex_unlock(&thermal_zone_mutex); -} - -static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work) -{ - unsigned long ms = msecs_to_jiffies(notify_delay_ms); - - schedule_delayed_work_on(cpu, work, ms); -} - -static int pkg_thermal_notify(u64 msr_val) -{ - int cpu = smp_processor_id(); - struct pkg_device *pkgdev; - unsigned long flags; - - spin_lock_irqsave(&pkg_temp_lock, flags); - ++pkg_interrupt_cnt; - - disable_pkg_thres_interrupt(); - - /* Work is per package, so scheduling it once is enough. */ - pkgdev = pkg_temp_thermal_get_dev(cpu); - if (pkgdev && !pkgdev->work_scheduled) { - pkgdev->work_scheduled = true; - pkg_thermal_schedule_work(pkgdev->cpu, &pkgdev->work); - } - - spin_unlock_irqrestore(&pkg_temp_lock, flags); - return 0; -} - -static int pkg_temp_thermal_device_add(unsigned int cpu) -{ - int pkgid = topology_logical_package_id(cpu); - u32 tj_max, eax, ebx, ecx, edx; - struct pkg_device *pkgdev; - int thres_count, err; - - if (pkgid >= max_packages) - return -ENOMEM; - - cpuid(6, &eax, &ebx, &ecx, &edx); - thres_count = ebx & 0x07; - if (!thres_count) - return -ENODEV; - - thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS); - - err = get_tj_max(cpu, &tj_max); - if (err) - return err; - - pkgdev = kzalloc(sizeof(*pkgdev), GFP_KERNEL); - if (!pkgdev) - return -ENOMEM; - - INIT_DELAYED_WORK(&pkgdev->work, pkg_temp_thermal_threshold_work_fn); - pkgdev->cpu = cpu; - pkgdev->tj_max = tj_max; - pkgdev->tzone = thermal_zone_device_register("x86_pkg_temp", - thres_count, - (thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01, - pkgdev, &tzone_ops, &pkg_temp_tz_params, 0, 0); - if (IS_ERR(pkgdev->tzone)) { - err = PTR_ERR(pkgdev->tzone); - kfree(pkgdev); - return err; - } - /* Store MSR value for package thermal interrupt, to restore at exit */ - rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, pkgdev->msr_pkg_therm_low, - pkgdev->msr_pkg_therm_high); - - cpumask_set_cpu(cpu, &pkgdev->cpumask); - spin_lock_irq(&pkg_temp_lock); - packages[pkgid] = pkgdev; - spin_unlock_irq(&pkg_temp_lock); - return 0; -} - -static int pkg_thermal_cpu_offline(unsigned int cpu) -{ - struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu); - bool lastcpu, was_target; - int target; - - if (!pkgdev) - return 0; - - target = cpumask_any_but(&pkgdev->cpumask, cpu); - cpumask_clear_cpu(cpu, &pkgdev->cpumask); - lastcpu = target >= nr_cpu_ids; - /* - * Remove the sysfs files, if this is the last cpu in the package - * before doing further cleanups. - */ - if (lastcpu) { - struct thermal_zone_device *tzone = pkgdev->tzone; - - /* - * We must protect against a work function calling - * thermal_zone_update, after/while unregister. We null out - * the pointer under the zone mutex, so the worker function - * won't try to call. - */ - mutex_lock(&thermal_zone_mutex); - pkgdev->tzone = NULL; - mutex_unlock(&thermal_zone_mutex); - - thermal_zone_device_unregister(tzone); - } - - /* Protect against work and interrupts */ - spin_lock_irq(&pkg_temp_lock); - - /* - * Check whether this cpu was the current target and store the new - * one. When we drop the lock, then the interrupt notify function - * will see the new target. - */ - was_target = pkgdev->cpu == cpu; - pkgdev->cpu = target; - - /* - * If this is the last CPU in the package remove the package - * reference from the array and restore the interrupt MSR. When we - * drop the lock neither the interrupt notify function nor the - * worker will see the package anymore. - */ - if (lastcpu) { - packages[topology_logical_package_id(cpu)] = NULL; - /* After this point nothing touches the MSR anymore. */ - wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, - pkgdev->msr_pkg_therm_low, pkgdev->msr_pkg_therm_high); - } - - /* - * Check whether there is work scheduled and whether the work is - * targeted at the outgoing CPU. - */ - if (pkgdev->work_scheduled && was_target) { - /* - * To cancel the work we need to drop the lock, otherwise - * we might deadlock if the work needs to be flushed. - */ - spin_unlock_irq(&pkg_temp_lock); - cancel_delayed_work_sync(&pkgdev->work); - spin_lock_irq(&pkg_temp_lock); - /* - * If this is not the last cpu in the package and the work - * did not run after we dropped the lock above, then we - * need to reschedule the work, otherwise the interrupt - * stays disabled forever. - */ - if (!lastcpu && pkgdev->work_scheduled) - pkg_thermal_schedule_work(target, &pkgdev->work); - } - - spin_unlock_irq(&pkg_temp_lock); - - /* Final cleanup if this is the last cpu */ - if (lastcpu) - kfree(pkgdev); - return 0; -} - -static int pkg_thermal_cpu_online(unsigned int cpu) -{ - struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu); - struct cpuinfo_x86 *c = &cpu_data(cpu); - - /* Paranoia check */ - if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS)) - return -ENODEV; - - /* If the package exists, nothing to do */ - if (pkgdev) { - cpumask_set_cpu(cpu, &pkgdev->cpumask); - return 0; - } - return pkg_temp_thermal_device_add(cpu); -} - -static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = { - { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS }, - {} -}; -MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids); - -static int __init pkg_temp_thermal_init(void) -{ - int ret; - - if (!x86_match_cpu(pkg_temp_thermal_ids)) - return -ENODEV; - - max_packages = topology_max_packages(); - packages = kcalloc(max_packages, sizeof(struct pkg_device *), - GFP_KERNEL); - if (!packages) - return -ENOMEM; - - ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal/x86_pkg:online", - pkg_thermal_cpu_online, pkg_thermal_cpu_offline); - if (ret < 0) - goto err; - - /* Store the state for module exit */ - pkg_thermal_hp_state = ret; - - platform_thermal_package_notify = pkg_thermal_notify; - platform_thermal_package_rate_control = pkg_thermal_rate_control; - - /* Don't care if it fails */ - pkg_temp_debugfs_init(); - return 0; - -err: - kfree(packages); - return ret; -} -module_init(pkg_temp_thermal_init) - -static void __exit pkg_temp_thermal_exit(void) -{ - platform_thermal_package_notify = NULL; - platform_thermal_package_rate_control = NULL; - - cpuhp_remove_state(pkg_thermal_hp_state); - debugfs_remove_recursive(debugfs); - kfree(packages); -} -module_exit(pkg_temp_thermal_exit) - -MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver"); -MODULE_AUTHOR("Srinivas Pandruvada "); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 72e9baf997286610a2a3109e79fdb528590c5523 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Fri, 7 Dec 2018 12:25:27 +0530 Subject: drivers: thermal: Move QCOM_SPMI_TEMP_ALARM into the qcom subdir This cleans up the directory a bit allowing just one place to look for thermal related drivers for QCOM platforms instead of being scattered in the root directory and the qcom/ subdirectory. Compile-tested with ARCH=arm64 defconfig and the driver explicitly enabled with menuconfig. Signed-off-by: Amit Kucheria Acked-by: Daniel Lezcano Signed-off-by: Zhang Rui --- drivers/thermal/Kconfig | 11 - drivers/thermal/Makefile | 1 - drivers/thermal/qcom-spmi-temp-alarm.c | 465 ---------------------------- drivers/thermal/qcom/Kconfig | 11 + drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/qcom-spmi-temp-alarm.c | 465 ++++++++++++++++++++++++++++ 6 files changed, 477 insertions(+), 477 deletions(-) delete mode 100644 drivers/thermal/qcom-spmi-temp-alarm.c create mode 100644 drivers/thermal/qcom/qcom-spmi-temp-alarm.c (limited to 'drivers/thermal') diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 772ab9dadda7..344f6459862b 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -374,17 +374,6 @@ config TANGO_THERMAL source "drivers/thermal/tegra/Kconfig" -config QCOM_SPMI_TEMP_ALARM - tristate "Qualcomm SPMI PMIC Temperature Alarm" - depends on OF && SPMI && IIO - select REGMAP_SPMI - help - This enables a thermal sysfs driver for Qualcomm plug-and-play (QPNP) - PMIC devices. It shows up in sysfs as a thermal sensor with multiple - trip points. The temperature reported by the thermal sensor reflects the - real time die temperature if an ADC is present or an estimate of the - temperature based upon the over temperature stage value. - config GENERIC_ADC_THERMAL tristate "Generic ADC based thermal sensor" depends on IIO diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 0b5d33a49b3e..486d682be047 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -29,7 +29,6 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o # platform thermal drivers obj-y += broadcom/ -obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o diff --git a/drivers/thermal/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom-spmi-temp-alarm.c deleted file mode 100644 index b2d5d5bf4a9b..000000000000 --- a/drivers/thermal/qcom-spmi-temp-alarm.c +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright (c) 2011-2015, 2017, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "thermal_core.h" - -#define QPNP_TM_REG_TYPE 0x04 -#define QPNP_TM_REG_SUBTYPE 0x05 -#define QPNP_TM_REG_STATUS 0x08 -#define QPNP_TM_REG_SHUTDOWN_CTRL1 0x40 -#define QPNP_TM_REG_ALARM_CTRL 0x46 - -#define QPNP_TM_TYPE 0x09 -#define QPNP_TM_SUBTYPE_GEN1 0x08 -#define QPNP_TM_SUBTYPE_GEN2 0x09 - -#define STATUS_GEN1_STAGE_MASK GENMASK(1, 0) -#define STATUS_GEN2_STATE_MASK GENMASK(6, 4) -#define STATUS_GEN2_STATE_SHIFT 4 - -#define SHUTDOWN_CTRL1_OVERRIDE_S2 BIT(6) -#define SHUTDOWN_CTRL1_THRESHOLD_MASK GENMASK(1, 0) - -#define SHUTDOWN_CTRL1_RATE_25HZ BIT(3) - -#define ALARM_CTRL_FORCE_ENABLE BIT(7) - -/* - * Trip point values based on threshold control - * 0 = {105 C, 125 C, 145 C} - * 1 = {110 C, 130 C, 150 C} - * 2 = {115 C, 135 C, 155 C} - * 3 = {120 C, 140 C, 160 C} -*/ -#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */ -#define TEMP_STAGE_HYSTERESIS 2000 - -#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */ -#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */ - -#define THRESH_MIN 0 -#define THRESH_MAX 3 - -/* Stage 2 Threshold Min: 125 C */ -#define STAGE2_THRESHOLD_MIN 125000 -/* Stage 2 Threshold Max: 140 C */ -#define STAGE2_THRESHOLD_MAX 140000 - -/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */ -#define DEFAULT_TEMP 37000 - -struct qpnp_tm_chip { - struct regmap *map; - struct device *dev; - struct thermal_zone_device *tz_dev; - unsigned int subtype; - long temp; - unsigned int thresh; - unsigned int stage; - unsigned int prev_stage; - unsigned int base; - /* protects .thresh, .stage and chip registers */ - struct mutex lock; - bool initialized; - - struct iio_channel *adc; -}; - -/* This array maps from GEN2 alarm state to GEN1 alarm stage */ -static const unsigned int alarm_state_map[8] = {0, 1, 1, 2, 2, 3, 3, 3}; - -static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data) -{ - unsigned int val; - int ret; - - ret = regmap_read(chip->map, chip->base + addr, &val); - if (ret < 0) - return ret; - - *data = val; - return 0; -} - -static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data) -{ - return regmap_write(chip->map, chip->base + addr, data); -} - -/** - * qpnp_tm_get_temp_stage() - return over-temperature stage - * @chip: Pointer to the qpnp_tm chip - * - * Return: stage (GEN1) or state (GEN2) on success, or errno on failure. - */ -static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip) -{ - int ret; - u8 reg = 0; - - ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®); - if (ret < 0) - return ret; - - if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) - ret = reg & STATUS_GEN1_STAGE_MASK; - else - ret = (reg & STATUS_GEN2_STATE_MASK) >> STATUS_GEN2_STATE_SHIFT; - - return ret; -} - -/* - * This function updates the internal temp value based on the - * current thermal stage and threshold as well as the previous stage - */ -static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip) -{ - unsigned int stage, stage_new, stage_old; - int ret; - - WARN_ON(!mutex_is_locked(&chip->lock)); - - ret = qpnp_tm_get_temp_stage(chip); - if (ret < 0) - return ret; - stage = ret; - - if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) { - stage_new = stage; - stage_old = chip->stage; - } else { - stage_new = alarm_state_map[stage]; - stage_old = alarm_state_map[chip->stage]; - } - - if (stage_new > stage_old) { - /* increasing stage, use lower bound */ - chip->temp = (stage_new - 1) * TEMP_STAGE_STEP + - chip->thresh * TEMP_THRESH_STEP + - TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; - } else if (stage_new < stage_old) { - /* decreasing stage, use upper bound */ - chip->temp = stage_new * TEMP_STAGE_STEP + - chip->thresh * TEMP_THRESH_STEP - - TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; - } - - chip->stage = stage; - - return 0; -} - -static int qpnp_tm_get_temp(void *data, int *temp) -{ - struct qpnp_tm_chip *chip = data; - int ret, mili_celsius; - - if (!temp) - return -EINVAL; - - if (!chip->initialized) { - *temp = DEFAULT_TEMP; - return 0; - } - - if (!chip->adc) { - mutex_lock(&chip->lock); - ret = qpnp_tm_update_temp_no_adc(chip); - mutex_unlock(&chip->lock); - if (ret < 0) - return ret; - } else { - ret = iio_read_channel_processed(chip->adc, &mili_celsius); - if (ret < 0) - return ret; - - chip->temp = mili_celsius; - } - - *temp = chip->temp < 0 ? 0 : chip->temp; - - return 0; -} - -static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip, - int temp) -{ - u8 reg; - bool disable_s2_shutdown = false; - - WARN_ON(!mutex_is_locked(&chip->lock)); - - /* - * Default: S2 and S3 shutdown enabled, thresholds at - * 105C/125C/145C, monitoring at 25Hz - */ - reg = SHUTDOWN_CTRL1_RATE_25HZ; - - if (temp == THERMAL_TEMP_INVALID || - temp < STAGE2_THRESHOLD_MIN) { - chip->thresh = THRESH_MIN; - goto skip; - } - - if (temp <= STAGE2_THRESHOLD_MAX) { - chip->thresh = THRESH_MAX - - ((STAGE2_THRESHOLD_MAX - temp) / - TEMP_THRESH_STEP); - disable_s2_shutdown = true; - } else { - chip->thresh = THRESH_MAX; - - if (chip->adc) - disable_s2_shutdown = true; - else - dev_warn(chip->dev, - "No ADC is configured and critical temperature is above the maximum stage 2 threshold of 140 C! Configuring stage 2 shutdown at 140 C.\n"); - } - -skip: - reg |= chip->thresh; - if (disable_s2_shutdown) - reg |= SHUTDOWN_CTRL1_OVERRIDE_S2; - - return qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg); -} - -static int qpnp_tm_set_trip_temp(void *data, int trip, int temp) -{ - struct qpnp_tm_chip *chip = data; - const struct thermal_trip *trip_points; - int ret; - - trip_points = of_thermal_get_trip_points(chip->tz_dev); - if (!trip_points) - return -EINVAL; - - if (trip_points[trip].type != THERMAL_TRIP_CRITICAL) - return 0; - - mutex_lock(&chip->lock); - ret = qpnp_tm_update_critical_trip_temp(chip, temp); - mutex_unlock(&chip->lock); - - return ret; -} - -static const struct thermal_zone_of_device_ops qpnp_tm_sensor_ops = { - .get_temp = qpnp_tm_get_temp, - .set_trip_temp = qpnp_tm_set_trip_temp, -}; - -static irqreturn_t qpnp_tm_isr(int irq, void *data) -{ - struct qpnp_tm_chip *chip = data; - - thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED); - - return IRQ_HANDLED; -} - -static int qpnp_tm_get_critical_trip_temp(struct qpnp_tm_chip *chip) -{ - int ntrips; - const struct thermal_trip *trips; - int i; - - ntrips = of_thermal_get_ntrips(chip->tz_dev); - if (ntrips <= 0) - return THERMAL_TEMP_INVALID; - - trips = of_thermal_get_trip_points(chip->tz_dev); - if (!trips) - return THERMAL_TEMP_INVALID; - - for (i = 0; i < ntrips; i++) { - if (of_thermal_is_trip_valid(chip->tz_dev, i) && - trips[i].type == THERMAL_TRIP_CRITICAL) - return trips[i].temperature; - } - - return THERMAL_TEMP_INVALID; -} - -/* - * This function initializes the internal temp value based on only the - * current thermal stage and threshold. Setup threshold control and - * disable shutdown override. - */ -static int qpnp_tm_init(struct qpnp_tm_chip *chip) -{ - unsigned int stage; - int ret; - u8 reg = 0; - int crit_temp; - - mutex_lock(&chip->lock); - - ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, ®); - if (ret < 0) - goto out; - - chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK; - chip->temp = DEFAULT_TEMP; - - ret = qpnp_tm_get_temp_stage(chip); - if (ret < 0) - goto out; - chip->stage = ret; - - stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1 - ? chip->stage : alarm_state_map[chip->stage]; - - if (stage) - chip->temp = chip->thresh * TEMP_THRESH_STEP + - (stage - 1) * TEMP_STAGE_STEP + - TEMP_THRESH_MIN; - - crit_temp = qpnp_tm_get_critical_trip_temp(chip); - ret = qpnp_tm_update_critical_trip_temp(chip, crit_temp); - if (ret < 0) - goto out; - - /* Enable the thermal alarm PMIC module in always-on mode. */ - reg = ALARM_CTRL_FORCE_ENABLE; - ret = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, reg); - - chip->initialized = true; - -out: - mutex_unlock(&chip->lock); - return ret; -} - -static int qpnp_tm_probe(struct platform_device *pdev) -{ - struct qpnp_tm_chip *chip; - struct device_node *node; - u8 type, subtype; - u32 res; - int ret, irq; - - node = pdev->dev.of_node; - - chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - dev_set_drvdata(&pdev->dev, chip); - chip->dev = &pdev->dev; - - mutex_init(&chip->lock); - - chip->map = dev_get_regmap(pdev->dev.parent, NULL); - if (!chip->map) - return -ENXIO; - - ret = of_property_read_u32(node, "reg", &res); - if (ret < 0) - return ret; - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - /* ADC based measurements are optional */ - chip->adc = devm_iio_channel_get(&pdev->dev, "thermal"); - if (IS_ERR(chip->adc)) { - ret = PTR_ERR(chip->adc); - chip->adc = NULL; - if (ret == -EPROBE_DEFER) - return ret; - } - - chip->base = res; - - ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type); - if (ret < 0) { - dev_err(&pdev->dev, "could not read type\n"); - return ret; - } - - ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype); - if (ret < 0) { - dev_err(&pdev->dev, "could not read subtype\n"); - return ret; - } - - if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1 - && subtype != QPNP_TM_SUBTYPE_GEN2)) { - dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n", - type, subtype); - return -ENODEV; - } - - chip->subtype = subtype; - - /* - * Register the sensor before initializing the hardware to be able to - * read the trip points. get_temp() returns the default temperature - * before the hardware initialization is completed. - */ - chip->tz_dev = devm_thermal_zone_of_sensor_register( - &pdev->dev, 0, chip, &qpnp_tm_sensor_ops); - if (IS_ERR(chip->tz_dev)) { - dev_err(&pdev->dev, "failed to register sensor\n"); - return PTR_ERR(chip->tz_dev); - } - - ret = qpnp_tm_init(chip); - if (ret < 0) { - dev_err(&pdev->dev, "init failed\n"); - return ret; - } - - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr, - IRQF_ONESHOT, node->name, chip); - if (ret < 0) - return ret; - - thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED); - - return 0; -} - -static const struct of_device_id qpnp_tm_match_table[] = { - { .compatible = "qcom,spmi-temp-alarm" }, - { } -}; -MODULE_DEVICE_TABLE(of, qpnp_tm_match_table); - -static struct platform_driver qpnp_tm_driver = { - .driver = { - .name = "spmi-temp-alarm", - .of_match_table = qpnp_tm_match_table, - }, - .probe = qpnp_tm_probe, -}; -module_platform_driver(qpnp_tm_driver); - -MODULE_ALIAS("platform:spmi-temp-alarm"); -MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index be32e5abce3c..cdb455ffd575 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -9,3 +9,14 @@ config QCOM_TSENS thermal zone device via the mode file results in disabling the sensor. Also able to set threshold temperature for both hot and cold and update when a threshold is reached. + +config QCOM_SPMI_TEMP_ALARM + tristate "Qualcomm SPMI PMIC Temperature Alarm" + depends on OF && SPMI && IIO + select REGMAP_SPMI + help + This enables a thermal sysfs driver for Qualcomm plug-and-play (QPNP) + PMIC devices. It shows up in sysfs as a thermal sensor with multiple + trip points. The temperature reported by the thermal sensor reflects the + real time die temperature if an ADC is present or an estimate of the + temperature based upon the over temperature stage value. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index a821929ede0b..717a08600bb5 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-v2.o +obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c new file mode 100644 index 000000000000..c1fd71dbab3e --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2011-2015, 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_core.h" + +#define QPNP_TM_REG_TYPE 0x04 +#define QPNP_TM_REG_SUBTYPE 0x05 +#define QPNP_TM_REG_STATUS 0x08 +#define QPNP_TM_REG_SHUTDOWN_CTRL1 0x40 +#define QPNP_TM_REG_ALARM_CTRL 0x46 + +#define QPNP_TM_TYPE 0x09 +#define QPNP_TM_SUBTYPE_GEN1 0x08 +#define QPNP_TM_SUBTYPE_GEN2 0x09 + +#define STATUS_GEN1_STAGE_MASK GENMASK(1, 0) +#define STATUS_GEN2_STATE_MASK GENMASK(6, 4) +#define STATUS_GEN2_STATE_SHIFT 4 + +#define SHUTDOWN_CTRL1_OVERRIDE_S2 BIT(6) +#define SHUTDOWN_CTRL1_THRESHOLD_MASK GENMASK(1, 0) + +#define SHUTDOWN_CTRL1_RATE_25HZ BIT(3) + +#define ALARM_CTRL_FORCE_ENABLE BIT(7) + +/* + * Trip point values based on threshold control + * 0 = {105 C, 125 C, 145 C} + * 1 = {110 C, 130 C, 150 C} + * 2 = {115 C, 135 C, 155 C} + * 3 = {120 C, 140 C, 160 C} +*/ +#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */ +#define TEMP_STAGE_HYSTERESIS 2000 + +#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */ +#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */ + +#define THRESH_MIN 0 +#define THRESH_MAX 3 + +/* Stage 2 Threshold Min: 125 C */ +#define STAGE2_THRESHOLD_MIN 125000 +/* Stage 2 Threshold Max: 140 C */ +#define STAGE2_THRESHOLD_MAX 140000 + +/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */ +#define DEFAULT_TEMP 37000 + +struct qpnp_tm_chip { + struct regmap *map; + struct device *dev; + struct thermal_zone_device *tz_dev; + unsigned int subtype; + long temp; + unsigned int thresh; + unsigned int stage; + unsigned int prev_stage; + unsigned int base; + /* protects .thresh, .stage and chip registers */ + struct mutex lock; + bool initialized; + + struct iio_channel *adc; +}; + +/* This array maps from GEN2 alarm state to GEN1 alarm stage */ +static const unsigned int alarm_state_map[8] = {0, 1, 1, 2, 2, 3, 3, 3}; + +static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(chip->map, chip->base + addr, &val); + if (ret < 0) + return ret; + + *data = val; + return 0; +} + +static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data) +{ + return regmap_write(chip->map, chip->base + addr, data); +} + +/** + * qpnp_tm_get_temp_stage() - return over-temperature stage + * @chip: Pointer to the qpnp_tm chip + * + * Return: stage (GEN1) or state (GEN2) on success, or errno on failure. + */ +static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip) +{ + int ret; + u8 reg = 0; + + ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®); + if (ret < 0) + return ret; + + if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) + ret = reg & STATUS_GEN1_STAGE_MASK; + else + ret = (reg & STATUS_GEN2_STATE_MASK) >> STATUS_GEN2_STATE_SHIFT; + + return ret; +} + +/* + * This function updates the internal temp value based on the + * current thermal stage and threshold as well as the previous stage + */ +static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip) +{ + unsigned int stage, stage_new, stage_old; + int ret; + + WARN_ON(!mutex_is_locked(&chip->lock)); + + ret = qpnp_tm_get_temp_stage(chip); + if (ret < 0) + return ret; + stage = ret; + + if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) { + stage_new = stage; + stage_old = chip->stage; + } else { + stage_new = alarm_state_map[stage]; + stage_old = alarm_state_map[chip->stage]; + } + + if (stage_new > stage_old) { + /* increasing stage, use lower bound */ + chip->temp = (stage_new - 1) * TEMP_STAGE_STEP + + chip->thresh * TEMP_THRESH_STEP + + TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; + } else if (stage_new < stage_old) { + /* decreasing stage, use upper bound */ + chip->temp = stage_new * TEMP_STAGE_STEP + + chip->thresh * TEMP_THRESH_STEP - + TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; + } + + chip->stage = stage; + + return 0; +} + +static int qpnp_tm_get_temp(void *data, int *temp) +{ + struct qpnp_tm_chip *chip = data; + int ret, mili_celsius; + + if (!temp) + return -EINVAL; + + if (!chip->initialized) { + *temp = DEFAULT_TEMP; + return 0; + } + + if (!chip->adc) { + mutex_lock(&chip->lock); + ret = qpnp_tm_update_temp_no_adc(chip); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + } else { + ret = iio_read_channel_processed(chip->adc, &mili_celsius); + if (ret < 0) + return ret; + + chip->temp = mili_celsius; + } + + *temp = chip->temp < 0 ? 0 : chip->temp; + + return 0; +} + +static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip, + int temp) +{ + u8 reg; + bool disable_s2_shutdown = false; + + WARN_ON(!mutex_is_locked(&chip->lock)); + + /* + * Default: S2 and S3 shutdown enabled, thresholds at + * 105C/125C/145C, monitoring at 25Hz + */ + reg = SHUTDOWN_CTRL1_RATE_25HZ; + + if (temp == THERMAL_TEMP_INVALID || + temp < STAGE2_THRESHOLD_MIN) { + chip->thresh = THRESH_MIN; + goto skip; + } + + if (temp <= STAGE2_THRESHOLD_MAX) { + chip->thresh = THRESH_MAX - + ((STAGE2_THRESHOLD_MAX - temp) / + TEMP_THRESH_STEP); + disable_s2_shutdown = true; + } else { + chip->thresh = THRESH_MAX; + + if (chip->adc) + disable_s2_shutdown = true; + else + dev_warn(chip->dev, + "No ADC is configured and critical temperature is above the maximum stage 2 threshold of 140 C! Configuring stage 2 shutdown at 140 C.\n"); + } + +skip: + reg |= chip->thresh; + if (disable_s2_shutdown) + reg |= SHUTDOWN_CTRL1_OVERRIDE_S2; + + return qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg); +} + +static int qpnp_tm_set_trip_temp(void *data, int trip, int temp) +{ + struct qpnp_tm_chip *chip = data; + const struct thermal_trip *trip_points; + int ret; + + trip_points = of_thermal_get_trip_points(chip->tz_dev); + if (!trip_points) + return -EINVAL; + + if (trip_points[trip].type != THERMAL_TRIP_CRITICAL) + return 0; + + mutex_lock(&chip->lock); + ret = qpnp_tm_update_critical_trip_temp(chip, temp); + mutex_unlock(&chip->lock); + + return ret; +} + +static const struct thermal_zone_of_device_ops qpnp_tm_sensor_ops = { + .get_temp = qpnp_tm_get_temp, + .set_trip_temp = qpnp_tm_set_trip_temp, +}; + +static irqreturn_t qpnp_tm_isr(int irq, void *data) +{ + struct qpnp_tm_chip *chip = data; + + thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED); + + return IRQ_HANDLED; +} + +static int qpnp_tm_get_critical_trip_temp(struct qpnp_tm_chip *chip) +{ + int ntrips; + const struct thermal_trip *trips; + int i; + + ntrips = of_thermal_get_ntrips(chip->tz_dev); + if (ntrips <= 0) + return THERMAL_TEMP_INVALID; + + trips = of_thermal_get_trip_points(chip->tz_dev); + if (!trips) + return THERMAL_TEMP_INVALID; + + for (i = 0; i < ntrips; i++) { + if (of_thermal_is_trip_valid(chip->tz_dev, i) && + trips[i].type == THERMAL_TRIP_CRITICAL) + return trips[i].temperature; + } + + return THERMAL_TEMP_INVALID; +} + +/* + * This function initializes the internal temp value based on only the + * current thermal stage and threshold. Setup threshold control and + * disable shutdown override. + */ +static int qpnp_tm_init(struct qpnp_tm_chip *chip) +{ + unsigned int stage; + int ret; + u8 reg = 0; + int crit_temp; + + mutex_lock(&chip->lock); + + ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, ®); + if (ret < 0) + goto out; + + chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK; + chip->temp = DEFAULT_TEMP; + + ret = qpnp_tm_get_temp_stage(chip); + if (ret < 0) + goto out; + chip->stage = ret; + + stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1 + ? chip->stage : alarm_state_map[chip->stage]; + + if (stage) + chip->temp = chip->thresh * TEMP_THRESH_STEP + + (stage - 1) * TEMP_STAGE_STEP + + TEMP_THRESH_MIN; + + crit_temp = qpnp_tm_get_critical_trip_temp(chip); + ret = qpnp_tm_update_critical_trip_temp(chip, crit_temp); + if (ret < 0) + goto out; + + /* Enable the thermal alarm PMIC module in always-on mode. */ + reg = ALARM_CTRL_FORCE_ENABLE; + ret = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, reg); + + chip->initialized = true; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int qpnp_tm_probe(struct platform_device *pdev) +{ + struct qpnp_tm_chip *chip; + struct device_node *node; + u8 type, subtype; + u32 res; + int ret, irq; + + node = pdev->dev.of_node; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, chip); + chip->dev = &pdev->dev; + + mutex_init(&chip->lock); + + chip->map = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->map) + return -ENXIO; + + ret = of_property_read_u32(node, "reg", &res); + if (ret < 0) + return ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* ADC based measurements are optional */ + chip->adc = devm_iio_channel_get(&pdev->dev, "thermal"); + if (IS_ERR(chip->adc)) { + ret = PTR_ERR(chip->adc); + chip->adc = NULL; + if (ret == -EPROBE_DEFER) + return ret; + } + + chip->base = res; + + ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type); + if (ret < 0) { + dev_err(&pdev->dev, "could not read type\n"); + return ret; + } + + ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype); + if (ret < 0) { + dev_err(&pdev->dev, "could not read subtype\n"); + return ret; + } + + if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1 + && subtype != QPNP_TM_SUBTYPE_GEN2)) { + dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n", + type, subtype); + return -ENODEV; + } + + chip->subtype = subtype; + + /* + * Register the sensor before initializing the hardware to be able to + * read the trip points. get_temp() returns the default temperature + * before the hardware initialization is completed. + */ + chip->tz_dev = devm_thermal_zone_of_sensor_register( + &pdev->dev, 0, chip, &qpnp_tm_sensor_ops); + if (IS_ERR(chip->tz_dev)) { + dev_err(&pdev->dev, "failed to register sensor\n"); + return PTR_ERR(chip->tz_dev); + } + + ret = qpnp_tm_init(chip); + if (ret < 0) { + dev_err(&pdev->dev, "init failed\n"); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr, + IRQF_ONESHOT, node->name, chip); + if (ret < 0) + return ret; + + thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED); + + return 0; +} + +static const struct of_device_id qpnp_tm_match_table[] = { + { .compatible = "qcom,spmi-temp-alarm" }, + { } +}; +MODULE_DEVICE_TABLE(of, qpnp_tm_match_table); + +static struct platform_driver qpnp_tm_driver = { + .driver = { + .name = "spmi-temp-alarm", + .of_match_table = qpnp_tm_match_table, + }, + .probe = qpnp_tm_probe, +}; +module_platform_driver(qpnp_tm_driver); + +MODULE_ALIAS("platform:spmi-temp-alarm"); +MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 24ef9ec891c761483c5270528781b8637b908eef Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Mon, 17 Dec 2018 10:57:15 +1100 Subject: thermal/intel: fixup for Kconfig string parsing tightening up Signed-off-by: Stephen Rothwell --- drivers/thermal/intel/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/thermal') diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig index 9c06d4ad7c97..2e013eeb4a1d 100644 --- a/drivers/thermal/intel/Kconfig +++ b/drivers/thermal/intel/Kconfig @@ -55,7 +55,7 @@ config INTEL_QUARK_DTS_THERMAL underlying BIOS/Firmware. menu "ACPI INT340X thermal drivers" -source drivers/thermal/intel/int340x_thermal/Kconfig +source "drivers/thermal/intel/int340x_thermal/Kconfig" endmenu config INTEL_BXT_PMIC_THERMAL -- cgit v1.2.3