diff options
Diffstat (limited to 'drivers/thermal')
-rw-r--r-- | drivers/thermal/Kconfig | 24 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 2 | ||||
-rw-r--r-- | drivers/thermal/int340x_thermal/processor_thermal_device.c | 59 | ||||
-rw-r--r-- | drivers/thermal/intel_powerclamp.c | 1 | ||||
-rw-r--r-- | drivers/thermal/intel_quark_dts_thermal.c | 473 | ||||
-rw-r--r-- | drivers/thermal/intel_soc_dts_iosf.c | 478 | ||||
-rw-r--r-- | drivers/thermal/intel_soc_dts_iosf.h | 62 | ||||
-rw-r--r-- | drivers/thermal/intel_soc_dts_thermal.c | 430 |
8 files changed, 1118 insertions, 411 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 80e9c45399c7..118938ee8552 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -282,9 +282,20 @@ config X86_PKG_TEMP_THERMAL 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 + 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 && IOSF_MBI + depends on X86 + select INTEL_SOC_DTS_IOSF_CORE help Enable this to register Intel SoCs (e.g. Bay Trail) platform digital temperature sensor (DTS). These SoCs have two additional DTSs in @@ -294,12 +305,23 @@ config INTEL_SOC_DTS_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. + 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 diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index ff6422e5840d..535dfee1496f 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -36,7 +36,9 @@ obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.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-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_ST_THERMAL) += st/ diff --git a/drivers/thermal/int340x_thermal/processor_thermal_device.c b/drivers/thermal/int340x_thermal/processor_thermal_device.c index 5e8d8e91ea6d..3df3dc34b124 100644 --- a/drivers/thermal/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/int340x_thermal/processor_thermal_device.c @@ -16,15 +16,20 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/pci.h> +#include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/acpi.h> #include <linux/thermal.h> #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 + /* Braswell thermal reporting device */ #define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC @@ -42,6 +47,7 @@ struct proc_thermal_device { 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 { @@ -308,6 +314,18 @@ static int int3401_remove(struct platform_device *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) { @@ -334,18 +352,57 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, 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) { - proc_thermal_remove(pci_get_drvdata(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)}, { 0, }, }; diff --git a/drivers/thermal/intel_powerclamp.c b/drivers/thermal/intel_powerclamp.c index 725718e97a0b..2e6716104d3f 100644 --- a/drivers/thermal/intel_powerclamp.c +++ b/drivers/thermal/intel_powerclamp.c @@ -697,6 +697,7 @@ static const struct x86_cpu_id intel_powerclamp_ids[] __initconst = { { X86_VENDOR_INTEL, 6, 0x4d}, { X86_VENDOR_INTEL, 6, 0x4f}, { X86_VENDOR_INTEL, 6, 0x56}, + { X86_VENDOR_INTEL, 6, 0x57}, {} }; MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); diff --git a/drivers/thermal/intel_quark_dts_thermal.c b/drivers/thermal/intel_quark_dts_thermal.c new file mode 100644 index 000000000000..4434ec812cb7 --- /dev/null +++ b/drivers/thermal/intel_quark_dts_thermal.c @@ -0,0 +1,473 @@ +/* + * 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 <boon.leong.ong@intel.com> + * 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 <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/thermal.h> +#include <asm/cpu_device_id.h> +#include <asm/iosf_mbi.h> + +#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, QRK_MBI_RMU_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, QRK_MBI_RMU_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, QRK_MBI_RMU_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, QRK_MBI_RMU_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, unsigned long *temp) +{ + int status; + u32 out; + + mutex_lock(&dts_update_mutex); + status = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_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, unsigned long *temp) +{ + return _get_trip_temp(trip, temp); +} + +static inline int sys_get_crit_temp(struct thermal_zone_device *tzd, + unsigned long *temp) +{ + return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp); +} + +static int update_trip_temp(struct soc_sensor_entry *aux_entry, + int trip, unsigned long 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, QRK_MBI_RMU_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, QRK_MBI_RMU_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, + unsigned long 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, + unsigned long *temp) +{ + u32 out; + int ret; + + mutex_lock(&dts_update_mutex); + ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_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, QRK_MBI_RMU_WRITE, + QRK_DTS_REG_OFFSET_ENABLE, + aux_entry->store_dts_enable); + + iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_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, QRK_MBI_RMU_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, QRK_MBI_RMU_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, QRK_MBI_RMU_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 <boon.leong.ong@intel.com>"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/thermal/intel_soc_dts_iosf.c b/drivers/thermal/intel_soc_dts_iosf.c new file mode 100644 index 000000000000..42e4b6ac3875 --- /dev/null +++ b/drivers/thermal/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 <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <asm/iosf_mbi.h> +#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, + unsigned long *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, BT_MBI_BUNIT_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, unsigned long 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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_READ, + SOC_DTS_OFFSET_PTMC, &out); + if (status) + goto err_restore_ptps; + + store_ptmc = out; + + status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, BT_MBI_BUNIT_WRITE, + SOC_DTS_OFFSET_PTMC, out); + if (status) + goto err_restore_te_out; + + status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, BT_MBI_BUNIT_WRITE, + SOC_DTS_OFFSET_PTMC, store_te_out); +err_restore_ptmc: + iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, + SOC_DTS_OFFSET_PTMC, store_ptmc); +err_restore_ptps: + iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, + unsigned long 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, + unsigned long *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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_READ, + SOC_DTS_OFFSET_PTMC, &ptmc_out); + ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT; + status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, + SOC_DTS_OFFSET_PTMC, ptmc_out); + + status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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); + } + } 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 new file mode 100644 index 000000000000..625e37bf93dc --- /dev/null +++ b/drivers/thermal/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 <linux/thermal.h> + +/* 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 index 9013505e43b7..4ebb31a35a64 100644 --- a/drivers/thermal/intel_soc_dts_thermal.c +++ b/drivers/thermal/intel_soc_dts_thermal.c @@ -16,431 +16,54 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> -#include <linux/slab.h> #include <linux/interrupt.h> -#include <linux/thermal.h> #include <asm/cpu_device_id.h> -#include <asm/iosf_mbi.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 - -/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */ -#define BYT_SOC_DTS_APIC_IRQ 86 - -/* 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 +#include "intel_soc_dts_iosf.h" #define CRITICAL_OFFSET_FROM_TJ_MAX 5000 -struct soc_sensor_entry { - int id; - u32 tj_max; - u32 temp_mask; - u32 temp_shift; - u32 store_status; - struct thermal_zone_device *tzone; -}; - -static struct soc_sensor_entry *soc_dts[SOC_MAX_DTS_SENSORS]; - 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."); -static DEFINE_MUTEX(aux_update_mutex); -static spinlock_t intr_notify_lock; -static int soc_dts_thres_irq; - -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, unsigned long *temp) -{ - int status; - u32 out; - struct soc_sensor_entry *aux_entry; - - aux_entry = tzd->devdata; - - if (!trip) { - /* Just return the critical temp */ - *temp = aux_entry->tj_max - crit_offset; - return 0; - } - - mutex_lock(&aux_update_mutex); - status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, - SOC_DTS_OFFSET_PTPS, &out); - mutex_unlock(&aux_update_mutex); - if (status) - return status; - - out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING; - - if (!out) - *temp = 0; - else - *temp = aux_entry->tj_max - out * 1000; - - return 0; -} - -static int update_trip_temp(struct soc_sensor_entry *aux_entry, - int thres_index, unsigned long temp) -{ - 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 | - SOC_DTS_TE_MSI_ENABLE; - - temp_out = (aux_entry->tj_max - temp) / 1000; - - status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, BT_MBI_BUNIT_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, BT_MBI_BUNIT_READ, - SOC_DTS_OFFSET_PTMC, &out); - if (status) - goto err_restore_ptps; - - store_ptmc = out; - - status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, BT_MBI_BUNIT_WRITE, - SOC_DTS_OFFSET_PTMC, out); - if (status) - goto err_restore_te_out; - - status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, - SOC_DTS_TE_AUX0 + thres_index, - te_out); - if (status) - goto err_restore_te_out; - - return 0; - -err_restore_te_out: - iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, - SOC_DTS_OFFSET_PTMC, store_te_out); -err_restore_ptmc: - iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, - SOC_DTS_OFFSET_PTMC, store_ptmc); -err_restore_ptps: - iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, - unsigned long temp) -{ - struct soc_sensor_entry *aux_entry = tzd->devdata; - int status; - - if (temp > (aux_entry->tj_max - crit_offset)) - return -EINVAL; - - mutex_lock(&aux_update_mutex); - status = update_trip_temp(tzd->devdata, trip, temp); - mutex_unlock(&aux_update_mutex); - - return status; -} - -static int sys_get_trip_type(struct thermal_zone_device *thermal, - int trip, enum thermal_trip_type *type) -{ - if (trip) - *type = THERMAL_TRIP_PASSIVE; - else - *type = THERMAL_TRIP_CRITICAL; - - return 0; -} - -static int sys_get_curr_temp(struct thermal_zone_device *tzd, - unsigned long *temp) -{ - int status; - u32 out; - struct soc_sensor_entry *aux_entry; - - aux_entry = tzd->devdata; - - status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, - SOC_DTS_OFFSET_TEMP, &out); - if (status) - return status; - - out = (out & aux_entry->temp_mask) >> aux_entry->temp_shift; - out -= SOC_DTS_TJMAX_ENCODING; - *temp = aux_entry->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 void free_soc_dts(struct soc_sensor_entry *aux_entry) -{ - if (aux_entry) { - iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, - SOC_DTS_OFFSET_ENABLE, aux_entry->store_status); - thermal_zone_device_unregister(aux_entry->tzone); - kfree(aux_entry); - } -} - -static int soc_dts_enable(int id) -{ - u32 out; - int ret; - - ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, BT_MBI_BUNIT_WRITE, - SOC_DTS_OFFSET_ENABLE, out); - if (ret) - return ret; - } - - return ret; -} - -static struct soc_sensor_entry *alloc_soc_dts(int id, u32 tj_max, - bool notification_support) -{ - struct soc_sensor_entry *aux_entry; - char name[10]; - int trip_count = 0; - int trip_mask = 0; - int err; - - aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL); - if (!aux_entry) { - err = -ENOMEM; - return ERR_PTR(-ENOMEM); - } - - /* Store status to restor on exit */ - err = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, - SOC_DTS_OFFSET_ENABLE, - &aux_entry->store_status); - if (err) - goto err_ret; - - aux_entry->id = id; - aux_entry->tj_max = tj_max; - aux_entry->temp_mask = 0x00FF << (id * 8); - aux_entry->temp_shift = id * 8; - if (notification_support) { - trip_count = SOC_MAX_DTS_TRIPS; - trip_mask = 0x02; - } - snprintf(name, sizeof(name), "soc_dts%d", id); - aux_entry->tzone = thermal_zone_device_register(name, - trip_count, - trip_mask, - aux_entry, &tzone_ops, - NULL, 0, 0); - if (IS_ERR(aux_entry->tzone)) { - err = PTR_ERR(aux_entry->tzone); - goto err_ret; - } - - err = soc_dts_enable(id); - 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 void proc_thermal_interrupt(void) -{ - u32 sticky_out; - int status; - u32 ptmc_out; - unsigned long flags; - - spin_lock_irqsave(&intr_notify_lock, flags); - - /* Clear APIC interrupt */ - status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, - SOC_DTS_OFFSET_PTMC, &ptmc_out); - - ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT; - status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, - SOC_DTS_OFFSET_PTMC, ptmc_out); - - /* Read status here */ - status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_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, BT_MBI_BUNIT_WRITE, - SOC_DTS_OFFSET_PTTSS, sticky_out); - spin_unlock_irqrestore(&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(soc_dts[i]->tzone); - } - } else - spin_unlock_irqrestore(&intr_notify_lock, flags); +/* 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_irq; +static struct intel_soc_dts_sensors *soc_dts; static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data) { - proc_thermal_interrupt(); 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, X86_FAMILY_ANY, 0x37, 0, BYT_SOC_DTS_APIC_IRQ}, - { X86_VENDOR_INTEL, X86_FAMILY_ANY, 0x4c, 0, 0}, {} }; MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids); static int __init intel_soc_thermal_init(void) { - u32 tj_max; int err = 0; - int i; const struct x86_cpu_id *match_cpu; match_cpu = x86_match_cpu(soc_thermal_ids); if (!match_cpu) return -ENODEV; - if (get_tj_max(&tj_max)) - return -EINVAL; - - soc_dts_thres_irq = (int)match_cpu->driver_data; - - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { - soc_dts[i] = alloc_soc_dts(i, tj_max, - soc_dts_thres_irq ? true : false); - if (IS_ERR(soc_dts[i])) { - err = PTR_ERR(soc_dts[i]); - goto err_free; - } + /* 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; } - spin_lock_init(&intr_notify_lock); + soc_dts_thres_irq = (int)match_cpu->driver_data; if (soc_dts_thres_irq) { err = request_threaded_irq(soc_dts_thres_irq, NULL, @@ -449,42 +72,31 @@ static int __init intel_soc_thermal_init(void) "soc_dts", soc_dts); if (err) { pr_err("request_threaded_irq ret %d\n", err); - goto err_free; + goto error_irq; } } - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { - err = update_trip_temp(soc_dts[i], 0, tj_max - crit_offset); - if (err) - goto err_trip_temp; - } + err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts, + crit_offset); + if (err) + goto error_trips; return 0; -err_trip_temp: - i = SOC_MAX_DTS_SENSORS; +error_trips: if (soc_dts_thres_irq) free_irq(soc_dts_thres_irq, soc_dts); -err_free: - while (--i >= 0) - free_soc_dts(soc_dts[i]); +error_irq: + intel_soc_dts_iosf_exit(soc_dts); return err; } static void __exit intel_soc_thermal_exit(void) { - int i; - - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) - update_trip_temp(soc_dts[i], 0, 0); - if (soc_dts_thres_irq) free_irq(soc_dts_thres_irq, soc_dts); - - for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) - free_soc_dts(soc_dts[i]); - + intel_soc_dts_iosf_exit(soc_dts); } module_init(intel_soc_thermal_init) |