| /* |
| * intel_soc_thermal.c - Intel SoC Platform Thermal Driver |
| * |
| * Copyright (C) 2012 Intel Corporation |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; version 2 of the License. |
| * |
| * 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. |
| * |
| * 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., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * Author: Shravan B M <shravan.k.b.m@intel.com> |
| * |
| * This driver registers to Thermal framework as SoC zone. It exposes |
| * two SoC DTS temperature with two writeable trip points. |
| */ |
| |
| #define pr_fmt(fmt) "intel_soc_thermal: " fmt |
| |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/debugfs.h> |
| #include <linux/thermal.h> |
| #include <linux/seq_file.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <asm/msr.h> |
| #include <asm/intel-mid.h> |
| #include <asm/intel_mid_thermal.h> |
| #include <asm/processor.h> |
| |
| #define DRIVER_NAME "soc_thrm" |
| |
| /* SOC DTS Registers */ |
| #define SOC_THERMAL_SENSORS 2 |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| #define SOC_THERMAL_TRIPS 2 |
| #define DTS_TRIP_RW 0x03 |
| #else |
| #define SOC_THERMAL_TRIPS 0 |
| #define DTS_TRIP_RW 0 |
| #endif |
| #define SOC_MAX_STATES 4 |
| #define DTS_ENABLE_REG 0xB0 |
| #define DTS_ENABLE 0x03 |
| #ifdef CONFIG_TP_ENABLE |
| #define TP_SETTING_REG 0x01 |
| #define ENABLE_TP_GAIN 0x10a |
| #endif |
| |
| #define PUNIT_PORT 0x04 |
| #define PUNIT_TEMP_REG 0xB1 |
| #define PUNIT_AUX_REG 0xB2 |
| |
| #define TJMAX_TEMP 90 |
| #define TJMAX_CODE 0x7F |
| |
| /* Default hysteresis values in C */ |
| #define DEFAULT_H2C_HYST 1 |
| #define MAX_HYST 7 |
| |
| /* Power Limit registers */ |
| #define PKG_TURBO_POWER_LIMIT 0x610 |
| #define PKG_TURBO_CFG 0x670 |
| #define MSR_THERM_CFG1 0x673 |
| |
| /* PKG_TURBO_PL1 holds PL1 in terms of 32mW */ |
| #define PL_UNIT_MW 32 |
| |
| /* Magic number symbolising Dynamic Turbo OFF */ |
| #define DISABLE_DYNAMIC_TURBO 0xB0FF |
| |
| /* IRQ details */ |
| #define SOC_DTS_CONTROL 0x80 |
| #define TRIP_STATUS_RO 0xB3 |
| #define TRIP_STATUS_RW 0xB4 |
| /* TE stands for THERMAL_EVENT */ |
| #define TE_AUX0 0xB5 |
| #define TE_AUX3 0xB8 |
| #define ENABLE_AUX_EVENT 0x0F |
| #define ENABLE_CPU0 (1 << 16) |
| #define ENABLE_CPU1 (1 << 17) |
| #define RTE_ENABLE (1 << 9) |
| |
| static int tjmax_temp; |
| static int turbo_floor_reg; |
| |
| static DEFINE_MUTEX(thrm_update_lock); |
| |
| struct platform_soc_data { |
| struct thermal_zone_device *tzd[SOC_THERMAL_SENSORS]; |
| struct thermal_cooling_device *soc_cdev; /* PL1 control */ |
| int irq; |
| }; |
| |
| struct cooling_device_info { |
| struct soc_throttle_data *soc_data; |
| /* Lock protecting the soc_cur_state variable */ |
| struct mutex lock_state; |
| unsigned long soc_cur_state; |
| }; |
| |
| struct thermal_device_info { |
| int sensor_index; |
| struct mutex lock_aux; |
| }; |
| |
| static inline u32 read_soc_reg(unsigned int addr) |
| { |
| return intel_mid_msgbus_read32(PUNIT_PORT, addr); |
| } |
| |
| static inline void write_soc_reg(unsigned int addr, u32 val) |
| { |
| intel_mid_msgbus_write32(PUNIT_PORT, addr, val); |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| struct dts_regs { |
| char *name; |
| u32 addr; |
| } dts_regs[] = { |
| /* Thermal Management Registers */ |
| {"PTMC", 0x80}, |
| {"TRR0", 0x81}, |
| {"TRR1", 0x82}, |
| {"TTS", 0x83}, |
| {"TELB", 0x84}, |
| {"TELT", 0x85}, |
| {"GFXT", 0x88}, |
| {"VEDT", 0x89}, |
| {"VECT", 0x8A}, |
| {"VSPT", 0x8B}, |
| {"ISPT", 0x8C}, |
| {"SWT", 0x8D}, |
| /* Trip Event Registers */ |
| {"DTSC", 0xB0}, |
| {"TRR", 0xB1}, |
| {"PTPS", 0xB2}, |
| {"PTTS", 0xB3}, |
| {"PTTSS", 0xB4}, |
| {"TE_AUX0", 0xB5}, |
| {"TE_AUX1", 0xB6}, |
| {"TE_AUX2", 0xB7}, |
| {"TE_AUX3", 0xB8}, |
| {"TTE_VRIcc", 0xB9}, |
| {"TTE_VRHOT", 0xBA}, |
| {"TTE_PROCHOT", 0xBB}, |
| {"TTE_SLM0", 0xBC}, |
| {"TTE_SLM1", 0xBD}, |
| {"BWTE", 0xBE}, |
| {"TTE_SWT", 0xBF}, |
| /* MSI Message Registers */ |
| {"TMA", 0xC0}, |
| {"TMD", 0xC1}, |
| }; |
| |
| /* /sys/kernel/debug/soc_thermal/soc_dts */ |
| static struct dentry *soc_dts_dent; |
| static struct dentry *soc_thermal_dir; |
| |
| static int soc_dts_debugfs_show(struct seq_file *s, void *unused) |
| { |
| int i; |
| u32 val; |
| |
| for (i = 0; i < ARRAY_SIZE(dts_regs); i++) { |
| val = read_soc_reg(dts_regs[i].addr); |
| seq_printf(s, |
| "%s[0x%X] Val: 0x%X\n", |
| dts_regs[i].name, dts_regs[i].addr, val); |
| } |
| return 0; |
| } |
| |
| static int debugfs_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, soc_dts_debugfs_show, NULL); |
| } |
| |
| static const struct file_operations soc_dts_debugfs_fops = { |
| .open = debugfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static void create_soc_dts_debugfs(void) |
| { |
| int err; |
| |
| /* /sys/kernel/debug/soc_thermal/ */ |
| soc_thermal_dir = debugfs_create_dir("soc_thermal", NULL); |
| if (IS_ERR(soc_thermal_dir)) { |
| err = PTR_ERR(soc_thermal_dir); |
| pr_err("debugfs_create_dir failed:%d\n", err); |
| return; |
| } |
| |
| /* /sys/kernel/debug/soc_thermal/soc_dts */ |
| soc_dts_dent = debugfs_create_file("soc_dts", S_IFREG | S_IRUGO, |
| soc_thermal_dir, NULL, |
| &soc_dts_debugfs_fops); |
| if (IS_ERR(soc_dts_dent)) { |
| err = PTR_ERR(soc_dts_dent); |
| debugfs_remove_recursive(soc_thermal_dir); |
| pr_err("debugfs_create_file failed:%d\n", err); |
| } |
| } |
| |
| static void remove_soc_dts_debugfs(void) |
| { |
| debugfs_remove_recursive(soc_thermal_dir); |
| } |
| #else |
| static inline void create_soc_dts_debugfs(void) { } |
| static inline void remove_soc_dts_debugfs(void) { } |
| #endif |
| |
| static |
| struct cooling_device_info *initialize_cdev(struct platform_device *pdev) |
| { |
| struct cooling_device_info *cdev_info = |
| kzalloc(sizeof(struct cooling_device_info), GFP_KERNEL); |
| if (!cdev_info) |
| return NULL; |
| |
| cdev_info->soc_data = pdev->dev.platform_data; |
| mutex_init(&cdev_info->lock_state); |
| return cdev_info; |
| } |
| |
| static struct thermal_device_info *initialize_sensor(int index) |
| { |
| struct thermal_device_info *td_info = |
| kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); |
| |
| if (!td_info) |
| return NULL; |
| td_info->sensor_index = index; |
| mutex_init(&td_info->lock_aux); |
| |
| return td_info; |
| } |
| |
| static void initialize_floor_reg_addr(void) |
| { |
| struct cpuinfo_x86 *c = &cpu_data(0); |
| |
| if (c->x86_model == 0x4a || c->x86_model == 0x5a) |
| turbo_floor_reg = 0xdf; |
| else |
| turbo_floor_reg = 0x2; |
| } |
| |
| static void enable_soc_dts(void) |
| { |
| __maybe_unused int i; |
| u32 val, eax, edx; |
| |
| rdmsr_on_cpu(0, MSR_THERM_CFG1, &eax, &edx); |
| |
| /* B[8:10] H2C Hyst */ |
| eax = (eax & ~(0x7 << 8)) | (DEFAULT_H2C_HYST << 8); |
| |
| /* Set the Hysteresis value */ |
| wrmsr_on_cpu(0, MSR_THERM_CFG1, eax, edx); |
| |
| /* Enable the DTS */ |
| write_soc_reg(DTS_ENABLE_REG, DTS_ENABLE); |
| |
| val = read_soc_reg(SOC_DTS_CONTROL); |
| write_soc_reg(SOC_DTS_CONTROL, val | ENABLE_AUX_EVENT | ENABLE_CPU0 | ENABLE_CPU1); |
| |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| /* Enable Interrupts for all the AUX trips for the DTS */ |
| for (i = 0; i < SOC_THERMAL_TRIPS; i++) { |
| val = read_soc_reg(TE_AUX0 + i); |
| write_soc_reg(TE_AUX0 + i, (val | RTE_ENABLE)); |
| } |
| #else |
| /* Disable interrupt for AUX3 explicitly since enabled from FW */ |
| val = read_soc_reg(TE_AUX3); |
| write_soc_reg(TE_AUX3, (val & (~RTE_ENABLE))); |
| #endif |
| #ifdef CONFIG_TP_ENABLE |
| /* Enable TP algorithm & set gain for controlling DTS temp */ |
| val = read_soc_reg(TP_SETTING_REG); |
| write_soc_reg(TP_SETTING_REG, (val | ENABLE_TP_GAIN)); |
| #endif |
| } |
| |
| static int show_temp(struct thermal_zone_device *tzd, long *temp) |
| { |
| struct thermal_device_info *td_info = tzd->devdata; |
| u32 val = read_soc_reg(PUNIT_TEMP_REG); |
| |
| /* Extract bits[0:7] or [8:15] using sensor_index */ |
| *temp = (val >> (8 * td_info->sensor_index)) & 0xFF; |
| |
| if (*temp == 0) |
| return 0; |
| |
| /* Calibrate the temperature */ |
| *temp = TJMAX_CODE - *temp + tjmax_temp; |
| |
| /* Convert to mC */ |
| *temp *= 1000; |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| static int show_trip_hyst(struct thermal_zone_device *tzd, |
| int trip, long *hyst) |
| { |
| u32 eax, edx; |
| struct thermal_device_info *td_info = tzd->devdata; |
| |
| /* Hysteresis is only supported for trip point 0 */ |
| if (trip != 0) { |
| *hyst = 0; |
| return 0; |
| } |
| |
| mutex_lock(&td_info->lock_aux); |
| |
| rdmsr_on_cpu(0, MSR_THERM_CFG1, &eax, &edx); |
| |
| /* B[8:10] H2C Hyst, for trip 0. Report hysteresis in mC */ |
| *hyst = ((eax >> 8) & 0x7) * 1000; |
| |
| mutex_unlock(&td_info->lock_aux); |
| return 0; |
| } |
| |
| static int store_trip_hyst(struct thermal_zone_device *tzd, |
| int trip, long hyst) |
| { |
| u32 eax, edx; |
| struct thermal_device_info *td_info = tzd->devdata; |
| |
| /* Convert from mC to C */ |
| hyst /= 1000; |
| |
| if (trip != 0 || hyst < 0 || hyst > MAX_HYST) |
| return -EINVAL; |
| |
| mutex_lock(&td_info->lock_aux); |
| |
| rdmsr_on_cpu(0, MSR_THERM_CFG1, &eax, &edx); |
| |
| /* B[8:10] H2C Hyst */ |
| eax = (eax & ~(0x7 << 8)) | (hyst << 8); |
| |
| wrmsr_on_cpu(0, MSR_THERM_CFG1, eax, edx); |
| |
| mutex_unlock(&td_info->lock_aux); |
| return 0; |
| } |
| |
| static int show_trip_type(struct thermal_zone_device *tzd, |
| int trip, enum thermal_trip_type *trip_type) |
| { |
| /* All are passive trip points */ |
| *trip_type = THERMAL_TRIP_PASSIVE; |
| |
| return 0; |
| } |
| |
| static int show_trip_temp(struct thermal_zone_device *tzd, |
| int trip, long *trip_temp) |
| { |
| u32 aux_value = read_soc_reg(PUNIT_AUX_REG); |
| |
| /* aux0 b[0:7], aux1 b[8:15], aux2 b[16:23], aux3 b[24:31] */ |
| *trip_temp = (aux_value >> (8 * trip)) & 0xFF; |
| |
| /* Calibrate the trip point temperature */ |
| *trip_temp = tjmax_temp - *trip_temp; |
| |
| /* Convert to mC and report */ |
| *trip_temp *= 1000; |
| |
| return 0; |
| } |
| |
| static int store_trip_temp(struct thermal_zone_device *tzd, |
| int trip, long trip_temp) |
| { |
| u32 aux_trip, aux = 0; |
| struct thermal_device_info *td_info = tzd->devdata; |
| |
| /* Convert from mC to C */ |
| trip_temp /= 1000; |
| |
| /* Do not program aux thresholds above TjMax */ |
| if (trip_temp > tjmax_temp) |
| return -EINVAL; |
| |
| /* Assign last byte to unsigned 32 */ |
| aux_trip = trip_temp & 0xFF; |
| |
| /* Calibrate w.r.t TJMAX_TEMP */ |
| aux_trip = tjmax_temp - aux_trip; |
| |
| mutex_lock(&td_info->lock_aux); |
| aux = read_soc_reg(PUNIT_AUX_REG); |
| switch (trip) { |
| case 0: |
| /* aux0 bits 0:7 */ |
| aux = (aux & 0xFFFFFF00) | (aux_trip << (8 * trip)); |
| break; |
| case 1: |
| /* aux1 bits 8:15 */ |
| aux = (aux & 0xFFFF00FF) | (aux_trip << (8 * trip)); |
| break; |
| } |
| write_soc_reg(PUNIT_AUX_REG, aux); |
| |
| mutex_unlock(&td_info->lock_aux); |
| |
| return 0; |
| } |
| #endif |
| |
| /* SoC cooling device callbacks */ |
| static int soc_get_max_state(struct thermal_cooling_device *cdev, |
| unsigned long *state) |
| { |
| /* SoC has 4 levels of throttling from 0 to 3 */ |
| *state = SOC_MAX_STATES - 1; |
| return 0; |
| } |
| |
| static int soc_get_cur_state(struct thermal_cooling_device *cdev, |
| unsigned long *state) |
| { |
| struct cooling_device_info *cdev_info = |
| (struct cooling_device_info *)cdev->devdata; |
| |
| mutex_lock(&cdev_info->lock_state); |
| *state = cdev_info->soc_cur_state; |
| mutex_unlock(&cdev_info->lock_state); |
| |
| return 0; |
| } |
| |
| static void set_floor_freq(int val) |
| { |
| u32 eax; |
| |
| eax = read_soc_reg(turbo_floor_reg); |
| |
| /* Set bits[8:14] of eax to val */ |
| eax = (eax & ~(0x7F << 8)) | (val << 8); |
| |
| write_soc_reg(turbo_floor_reg, eax); |
| } |
| |
| static int disable_dynamic_turbo(struct cooling_device_info *cdev_info) |
| { |
| u32 eax, edx; |
| |
| mutex_lock(&cdev_info->lock_state); |
| |
| rdmsr_on_cpu(0, PKG_TURBO_CFG, &eax, &edx); |
| |
| /* Set bits[0:2] to 0 to enable TjMax Turbo mode */ |
| eax = eax & ~0x07; |
| |
| /* Set bit[8] to 0 to disable Dynamic Turbo */ |
| eax = eax & ~(1 << 8); |
| |
| /* Set bits[9:11] to 0 disable Dynamic Turbo Policy */ |
| eax = eax & ~(0x07 << 9); |
| |
| wrmsr_on_cpu(0, PKG_TURBO_CFG, eax, edx); |
| |
| /* |
| * Now that we disabled Dynamic Turbo, we can |
| * make the floor frequency ratio also 0. |
| */ |
| set_floor_freq(0); |
| |
| cdev_info->soc_cur_state = DISABLE_DYNAMIC_TURBO; |
| |
| mutex_unlock(&cdev_info->lock_state); |
| return 0; |
| } |
| |
| static int soc_set_cur_state(struct thermal_cooling_device *cdev, |
| unsigned long state) |
| { |
| u32 eax, edx; |
| struct soc_throttle_data *data; |
| struct cooling_device_info *cdev_info = |
| (struct cooling_device_info *)cdev->devdata; |
| |
| if (state == DISABLE_DYNAMIC_TURBO) |
| return disable_dynamic_turbo(cdev_info); |
| |
| if (state >= SOC_MAX_STATES) { |
| pr_err("Invalid SoC throttle state:%ld\n", state); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&cdev_info->lock_state); |
| |
| data = &cdev_info->soc_data[state]; |
| |
| rdmsr_on_cpu(0, PKG_TURBO_POWER_LIMIT, &eax, &edx); |
| |
| /* Set bits[0:14] of eax to 'data->power_limit' */ |
| eax = (eax & ~0x7FFF) | data->power_limit; |
| |
| wrmsr_on_cpu(0, PKG_TURBO_POWER_LIMIT, eax, edx); |
| |
| set_floor_freq(data->floor_freq); |
| |
| cdev_info->soc_cur_state = state; |
| |
| mutex_unlock(&cdev_info->lock_state); |
| return 0; |
| } |
| |
| #ifdef CONFIG_DEBUG_THERMAL |
| static int soc_get_force_state_override(struct thermal_cooling_device *cdev, |
| char *buf) |
| { |
| int i; |
| int pl1_vals_mw[SOC_MAX_STATES]; |
| struct cooling_device_info *cdev_info = |
| (struct cooling_device_info *)cdev->devdata; |
| |
| mutex_lock(&cdev_info->lock_state); |
| |
| /* PKG_TURBO_PL1 holds PL1 in terms of 32mW. So, multiply by 32 */ |
| for (i = 0; i < SOC_MAX_STATES; i++) { |
| pl1_vals_mw[i] = |
| cdev_info->soc_data[i].power_limit * PL_UNIT_MW; |
| } |
| |
| mutex_unlock(&cdev_info->lock_state); |
| |
| return sprintf(buf, "%d %d %d %d\n", pl1_vals_mw[0], pl1_vals_mw[1], |
| pl1_vals_mw[2], pl1_vals_mw[3]); |
| } |
| |
| static int soc_set_force_state_override(struct thermal_cooling_device *cdev, |
| char *buf) |
| { |
| int i, ret; |
| int pl1_vals_mw[SOC_MAX_STATES]; |
| unsigned long cur_state; |
| struct cooling_device_info *cdev_info = |
| (struct cooling_device_info *)cdev->devdata; |
| |
| /* |
| * The four space separated values entered via the sysfs node |
| * override the default values configured through platform data. |
| */ |
| ret = sscanf(buf, "%d %d %d %d", &pl1_vals_mw[0], &pl1_vals_mw[1], |
| &pl1_vals_mw[2], &pl1_vals_mw[3]); |
| if (ret != SOC_MAX_STATES) { |
| pr_err("Invalid values in soc_set_force_state_override\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&cdev_info->lock_state); |
| |
| /* PKG_TURBO_PL1 takes PL1 in terms of 32mW. So, divide by 32 */ |
| for (i = 0; i < SOC_MAX_STATES; i++) { |
| cdev_info->soc_data[i].power_limit = |
| pl1_vals_mw[i] / PL_UNIT_MW; |
| } |
| |
| /* Update the cur_state value of this cooling device */ |
| cur_state = cdev_info->soc_cur_state; |
| |
| mutex_unlock(&cdev_info->lock_state); |
| |
| return soc_set_cur_state(cdev, cur_state); |
| } |
| #endif |
| |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| static void notify_thermal_event(struct thermal_zone_device *tzd, |
| long temp, int event, int level) |
| { |
| char *thermal_event[5]; |
| |
| /* |
| * Send UEvents only when temperature goes below the lower |
| * temperature threshold or above the upper temperature threshold. |
| */ |
| if ((event == 0 && level == 1) || (event == 1 && level == 0)) |
| return; |
| |
| pr_info("Thermal Event: sensor: %s, cur_temp: %ld, event: %d, level: %d\n", |
| tzd->type, temp, event, level); |
| |
| thermal_event[0] = kasprintf(GFP_KERNEL, "NAME=%s", tzd->type); |
| thermal_event[1] = kasprintf(GFP_KERNEL, "TEMP=%ld", temp); |
| thermal_event[2] = kasprintf(GFP_KERNEL, "EVENT=%d", event); |
| thermal_event[3] = kasprintf(GFP_KERNEL, "LEVEL=%d", level); |
| thermal_event[4] = NULL; |
| |
| kobject_uevent_env(&tzd->device.kobj, KOBJ_CHANGE, thermal_event); |
| |
| kfree(thermal_event[3]); |
| kfree(thermal_event[2]); |
| kfree(thermal_event[1]); |
| kfree(thermal_event[0]); |
| |
| return; |
| } |
| |
| static int get_max_temp(struct platform_soc_data *pdata, long *cur_temp) |
| { |
| int i, ret; |
| long temp; |
| |
| /* |
| * The SoC has two or more DTS placed, to determine the |
| * temperature of the SoC. The hardware actions are taken |
| * using T(DTS) which is MAX(T(DTS0), T(DTS1), ... T(DTSn)) |
| * |
| * Do not report error, as long as we can read at least |
| * one DTS correctly. |
| */ |
| ret = show_temp(pdata->tzd[0], cur_temp); |
| if (ret) |
| return ret; |
| |
| for (i = 1; i < SOC_THERMAL_SENSORS; i++) { |
| ret = show_temp(pdata->tzd[i], &temp); |
| if (ret) |
| goto fail_safe; |
| |
| if (temp > *cur_temp) |
| *cur_temp = temp; |
| } |
| |
| fail_safe: |
| /* |
| * We have one valid DTS temperature; Use that, |
| * instead of reporting error. |
| */ |
| return 0; |
| } |
| |
| static irqreturn_t soc_dts_intrpt(int irq, void *dev_data) |
| { |
| u32 irq_sts, cur_sts; |
| int i, ret, event, level = -1; |
| long cur_temp; |
| struct thermal_zone_device *tzd; |
| struct platform_soc_data *pdata = (struct platform_soc_data *)dev_data; |
| |
| if (!pdata || !pdata->tzd[0]) |
| return IRQ_NONE; |
| |
| mutex_lock(&thrm_update_lock); |
| |
| tzd = pdata->tzd[0]; |
| |
| irq_sts = read_soc_reg(TRIP_STATUS_RW); |
| cur_sts = read_soc_reg(TRIP_STATUS_RO); |
| |
| for (i = 0; i < SOC_THERMAL_TRIPS; i++) { |
| if (irq_sts & (1 << i)) { |
| level = i; |
| event = !!(cur_sts & (1 << i)); |
| break; |
| } |
| } |
| |
| /* Clear the status bits */ |
| write_soc_reg(TRIP_STATUS_RW, irq_sts); |
| |
| /* level == -1, indicates an invalid event */ |
| if (level == -1) { |
| dev_err(&tzd->device, "Invalid event from SoC DTS\n"); |
| goto exit; |
| } |
| |
| ret = get_max_temp(pdata, &cur_temp); |
| if (ret) { |
| dev_err(&tzd->device, "Cannot read SoC DTS temperature\n"); |
| goto exit; |
| } |
| |
| /* Notify using UEvent */ |
| notify_thermal_event(tzd, cur_temp, event, level); |
| |
| exit: |
| mutex_unlock(&thrm_update_lock); |
| return IRQ_HANDLED; |
| } |
| #endif |
| static struct thermal_zone_device_ops tzd_ops = { |
| .get_temp = show_temp, |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| .get_trip_type = show_trip_type, |
| .get_trip_temp = show_trip_temp, |
| .set_trip_temp = store_trip_temp, |
| .get_trip_hyst = show_trip_hyst, |
| .set_trip_hyst = store_trip_hyst, |
| #endif |
| }; |
| |
| static struct thermal_cooling_device_ops soc_cooling_ops = { |
| .get_max_state = soc_get_max_state, |
| .get_cur_state = soc_get_cur_state, |
| .set_cur_state = soc_set_cur_state, |
| #ifdef CONFIG_DEBUG_THERMAL |
| .get_force_state_override = soc_get_force_state_override, |
| .set_force_state_override = soc_set_force_state_override, |
| #endif |
| }; |
| |
| /********************************************************************* |
| * Driver initialization and finalization |
| *********************************************************************/ |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| static irqreturn_t soc_dts_intrpt_handler(int irq, void *dev_data) |
| { |
| return IRQ_WAKE_THREAD; |
| } |
| #endif |
| |
| static int soc_thermal_probe(struct platform_device *pdev) |
| { |
| struct platform_soc_data *pdata; |
| int i, ret; |
| u32 eax, edx; |
| static char *name[SOC_THERMAL_SENSORS] = {"SoC_DTS0", "SoC_DTS1"}; |
| |
| /* |
| * Register to configure floor frequency for DT done |
| * using shadow register for ANN and TNG, Register address |
| * chosen based on cpu model.[Refer:HSD:4380040] |
| */ |
| initialize_floor_reg_addr(); |
| |
| pdata = kzalloc(sizeof(struct platform_soc_data), GFP_KERNEL); |
| if (!pdata) |
| return -ENOMEM; |
| |
| ret = rdmsr_safe_on_cpu(0, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); |
| if (ret) { |
| tjmax_temp = TJMAX_TEMP; |
| dev_err(&pdev->dev, "TjMax read from MSR %x failed, error:%d\n", |
| MSR_IA32_TEMPERATURE_TARGET, ret); |
| } else { |
| tjmax_temp = (eax >> 16) & 0xff; |
| dev_dbg(&pdev->dev, "TjMax is %d degrees C\n", tjmax_temp); |
| } |
| |
| /* Register each sensor with the generic thermal framework */ |
| for (i = 0; i < SOC_THERMAL_SENSORS; i++) { |
| pdata->tzd[i] = thermal_zone_device_register(name[i], |
| SOC_THERMAL_TRIPS, DTS_TRIP_RW, |
| initialize_sensor(i), |
| &tzd_ops, NULL, 0, 0); |
| if (IS_ERR(pdata->tzd[i])) { |
| ret = PTR_ERR(pdata->tzd[i]); |
| dev_err(&pdev->dev, "tzd register failed: %d\n", ret); |
| goto exit_reg; |
| } |
| } |
| |
| /* Register a cooling device for PL1 (power limit) control */ |
| pdata->soc_cdev = thermal_cooling_device_register("SoC", |
| initialize_cdev(pdev), |
| &soc_cooling_ops); |
| if (IS_ERR(pdata->soc_cdev)) { |
| ret = PTR_ERR(pdata->soc_cdev); |
| pdata->soc_cdev = NULL; |
| goto exit_reg; |
| } |
| |
| platform_set_drvdata(pdev, pdata); |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| ret = platform_get_irq(pdev, 0); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "platform_get_irq failed:%d\n", ret); |
| goto exit_cdev; |
| } |
| |
| pdata->irq = ret; |
| |
| /* Register for Interrupt Handler */ |
| ret = request_threaded_irq(pdata->irq, soc_dts_intrpt_handler, |
| soc_dts_intrpt, |
| IRQF_TRIGGER_RISING, |
| DRIVER_NAME, pdata); |
| if (ret) { |
| dev_err(&pdev->dev, "request_threaded_irq failed:%d\n", ret); |
| goto exit_cdev; |
| } |
| #endif |
| /* Enable DTS0 and DTS1 */ |
| enable_soc_dts(); |
| |
| create_soc_dts_debugfs(); |
| |
| return 0; |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| exit_cdev: |
| thermal_cooling_device_unregister(pdata->soc_cdev); |
| #endif |
| exit_reg: |
| while (--i >= 0) { |
| struct thermal_device_info *td_info = pdata->tzd[i]->devdata; |
| kfree(td_info); |
| thermal_zone_device_unregister(pdata->tzd[i]); |
| } |
| platform_set_drvdata(pdev, NULL); |
| kfree(pdata); |
| return ret; |
| } |
| |
| static int soc_thermal_remove(struct platform_device *pdev) |
| { |
| int i; |
| struct platform_soc_data *pdata = platform_get_drvdata(pdev); |
| |
| /* Unregister each sensor with the generic thermal framework */ |
| for (i = 0; i < SOC_THERMAL_SENSORS; i++) { |
| struct thermal_device_info *td_info = pdata->tzd[i]->devdata; |
| kfree(td_info); |
| thermal_zone_device_unregister(pdata->tzd[i]); |
| } |
| thermal_cooling_device_unregister(pdata->soc_cdev); |
| platform_set_drvdata(pdev, NULL); |
| #ifdef CONFIG_SENSORS_SOCDTS_INTERRUPT |
| free_irq(pdata->irq, pdata); |
| #endif |
| kfree(pdata); |
| |
| remove_soc_dts_debugfs(); |
| |
| return 0; |
| } |
| |
| static const struct platform_device_id therm_id_table[] = { |
| { DRIVER_NAME, 1}, |
| }; |
| |
| static struct platform_driver soc_thermal_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = DRIVER_NAME, |
| }, |
| .probe = soc_thermal_probe, |
| .remove = soc_thermal_remove, |
| .id_table = therm_id_table, |
| }; |
| |
| static int __init soc_thermal_module_init(void) |
| { |
| return platform_driver_register(&soc_thermal_driver); |
| } |
| |
| static void __exit soc_thermal_module_exit(void) |
| { |
| platform_driver_unregister(&soc_thermal_driver); |
| } |
| |
| module_init(soc_thermal_module_init); |
| module_exit(soc_thermal_module_exit); |
| |
| MODULE_AUTHOR("Shravan B M <shravan.k.b.m@intel.com>"); |
| MODULE_DESCRIPTION("Intel SoC Thermal Driver"); |
| MODULE_LICENSE("GPL"); |