diff options
Diffstat (limited to 'drivers/thermal/intel/intel_soc_dts_iosf.c')
| -rw-r--r-- | drivers/thermal/intel/intel_soc_dts_iosf.c | 393 |
1 files changed, 393 insertions, 0 deletions
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..ea87439fe7a9 --- /dev/null +++ b/drivers/thermal/intel/intel_soc_dts_iosf.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * intel_soc_dts_iosf.c + * Copyright (c) 2015, Intel Corporation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bitops.h> +#include <linux/intel_tcc.h> +#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 + +/* Mask for two trips in status bits */ +#define SOC_DTS_TRIP_MASK 0x03 + +static int update_trip_temp(struct intel_soc_dts_sensors *sensors, + int thres_index, int temp) +{ + int status; + u32 temp_out; + u32 out; + unsigned long update_ptps; + u32 store_ptps; + u32 store_ptmc; + u32 store_te_out; + u32 te_out; + u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE; + + 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; + + update_ptps = store_ptps; + bitmap_set_value8(&update_ptps, temp_out & 0xFF, thres_index * 8); + out = update_ptps; + + 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; + + 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, + const struct thermal_trip *trip, + int temp) +{ + struct intel_soc_dts_sensor_entry *dts = thermal_zone_device_priv(tzd); + struct intel_soc_dts_sensors *sensors = dts->sensors; + unsigned int trip_index = THERMAL_TRIP_PRIV_TO_INT(trip->priv); + int status; + + if (temp > sensors->tj_max) + return -EINVAL; + + mutex_lock(&sensors->dts_update_lock); + status = update_trip_temp(sensors, trip_index, temp); + mutex_unlock(&sensors->dts_update_lock); + + return status; +} + +static int sys_get_curr_temp(struct thermal_zone_device *tzd, + int *temp) +{ + int status; + u32 out; + struct intel_soc_dts_sensor_entry *dts = thermal_zone_device_priv(tzd); + struct intel_soc_dts_sensors *sensors; + unsigned long raw; + + sensors = dts->sensors; + status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, + SOC_DTS_OFFSET_TEMP, &out); + if (status) + return status; + + raw = out; + out = bitmap_get_value8(&raw, dts->id * 8) - SOC_DTS_TJMAX_ENCODING; + *temp = sensors->tj_max - out * 1000; + + return 0; +} + +static const struct thermal_zone_device_ops tzone_ops = { + .get_temp = sys_get_curr_temp, + .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) +{ + 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, + struct thermal_trip *trips) +{ + char name[10]; + u32 store_ptps; + int ret; + + /* 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; + + /* 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) { + int i; + + for (i = 0; i <= 1; i++) { + if (store_ptps & (0xFFU << i * 8)) + trips[i].flags &= ~THERMAL_TRIP_FLAG_RW_TEMP; + } + } + snprintf(name, sizeof(name), "soc_dts%d", id); + dts->tzone = thermal_zone_device_register_with_trips(name, trips, + SOC_MAX_DTS_TRIPS, + dts, &tzone_ops, + NULL, 0, 0); + if (IS_ERR(dts->tzone)) { + ret = PTR_ERR(dts->tzone); + goto err_ret; + } + ret = thermal_zone_device_enable(dts->tzone); + if (ret) + goto err_enable; + + ret = soc_dts_enable(id); + if (ret) + goto err_enable; + + return 0; +err_enable: + thermal_zone_device_unregister(dts->tzone); +err_ret: + return ret; +} + +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); + +static void dts_trips_reset(struct intel_soc_dts_sensors *sensors, int dts_index) +{ + update_trip_temp(sensors, 0, 0); + update_trip_temp(sensors, 1, 0); +} + +static void set_trip(struct thermal_trip *trip, enum thermal_trip_type type, + u8 flags, int temp, unsigned int index) +{ + trip->type = type; + trip->flags = flags; + trip->temperature = temp; + trip->priv = THERMAL_INT_TO_TRIP_PRIV(index); +} + +struct intel_soc_dts_sensors * +intel_soc_dts_iosf_init(enum intel_soc_dts_interrupt_type intr_type, + bool critical_trip, int crit_offset) +{ + struct thermal_trip trips[SOC_MAX_DTS_SENSORS][SOC_MAX_DTS_TRIPS] = { 0 }; + struct intel_soc_dts_sensors *sensors; + int tj_max; + int ret; + int i; + + if (!iosf_mbi_available()) + return ERR_PTR(-ENODEV); + + tj_max = intel_tcc_get_tjmax(-1); + if (tj_max < 0) + return ERR_PTR(tj_max); + + 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 * 1000; + + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { + int temp; + + sensors->soc_dts[i].sensors = sensors; + + set_trip(&trips[i][0], THERMAL_TRIP_PASSIVE, + THERMAL_TRIP_FLAG_RW_TEMP, 0, 0); + + ret = update_trip_temp(sensors, 0, 0); + if (ret) + goto err_reset_trips; + + if (critical_trip) { + temp = sensors->tj_max - crit_offset; + set_trip(&trips[i][1], THERMAL_TRIP_CRITICAL, 0, temp, 1); + } else { + set_trip(&trips[i][1], THERMAL_TRIP_PASSIVE, + THERMAL_TRIP_FLAG_RW_TEMP, 0, 1); + temp = 0; + } + + ret = update_trip_temp(sensors, 1, temp); + if (ret) + goto err_reset_trips; + } + + for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { + ret = add_dts_thermal_zone(i, &sensors->soc_dts[i], trips[i]); + 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_reset_trips: + for (i = 0; i < SOC_MAX_DTS_SENSORS; i++) + dts_trips_reset(sensors, i); + + 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) { + remove_dts_thermal_zone(&sensors->soc_dts[i]); + dts_trips_reset(sensors, i); + } + kfree(sensors); +} +EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit); + +MODULE_IMPORT_NS("INTEL_TCC"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SoC DTS driver using side band interface"); |
