File-copy from v4.4.100

This is the result of 'cp' from a linux-stable tree with the 'v4.4.100'
tag checked out (commit 26d6298789e695c9f627ce49a7bbd2286405798a) on
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

Please refer to that tree for all history prior to this point.

Change-Id: I8a9ee2aea93cd29c52c847d0ce33091a73ae6afe
diff --git a/drivers/thermal/intel_soc_dts_iosf.c b/drivers/thermal/intel_soc_dts_iosf.c
new file mode 100644
index 0000000..5841d1d
--- /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,
+			     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, 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, 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, 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,
+			     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, 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");