1312 lines
32 KiB
C
1312 lines
32 KiB
C
/*
|
|
* intel_byt_thermal.c - Intel Baytrail Platform Thermal Driver
|
|
*
|
|
* Copyright (C) 2013 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.
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
* Author: Durgadoss R <durgadoss.r@intel.com>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "intel_byt_thermal: " fmt
|
|
|
|
#include <linux/pm.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/kfifo.h>
|
|
#include <linux/module.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/mfd/intel_mid_pmic.h>
|
|
|
|
#include <asm/processor.h>
|
|
#include <asm/intel_mid_thermal.h>
|
|
#include <asm/intel_crystalcove_gpadc.h>
|
|
|
|
#include <linux/iio/consumer.h>
|
|
|
|
#define DEVICE_NAME "crystal_cove_thermal"
|
|
|
|
/* Number of Thermal sensors on the PMIC */
|
|
#define PMIC_THERMAL_SENSORS 4
|
|
|
|
/* Registers that govern Thermal Monitoring */
|
|
#define THRMIRQ0 0x04
|
|
#define THRMIRQ1 0x05
|
|
#define MTHRMIRQ0 0x11
|
|
#define MTHRMIRQ1 0x12
|
|
#define IRQLVL1 0x02
|
|
#define MIRQLVL1 0x0E
|
|
#define IRQ_MASK_ALL 0x0F
|
|
|
|
#define THRM_MON_CTRL0 0x8E
|
|
#define THRM_MON_CTRL1 0x8F
|
|
#define TS_ENABLE 0x90
|
|
#define TS_CRIT_ENABLE 0x91
|
|
#define TS_A0_STS 0x92
|
|
#define TS_A1_STS 0x93
|
|
#define TS_A2_STS 0xBD
|
|
|
|
#define PMICSTS (1 << 5)
|
|
#define PMICALRT (1 << 3)
|
|
#define SYS2ALRT (1 << 2)
|
|
#define SYS1ALRT (1 << 1)
|
|
#define SYS0ALRT (1 << 0)
|
|
#define THERM_EN (1 << 0)
|
|
#define ALERT_EN (1 << 6)
|
|
#define PROCHOT_EN (1 << 7)
|
|
#define IRQ_LVL1_EN (1 << 1)
|
|
#define TS_ENABLE_ALL 0x27
|
|
|
|
/* ADC to Temperature conversion table length */
|
|
#define TABLE_LENGTH 35
|
|
#define TEMP_INTERVAL 5
|
|
|
|
/* Default Alert threshold 85 C */
|
|
#define DEFAULT_MAX_TEMP 85
|
|
#define MIN_CRIT_TEMP 55
|
|
#define PMIC_DIE_MIN_CRIT_TEMP 120
|
|
|
|
/*
|
|
* Default Hysteresis value: 15 corresponds to 3C.
|
|
* Why 15? : Hysteresis value is 4 bits wide. This is
|
|
* the maximum possible value that can be supported.
|
|
* Can be changed at run-time through Sysfs interface
|
|
*/
|
|
#define DEFAULT_HYST 15
|
|
|
|
#define NUM_ALERT_LEVELS 3
|
|
#define ALERT_RW_MASK 0x07
|
|
#define LEVEL_ALERT0 0
|
|
#define LEVEL_ALERT1 1
|
|
#define LEVEL_ALERT2 2
|
|
|
|
/*
|
|
* LOW event is defined as 0 (implicit)
|
|
* HIGH event is defined as 1 (implicit)
|
|
* Hence this event is defined as 2.
|
|
*/
|
|
#define EMUL_TEMP_EVENT 2
|
|
#define TEMP_WRITE_TIMEOUT (2 * HZ)
|
|
|
|
/* Constants defined in CrystalCove PMIC spec */
|
|
#define PMIC_DIE_SENSOR 3
|
|
#define PMIC_DIE_ADC_MIN 488
|
|
#define PMIC_DIE_ADC_MAX 802
|
|
#define PMIC_DIE_TEMP_MIN -40
|
|
#define PMIC_DIE_TEMP_MAX 125
|
|
|
|
#define IRQ_FIFO_MAX 16
|
|
|
|
struct thermal_event {
|
|
int sensor; /* For which sensor ? */
|
|
int event; /* Whether LOW or HIGH event ? */
|
|
int level; /* Which alert level 0/1/2 ? */
|
|
};
|
|
|
|
static DEFINE_KFIFO(irq_fifo, struct thermal_event, IRQ_FIFO_MAX);
|
|
|
|
static const int params[3][PMIC_THERMAL_SENSORS] = {
|
|
{ TS_A0_STS, TS_A1_STS, TS_A2_STS, 0 }, /* status register */
|
|
{ SYS0ALRT, SYS1ALRT, SYS2ALRT, PMICALRT }, /* Interrupt bit */
|
|
{ SYS0ALRT, SYS1ALRT, SYS2ALRT, PMICSTS }, /* Status bit */
|
|
};
|
|
/*
|
|
* ADC Result registers: The 10 bit ADC code is stored in two registers.
|
|
* The 'high' register holds D[8:9] of the ADC code, in D[0:1]. The 'low'
|
|
* register holds D[0:7] of the ADC code. These register addresses are
|
|
* consecutive.
|
|
*/
|
|
static const int adc_res_reg_l[PMIC_THERMAL_SENSORS] = {
|
|
0x75, 0x77, 0x79, 0x7F };
|
|
/*
|
|
* Alert registers store the 'alert' temperature for each sensor,
|
|
* as 10 bit ADC code. The higher two bits are stored in bits[0:1] of
|
|
* alert_regs_h. The lower eight bits are stored in alert_regs_l.
|
|
* The hysteresis value is stored in bits[2:5] of alert_regs_h.
|
|
* Alert level 2 (also known as 'critical level') is 8 bits wide
|
|
* and hence does not have a 'high' register.
|
|
*
|
|
* static const int alert_regs_h[3][4] = {
|
|
* SYS0, SYS1, SYS2, PMIC_DIE
|
|
* Alert 0 { 0x94, 0x99, 0x9E, 0xAF },
|
|
* Alert 1 { 0x96, 0x9B, 0xA0, 0xB1 },
|
|
* Alert 2 { -, -, -, - },
|
|
* };
|
|
*/
|
|
static const int alert_regs_l[3][4] = {
|
|
/* SYS0, SYS1, SYS2, PMIC_DIE */
|
|
/* Alert 0 */ { 0x95, 0x9A, 0x9F, 0xB0 },
|
|
/* Alert 1 */ { 0x97, 0x9C, 0xA1, 0xB2 },
|
|
/* Alert 2 */ { 0x98, 0x9D, 0xA2, 0xB3 },
|
|
};
|
|
/*
|
|
* ADC code vs Temperature table
|
|
* This table will be different for different thermistors
|
|
* Row 0: ADC code
|
|
* Row 1: Temperature (in degree celsius)
|
|
*/
|
|
static const int adc_code[2][TABLE_LENGTH] = {
|
|
{977, 961, 941, 917, 887, 853, 813, 769, 720, 669, 615, 561, 508, 456,
|
|
407, 357, 315, 277, 243, 212, 186, 162, 140, 107,
|
|
94, 82, 72, 64, 56, 50, 44, 39, 35, 31},
|
|
{-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60,
|
|
65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125,
|
|
130, 135, 140, 145, 150},
|
|
};
|
|
|
|
static DEFINE_MUTEX(thrm_update_lock);
|
|
|
|
struct thermal_device_info {
|
|
struct intel_mid_thermal_sensor *sensor;
|
|
long trip_temp[NUM_ALERT_LEVELS];
|
|
struct completion temp_write_complete;
|
|
int sensor_index;
|
|
};
|
|
|
|
struct thermal_data {
|
|
struct platform_device *pdev;
|
|
struct iio_channel *iio_chan;
|
|
struct work_struct thermal_work;
|
|
struct mutex thrm_irq_lock;
|
|
struct thermal_zone_device **tzd;
|
|
/* Caching information */
|
|
bool is_initialized;
|
|
unsigned long last_updated;
|
|
int cached_vals[PMIC_THERMAL_SENSORS];
|
|
/* Details obtained from platform data */
|
|
int num_sensors;
|
|
int num_virtual_sensors;
|
|
unsigned int irq;
|
|
struct intel_mid_thermal_sensor *sensors;
|
|
/* CPU data */
|
|
bool is_vlv;
|
|
};
|
|
static struct thermal_data *tdata;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static struct thermal_regs {
|
|
char *name;
|
|
int addr;
|
|
} thermal_regs[] = {
|
|
/* Thermal Management Registers */
|
|
{"THRM_MON_CTRL0", 0x8E},
|
|
{"THRM_MON_CTRL1", 0x8F},
|
|
{"TS_ENABLE", 0x90},
|
|
{"TS_CRIT_ENABLE", 0x91},
|
|
{"TS_A0_STATUS", 0x92},
|
|
{"TS_A1_STATUS", 0x93},
|
|
{"TS_CRIT_STATUS", 0xBD},
|
|
{"IRQLVL1", 0x02},
|
|
{"MIRQLVL1", 0x0E},
|
|
{"THRMIRQ0", 0x04},
|
|
{"THRMIRQ1", 0x05},
|
|
{"MTHRMIRQ0", 0x11},
|
|
{"MTHRMIRQ1", 0x12},
|
|
{"A0_SYS0_H", 0x94},
|
|
{"A0_SYS1_H", 0x99},
|
|
{"A0_SYS2_H", 0x9E},
|
|
{"A0_BAT0_H", 0xA3},
|
|
{"A0_BAT1_H", 0xA9},
|
|
{"A0_PMIC_H", 0xAF},
|
|
};
|
|
|
|
static struct dentry *thermal_dent[ARRAY_SIZE(thermal_regs)];
|
|
static struct dentry *ccove_thermal_dir;
|
|
|
|
static int ccove_thermal_debugfs_show(struct seq_file *s, void *unused)
|
|
{
|
|
int addr = *((int *)s->private);
|
|
int val = intel_mid_pmic_readb(addr);
|
|
|
|
seq_printf(s, "Addr[0x%X] Val: 0x%X\n", addr, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, ccove_thermal_debugfs_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations ccove_thermal_debugfs_ops = {
|
|
.open = debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void create_ccove_thermal_debugfs(void)
|
|
{
|
|
int i, err;
|
|
|
|
/* /sys/kernel/debug/ccove_thermal/ */
|
|
ccove_thermal_dir = debugfs_create_dir("ccove_thermal", NULL);
|
|
if (IS_ERR(ccove_thermal_dir)) {
|
|
err = PTR_ERR(ccove_thermal_dir);
|
|
pr_err("debugfs_create_dir failed:%d\n", err);
|
|
return;
|
|
}
|
|
|
|
/* /sys/kernel/debug/ccove_thermal/REG_NAME */
|
|
for (i = 0; i < ARRAY_SIZE(thermal_regs); i++) {
|
|
thermal_dent[i] = debugfs_create_file(thermal_regs[i].name,
|
|
S_IFREG | S_IRUGO,
|
|
ccove_thermal_dir,
|
|
&thermal_regs[i].addr,
|
|
&ccove_thermal_debugfs_ops);
|
|
if (IS_ERR(thermal_dent[i])) {
|
|
err = PTR_ERR(thermal_dent[i]);
|
|
debugfs_remove_recursive(ccove_thermal_dir);
|
|
pr_debug("debugfs_create_file failed:%d\n", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void remove_ccove_thermal_debugfs(void)
|
|
{
|
|
debugfs_remove_recursive(ccove_thermal_dir);
|
|
}
|
|
#else
|
|
static inline void create_pmic_thermal_debugfs(void) { }
|
|
static inline void remove_pmic_thermal_debugfs(void) { }
|
|
#endif
|
|
|
|
static inline int adc_to_pmic_die_temp(unsigned int val)
|
|
{
|
|
/* return temperature in mC */
|
|
return 382100 - val * 526;
|
|
}
|
|
|
|
static inline int pmic_die_temp_to_adc(int temp)
|
|
{
|
|
/* 'temp' is in C, convert to mC and then do calculations */
|
|
return (382100 - temp * 1000) / 526;
|
|
}
|
|
|
|
/**
|
|
* find_adc_code - searches the ADC code using binary search
|
|
* @val: value to find in the array
|
|
*
|
|
* This function does binary search on an array sorted in 'descending' order
|
|
* Can sleep
|
|
*/
|
|
static int find_adc_code(uint16_t val)
|
|
{
|
|
int left = 0;
|
|
int right = TABLE_LENGTH - 1;
|
|
int mid;
|
|
while (left <= right) {
|
|
mid = (left + right)/2;
|
|
if (val == adc_code[0][mid] ||
|
|
(mid > 0 &&
|
|
val > adc_code[0][mid] && val < adc_code[0][mid-1]))
|
|
return mid;
|
|
else if (val > adc_code[0][mid])
|
|
right = mid - 1;
|
|
else if (val < adc_code[0][mid])
|
|
left = mid + 1;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* adc_to_temp - converts the ADC code to temperature in mC
|
|
* @direct: true if the sensor uses direct conversion
|
|
* @adc_val: the ADC code to be converted
|
|
* @tp: temperature return value
|
|
*
|
|
* Can sleep
|
|
*/
|
|
static int adc_to_temp(int direct, uint16_t adc_val, long *tp)
|
|
{
|
|
int x0, x1, y0, y1;
|
|
int nr, dr; /* Numerator & Denominator */
|
|
int indx;
|
|
int x = adc_val;
|
|
|
|
/* Direct conversion for pmic die temperature */
|
|
if (direct) {
|
|
if (adc_val < PMIC_DIE_ADC_MIN || adc_val > PMIC_DIE_ADC_MAX)
|
|
return -EINVAL;
|
|
|
|
*tp = adc_to_pmic_die_temp(adc_val);
|
|
return 0;
|
|
}
|
|
|
|
indx = find_adc_code(adc_val);
|
|
if (indx < 0)
|
|
return -EINVAL;
|
|
|
|
if (adc_code[0][indx] == adc_val) {
|
|
*tp = adc_code[1][indx] * 1000;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The ADC code is in between two values directly defined in the
|
|
* table. So, do linear interpolation to calculate the temperature.
|
|
*/
|
|
x0 = adc_code[0][indx];
|
|
x1 = adc_code[0][indx - 1];
|
|
y0 = adc_code[1][indx];
|
|
y1 = adc_code[1][indx - 1];
|
|
|
|
/*
|
|
* Find y:
|
|
* Of course, we can avoid these variables, but keep them
|
|
* for readability and maintainability.
|
|
*/
|
|
nr = (x-x0)*y1 + (x1-x)*y0;
|
|
dr = x1-x0;
|
|
|
|
if (!dr)
|
|
return -EINVAL;
|
|
/*
|
|
* We have to report the temperature in milli degree celsius.
|
|
* So, to reduce the loss of precision, do (Nr*1000)/Dr, instead
|
|
* of (Nr/Dr)*1000.
|
|
*/
|
|
*tp = (nr * 1000)/dr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* temp_to_adc - converts the temperature(in C) to ADC code
|
|
* @direct: true if the sensor uses direct conversion
|
|
* @temp: the temperature to be converted
|
|
* @adc_val: ADC code return value
|
|
*
|
|
* Can sleep
|
|
*/
|
|
static int temp_to_adc(int direct, int temp, int *adc_val)
|
|
{
|
|
int indx;
|
|
int x0, x1, y0, y1;
|
|
int nr, dr; /* Numerator & Denominator */
|
|
int x = temp;
|
|
|
|
/* Direct conversion for pmic die temperature */
|
|
if (direct) {
|
|
if (temp < PMIC_DIE_TEMP_MIN || temp > PMIC_DIE_TEMP_MAX)
|
|
return -EINVAL;
|
|
|
|
*adc_val = pmic_die_temp_to_adc(temp);
|
|
return 0;
|
|
}
|
|
|
|
if (temp < adc_code[1][0] || temp > adc_code[1][TABLE_LENGTH - 1])
|
|
return -EINVAL;
|
|
|
|
|
|
/* Find the 'indx' of this 'temp' in the table */
|
|
indx = (temp - adc_code[1][0]) / TEMP_INTERVAL;
|
|
|
|
if (temp == adc_code[1][indx]) {
|
|
*adc_val = adc_code[0][indx];
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Temperature is not a multiple of 'TEMP_INTERVAL'. So,
|
|
* do linear interpolation to obtain a better ADC code.
|
|
*/
|
|
x0 = adc_code[1][indx];
|
|
x1 = adc_code[1][indx + 1];
|
|
y0 = adc_code[0][indx];
|
|
y1 = adc_code[0][indx + 1];
|
|
|
|
nr = (x-x0)*y1 + (x1-x)*y0;
|
|
dr = x1-x0;
|
|
|
|
if (!dr)
|
|
return -EINVAL;
|
|
|
|
*adc_val = nr/dr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* set_hyst_val - sets the given 'val' as the 'hysteresis'
|
|
* @alert_reg_h: The 'high' register address
|
|
* @hyst: Hysteresis value (in ADC codes) to be programmed
|
|
*
|
|
* Not protected. Calling function should handle synchronization.
|
|
* Can sleep
|
|
*/
|
|
static int set_hyst_val(int alert_reg_h, int hyst)
|
|
{
|
|
int ret;
|
|
|
|
ret = intel_mid_pmic_readb(alert_reg_h);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Set bits [2:5] to value of hyst */
|
|
ret = (ret & 0xC3) | (hyst << 2);
|
|
|
|
return intel_mid_pmic_writeb(alert_reg_h, ret);
|
|
}
|
|
|
|
/**
|
|
* set_alert_temp - sets the given 'adc_val' to the 'alert_reg'
|
|
* @alert_reg_l: The 'low' register address
|
|
* @adc_val: ADC value to be programmed
|
|
* @level: 0 - alert0, 1 - alert1, 2 - alert2(only 8 bits wide)
|
|
*
|
|
* Not protected. Calling function should handle synchronization.
|
|
* Can sleep
|
|
*/
|
|
static int set_alert_temp(int alert_reg_l, int adc_val, int level)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* Method used for VLV-CRC PMICs:
|
|
* The alert register stores B[1:8] of val and the HW
|
|
* while comparing prefixes and suffixes this value with
|
|
* a 0; i.e B[0] and B[9] are 0.
|
|
*
|
|
* Method used for CHV-CRC+ PMICs:
|
|
* Use B[2..9]; B[0] and B[1] are assumed to be 0 by the HW.
|
|
*/
|
|
if (level == LEVEL_ALERT2) {
|
|
if (tdata->is_vlv)
|
|
adc_val = (adc_val & 0x1FF) >> 1;
|
|
else
|
|
adc_val = (adc_val & 0x3FF) >> 2;
|
|
|
|
return intel_mid_pmic_writeb(alert_reg_l, adc_val);
|
|
}
|
|
|
|
/* Extract bits[0:7] of 'adc_val' and write them into alert_reg_l */
|
|
ret = intel_mid_pmic_writeb(alert_reg_l, adc_val & 0xFF);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Get the address of alert_reg_h */
|
|
--alert_reg_l;
|
|
|
|
ret = intel_mid_pmic_readb(alert_reg_l);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Set bits[0:1] of alert_reg_h to bits[8:9] of 'adc_val' */
|
|
ret = (ret & ~0x03) | (adc_val >> 8);
|
|
|
|
return intel_mid_pmic_writeb(alert_reg_l, ret);
|
|
}
|
|
|
|
/**
|
|
* get_alert_temp - gets the ADC code from the alert register
|
|
* @alert_reg_l: The 'low' register address
|
|
* @level: 0 - alert0, 1 - alert1, 2 - alert2(only 8 bits wide)
|
|
*
|
|
* Not protected. Calling function should handle synchronization.
|
|
* Can sleep
|
|
*/
|
|
static int get_alert_temp(int alert_reg_l, int level)
|
|
{
|
|
int l, h;
|
|
|
|
l = intel_mid_pmic_readb(alert_reg_l);
|
|
if (l < 0)
|
|
return l;
|
|
|
|
if (level == LEVEL_ALERT2) {
|
|
/* For VLV-CRC based platforms */
|
|
if (tdata->is_vlv)
|
|
return l << 1;
|
|
/* For other platforms */
|
|
return l << 2;
|
|
}
|
|
|
|
/* Get the address of alert_reg_h */
|
|
--alert_reg_l;
|
|
|
|
h = intel_mid_pmic_readb(alert_reg_l);
|
|
if (h < 0)
|
|
return h;
|
|
|
|
/* Concatenate 'h' and 'l' to get 10-bit ADC code */
|
|
return ((h & 0x03) << 8) | l;
|
|
}
|
|
|
|
static int disable_prochot(void)
|
|
{
|
|
int i, reg, ret;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
for (i = 0; i < PMIC_THERMAL_SENSORS; i++) {
|
|
reg = alert_regs_l[0][i] - 1;
|
|
ret = intel_mid_pmic_clearb(reg, PROCHOT_EN);
|
|
if (ret < 0)
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* program_tmax - programs a default _max value for each sensor
|
|
* @dev: device pointer
|
|
*
|
|
* Can sleep
|
|
*/
|
|
static int program_tmax(struct device *dev)
|
|
{
|
|
int i, ret, level;
|
|
int pmic_die_val, adc_val, val;
|
|
int pmic_die_crit_val;
|
|
|
|
ret = temp_to_adc(1, PMIC_DIE_MIN_CRIT_TEMP, &pmic_die_crit_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = temp_to_adc(1, DEFAULT_MAX_TEMP, &pmic_die_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* ADC code corresponding to max Temp 85 C */
|
|
ret = temp_to_adc(0, DEFAULT_MAX_TEMP, &adc_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (level = 0; level <= LEVEL_ALERT2; level++) {
|
|
for (i = 0; i < PMIC_THERMAL_SENSORS; i++) {
|
|
val = (i == PMIC_DIE_SENSOR) ? pmic_die_val : adc_val;
|
|
|
|
if (level == LEVEL_ALERT2 && i == PMIC_DIE_SENSOR)
|
|
val = pmic_die_crit_val;
|
|
|
|
ret = set_alert_temp(alert_regs_l[level][i],
|
|
val, level);
|
|
if (ret < 0)
|
|
goto exit_err;
|
|
/* Set default Hysteresis for Alerts 0,1 only */
|
|
if (level == LEVEL_ALERT2)
|
|
continue;
|
|
ret = set_hyst_val(alert_regs_l[level][i] - 1,
|
|
DEFAULT_HYST);
|
|
if (ret < 0)
|
|
goto exit_err;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
|
|
exit_err:
|
|
dev_err(dev, "set alert %d for channel %d failed:%d\n", level, i, ret);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t store_trip_hyst(struct thermal_zone_device *tzd,
|
|
int trip, long hyst)
|
|
{
|
|
int ret;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg = alert_regs_l[trip][td_info->sensor_index] - 1;
|
|
|
|
/*
|
|
* Alert level 2 does not support hysteresis; and (for
|
|
* other levels) the hysteresis value is 4 bits wide.
|
|
*/
|
|
if (trip == LEVEL_ALERT2 || hyst > DEFAULT_HYST)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
ret = set_hyst_val(alert_reg, hyst);
|
|
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t show_trip_hyst(struct thermal_zone_device *tzd,
|
|
int trip, long *hyst)
|
|
{
|
|
int ret;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg_l = alert_regs_l[trip][td_info->sensor_index];
|
|
|
|
if (trip == LEVEL_ALERT2) {
|
|
*hyst = 0;
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
/* Get the address of alert_reg_h */
|
|
--alert_reg_l;
|
|
|
|
ret = intel_mid_pmic_readb(alert_reg_l);
|
|
if (ret >= 0)
|
|
*hyst = (ret >> 2) & 0x0F; /* Extract bits[2:5] of data */
|
|
|
|
mutex_unlock(&thrm_update_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t store_trip_temp(struct thermal_zone_device *tzd,
|
|
int trip, long trip_temp)
|
|
{
|
|
int ret, adc_val, min_tcrit = MIN_CRIT_TEMP;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg_l = alert_regs_l[trip][td_info->sensor_index];
|
|
|
|
if (trip_temp != 0 && trip_temp < 1000) {
|
|
dev_err(&tzd->device, "Temperature should be in mC\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Convert to C */
|
|
trip_temp /= 1000;
|
|
|
|
/* Minimum Tcrit for PMIC DIE is different from that of others */
|
|
if (td_info->sensor->direct)
|
|
min_tcrit = PMIC_DIE_MIN_CRIT_TEMP;
|
|
|
|
if (trip == LEVEL_ALERT2 && trip_temp < min_tcrit) {
|
|
dev_err(&tzd->device,
|
|
"Tcrit should be more than %dC\n", min_tcrit);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
ret = temp_to_adc(td_info->sensor->direct, (int)trip_temp, &adc_val);
|
|
if (ret) {
|
|
adc_val = trip_temp > 0 ?
|
|
adc_code[0][TABLE_LENGTH - 1] : adc_code[0][0];
|
|
|
|
dev_err(&tzd->device,
|
|
"ADC code out of range. Capping it to %s possible\n",
|
|
trip_temp > 0 ? "highest" : "lowest");
|
|
}
|
|
|
|
/*
|
|
* Hold the irq lock, so that no alert threshold for any sensor
|
|
* is being programmed while we are handling an interrupt
|
|
*/
|
|
mutex_lock(&tdata->thrm_irq_lock);
|
|
|
|
ret = set_alert_temp(alert_reg_l, adc_val, trip);
|
|
|
|
mutex_unlock(&tdata->thrm_irq_lock);
|
|
|
|
/* Store the trip point value written into the register */
|
|
ret = adc_to_temp(td_info->sensor->direct, adc_val,
|
|
&td_info->trip_temp[trip]);
|
|
if (ret)
|
|
dev_err(&tzd->device,
|
|
"adc_to_temp for trip%d failed:%d\n", trip, ret);
|
|
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t show_trip_temp(struct thermal_zone_device *tzd,
|
|
int trip, long *trip_temp)
|
|
{
|
|
int ret = -EINVAL, adc_val;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg_l = alert_regs_l[trip][td_info->sensor_index];
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
adc_val = get_alert_temp(alert_reg_l, trip);
|
|
if (adc_val < 0)
|
|
goto exit;
|
|
|
|
ret = adc_to_temp(td_info->sensor->direct, adc_val, trip_temp);
|
|
exit:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t 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 read_result_regs(void)
|
|
{
|
|
int i, ret;
|
|
|
|
for (i = 0; i < PMIC_THERMAL_SENSORS; i++) {
|
|
/*
|
|
* Exploit the fact that the result registers store
|
|
* the value in the same format as that of the alert
|
|
* registers. So, use get_alert_temp but pass the
|
|
* result register address, and level as 0.
|
|
*/
|
|
ret = get_alert_temp(adc_res_reg_l[i], 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
tdata->cached_vals[i] = ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int update_temp(struct thermal_zone_device *tzd, long *temp)
|
|
{
|
|
int ret;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int indx = td_info->sensor_index;
|
|
|
|
if (!tdata->iio_chan)
|
|
return -EINVAL;
|
|
|
|
if (!tdata->is_initialized ||
|
|
time_after(jiffies, tdata->last_updated + HZ)) {
|
|
ret = iio_read_channel_all_raw(tdata->iio_chan,
|
|
tdata->cached_vals);
|
|
if (ret == -ETIMEDOUT) {
|
|
dev_err(&tzd->device,
|
|
"ADC sampling failed:%d Reading rslt regs\n",
|
|
ret);
|
|
|
|
ret = read_result_regs();
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
tdata->last_updated = jiffies;
|
|
tdata->is_initialized = true;
|
|
}
|
|
|
|
ret = adc_to_temp(td_info->sensor->direct,
|
|
tdata->cached_vals[indx], temp);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t show_emul_temp(struct thermal_zone_device *tzd, long *temp)
|
|
{
|
|
int ret = 0;
|
|
char *thermal_event[3];
|
|
unsigned long timeout;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
|
|
thermal_event[0] = kasprintf(GFP_KERNEL, "NAME=%s", tzd->type);
|
|
thermal_event[1] = kasprintf(GFP_KERNEL, "EVENT=%d", EMUL_TEMP_EVENT);
|
|
thermal_event[2] = NULL;
|
|
|
|
INIT_COMPLETION(td_info->temp_write_complete);
|
|
kobject_uevent_env(&tzd->device.kobj, KOBJ_CHANGE, thermal_event);
|
|
|
|
timeout = wait_for_completion_timeout(&td_info->temp_write_complete,
|
|
TEMP_WRITE_TIMEOUT);
|
|
if (timeout == 0) {
|
|
/* Waiting timed out */
|
|
ret = -ETIMEDOUT;
|
|
goto exit;
|
|
}
|
|
|
|
*temp = tzd->emul_temperature;
|
|
exit:
|
|
kfree(thermal_event[1]);
|
|
kfree(thermal_event[0]);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t store_emul_temp(struct thermal_zone_device *tzd,
|
|
unsigned long temp)
|
|
{
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
|
|
tzd->emul_temperature = temp;
|
|
complete(&td_info->temp_write_complete);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t show_temp(struct thermal_zone_device *tzd, long *temp)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
ret = update_temp(tzd, temp);
|
|
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int enable_tm(void)
|
|
{
|
|
int i, reg, ret, level;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
/* Setting these bits enables ADC to poll for these Thermistors */
|
|
ret = intel_mid_pmic_setb(TS_ENABLE, TS_ENABLE_ALL);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
/*
|
|
* Enable Interrupts for Alert level 0 and 1. Alert
|
|
* level 2 is critical and is enabled by default in the HW.
|
|
*/
|
|
for (level = 0; level <= LEVEL_ALERT1; level++) {
|
|
/* Unmask the 2nd level interrupts for Alerts 0,1,2 */
|
|
ret = intel_mid_pmic_writeb(MTHRMIRQ0 + level, 0x00);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
for (i = 0; i < PMIC_THERMAL_SENSORS; i++) {
|
|
reg = alert_regs_l[level][i] - 1;
|
|
ret = intel_mid_pmic_setb(reg, ALERT_EN);
|
|
if (ret < 0)
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
/* Unmask the first level IRQ bit for Thermal alerts */
|
|
ret = intel_mid_pmic_clearb(MIRQLVL1, IRQ_LVL1_EN);
|
|
|
|
exit:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static struct thermal_device_info *initialize_sensor(int index,
|
|
struct intel_mid_thermal_sensor *sensor)
|
|
{
|
|
struct thermal_device_info *td_info =
|
|
kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL);
|
|
|
|
if (!td_info)
|
|
return NULL;
|
|
|
|
td_info->sensor = sensor;
|
|
td_info->sensor_index = index;
|
|
|
|
init_completion(&td_info->temp_write_complete);
|
|
return td_info;
|
|
}
|
|
|
|
static void notify_thermal_event(struct thermal_event te)
|
|
{
|
|
int ret;
|
|
long cur_temp;
|
|
char *thermal_event[5];
|
|
|
|
struct thermal_zone_device *tzd = tdata->tzd[te.sensor];
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
/*
|
|
* We want to get the 'latest' temperature value after an
|
|
* interrupt. So, make sure update_temp() actually samples and
|
|
* returns the 'latest' temperature values; and not
|
|
* the previously cached ones.
|
|
*/
|
|
tdata->is_initialized = false;
|
|
|
|
/*
|
|
* Read the current Temperature and send it to user land;
|
|
* so that the user space can avoid a sysfs read.
|
|
*/
|
|
ret = update_temp(tzd, &cur_temp);
|
|
if (ret) {
|
|
dev_err(&tzd->device, "Cannot update temperature\n");
|
|
goto exit;
|
|
}
|
|
|
|
pr_info("Thermal Event: sensor: %s, cur_temp: %ld, event: %d, level: %d\n",
|
|
tzd->type, cur_temp, te.event, te.level);
|
|
/*
|
|
* Send UEvents only when temperature goes below
|
|
* ALERT0 or goes above ALERT1. No UEvents for
|
|
* ALERT2 as the actions are taken by Hardware.
|
|
*/
|
|
if (te.level == LEVEL_ALERT2 ||
|
|
(te.event == 1 && te.level == LEVEL_ALERT0) ||
|
|
(te.event == 0 && te.level == LEVEL_ALERT1)) {
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* For Cold-to-Hot events, make sure the temperature is
|
|
* 'at least' as high as the programmed threshold.
|
|
*/
|
|
if (te.event == 1) {
|
|
if (cur_temp < td_info->trip_temp[te.level])
|
|
cur_temp = td_info->trip_temp[te.level];
|
|
}
|
|
|
|
thermal_event[0] = kasprintf(GFP_KERNEL, "NAME=%s", tzd->type);
|
|
thermal_event[1] = kasprintf(GFP_KERNEL, "TEMP=%ld", cur_temp);
|
|
thermal_event[2] = kasprintf(GFP_KERNEL, "EVENT=%d", te.event);
|
|
thermal_event[3] = kasprintf(GFP_KERNEL, "LEVEL=%d", te.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]);
|
|
|
|
exit:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return;
|
|
}
|
|
|
|
static int update_intrpt_params(int irq, int level)
|
|
{
|
|
int i, sts;
|
|
struct thermal_event te;
|
|
|
|
/* Read the appropriate status register */
|
|
sts = intel_mid_pmic_readb(params[0][level]);
|
|
if (sts < 0)
|
|
return sts;
|
|
|
|
for (i = 0; i < PMIC_THERMAL_SENSORS; i++) {
|
|
if (irq & params[1][i]) {
|
|
te.level = level;
|
|
te.sensor = i;
|
|
te.event = !!(sts & params[2][i]);
|
|
kfifo_put(&irq_fifo, &te);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void thermal_work_func(struct work_struct *work)
|
|
{
|
|
int gotten;
|
|
struct thermal_event te;
|
|
|
|
while (!kfifo_is_empty(&irq_fifo)) {
|
|
gotten = kfifo_get(&irq_fifo, &te);
|
|
if (!gotten) {
|
|
pr_err("kfifo empty\n");
|
|
return;
|
|
}
|
|
|
|
/* Notify the user space through UEvent */
|
|
notify_thermal_event(te);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t thermal_intrpt(int irq_nr, void *id)
|
|
{
|
|
int ret, irq, irq_cache, reg, level;
|
|
struct thermal_data *tdata = (struct thermal_data *)id;
|
|
|
|
if (!tdata)
|
|
return IRQ_HANDLED;
|
|
|
|
mutex_lock(&tdata->thrm_irq_lock);
|
|
|
|
/*
|
|
* Assertion of THRMIRQ1[0:3] indicates Alert2 ('critical')
|
|
* event. Register Layout:
|
|
*
|
|
* THRMIRQ0: PMIC_DIE SYS2 SYS1 SYS0 PMIC_DIE SYS2 SYS1 SYS0
|
|
* Alert0-1: Alert1 A1 A1 A1 Alert0 A0 A0 A0
|
|
*
|
|
* THRMIRQ1: RSVD RSVD RSVD RSVD PMIC_DIE SYS2 SYS1 SYS0
|
|
* Alert 2: Alert2 A2 A2 A2
|
|
*/
|
|
for (level = 0; level <= LEVEL_ALERT2; level++) {
|
|
reg = THRMIRQ0 + (level == LEVEL_ALERT2);
|
|
irq = intel_mid_pmic_readb(reg);
|
|
if (irq < 0)
|
|
goto exit;
|
|
|
|
irq_cache = irq;
|
|
|
|
irq = irq >> (PMIC_THERMAL_SENSORS * (level == LEVEL_ALERT1));
|
|
if (irq & 0x0F)
|
|
goto handle_event;
|
|
}
|
|
|
|
/*
|
|
* If we are here, then this event is none of Alert0/1/2
|
|
* events but somehow we got the interrupt. Just exit.
|
|
* Very unlikely case though.
|
|
*/
|
|
goto exit;
|
|
|
|
handle_event:
|
|
/*
|
|
* From the interrupt and status register, find out
|
|
* which sensor caused the event, and for what transition
|
|
* [either 'Hot to Cold' or 'Cold to Hot']
|
|
*/
|
|
ret = update_intrpt_params(irq, level);
|
|
if (ret < 0)
|
|
goto exit_err;
|
|
|
|
ret = intel_mid_pmic_writeb(reg, irq_cache);
|
|
if (ret < 0)
|
|
goto exit_err;
|
|
|
|
schedule_work(&tdata->thermal_work);
|
|
|
|
mutex_unlock(&tdata->thrm_irq_lock);
|
|
return IRQ_HANDLED;
|
|
|
|
exit_err:
|
|
pr_err("I2C read/write failed:%d\n", ret);
|
|
exit:
|
|
mutex_unlock(&tdata->thrm_irq_lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct thermal_zone_device_ops tzd_emul_ops = {
|
|
.get_temp = show_emul_temp,
|
|
.set_emul_temp = store_emul_temp,
|
|
};
|
|
|
|
static struct thermal_zone_device_ops tzd_ops = {
|
|
.get_temp = show_temp,
|
|
.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,
|
|
};
|
|
|
|
static int byt_thermal_probe(struct platform_device *pdev)
|
|
{
|
|
int i, size, ret;
|
|
int total_sensors; /* real + virtual sensors */
|
|
struct intel_mid_thermal_platform_data *pdata;
|
|
struct cpuinfo_x86 *c = &cpu_data(0);
|
|
|
|
pdata = pdev->dev.platform_data;
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "Unable to fetch platform data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tdata = kzalloc(sizeof(struct thermal_data), GFP_KERNEL);
|
|
if (!tdata) {
|
|
dev_err(&pdev->dev, "kzalloc failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tdata->pdev = pdev;
|
|
tdata->num_sensors = pdata->num_sensors;
|
|
tdata->num_virtual_sensors = pdata->num_virtual_sensors;
|
|
tdata->sensors = pdata->sensors;
|
|
tdata->irq = platform_get_irq(pdev, 0);
|
|
platform_set_drvdata(pdev, tdata);
|
|
mutex_init(&tdata->thrm_irq_lock);
|
|
|
|
/*
|
|
* Identify whether this is VLV(0x37) or CHV(0x4c) board.
|
|
* TODO: Use PMIC registers on I2C space to differentiate this.
|
|
*/
|
|
if (c->x86_model == 0x37)
|
|
tdata->is_vlv = true;
|
|
else
|
|
tdata->is_vlv = false;
|
|
|
|
total_sensors = tdata->num_sensors;
|
|
#ifdef CONFIG_THERMAL_EMULATION
|
|
total_sensors += tdata->num_virtual_sensors;
|
|
#endif
|
|
size = sizeof(struct thermal_zone_device *) * total_sensors;
|
|
tdata->tzd = kzalloc(size, GFP_KERNEL);
|
|
if (!tdata->tzd) {
|
|
dev_err(&pdev->dev, "kzalloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto exit_free;
|
|
}
|
|
|
|
/* Disable prochot on alert0 crossing */
|
|
ret = disable_prochot();
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Disabling prochot failed:%d\n", ret);
|
|
goto exit_tzd;
|
|
}
|
|
|
|
/* Program a default _max value for each sensor */
|
|
ret = program_tmax(&pdev->dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Programming _max failed:%d\n", ret);
|
|
goto exit_tzd;
|
|
}
|
|
|
|
/* Register with IIO to sample temperature values */
|
|
tdata->iio_chan = iio_channel_get_all(&pdev->dev);
|
|
if (tdata->iio_chan == NULL) {
|
|
dev_err(&pdev->dev, "tdata->iio_chan is null\n");
|
|
ret = -EINVAL;
|
|
goto exit_tzd;
|
|
}
|
|
|
|
/* Check whether we got all the four channels */
|
|
ret = iio_channel_get_num(tdata->iio_chan);
|
|
if (ret != PMIC_THERMAL_SENSORS) {
|
|
dev_err(&pdev->dev, "incorrect number of channels:%d\n", ret);
|
|
ret = -EFAULT;
|
|
goto exit_iio;
|
|
}
|
|
|
|
/* Register each sensor with the generic thermal framework */
|
|
for (i = 0; i < total_sensors; i++) {
|
|
if (i < tdata->num_sensors) {
|
|
tdata->tzd[i] = thermal_zone_device_register(
|
|
tdata->sensors[i].name,
|
|
NUM_ALERT_LEVELS, ALERT_RW_MASK,
|
|
initialize_sensor(i, &tdata->sensors[i]),
|
|
&tzd_ops,
|
|
NULL, 0, 0);
|
|
} else {
|
|
tdata->tzd[i] = thermal_zone_device_register(
|
|
tdata->sensors[i].name,
|
|
0, 0,
|
|
initialize_sensor(i, &tdata->sensors[i]),
|
|
&tzd_emul_ops,
|
|
NULL, 0, 0);
|
|
}
|
|
if (IS_ERR(tdata->tzd[i])) {
|
|
ret = PTR_ERR(tdata->tzd[i]);
|
|
dev_err(&pdev->dev,
|
|
"registering thermal sensor %s failed: %d\n",
|
|
tdata->sensors[i].name, ret);
|
|
goto exit_reg;
|
|
}
|
|
}
|
|
|
|
INIT_WORK(&tdata->thermal_work, thermal_work_func);
|
|
|
|
/* Register for Interrupt Handler */
|
|
ret = request_threaded_irq(tdata->irq, NULL, thermal_intrpt,
|
|
IRQF_TRIGGER_RISING,
|
|
DEVICE_NAME, tdata);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "request_threaded_irq failed:%d\n", ret);
|
|
goto exit_reg;
|
|
}
|
|
|
|
/* Enable Thermal Monitoring */
|
|
ret = enable_tm();
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Enabling TM failed:%d\n", ret);
|
|
goto exit_irq;
|
|
}
|
|
|
|
create_ccove_thermal_debugfs();
|
|
|
|
return 0;
|
|
|
|
exit_irq:
|
|
free_irq(tdata->irq, tdata);
|
|
exit_reg:
|
|
while (--i >= 0)
|
|
thermal_zone_device_unregister(tdata->tzd[i]);
|
|
exit_iio:
|
|
iio_channel_release_all(tdata->iio_chan);
|
|
exit_tzd:
|
|
kfree(tdata->tzd);
|
|
exit_free:
|
|
mutex_destroy(&tdata->thrm_irq_lock);
|
|
kfree(tdata);
|
|
return ret;
|
|
}
|
|
|
|
static int byt_thermal_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int byt_thermal_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int byt_thermal_remove(struct platform_device *pdev)
|
|
{
|
|
int i, total_sensors;
|
|
struct thermal_data *tdata = platform_get_drvdata(pdev);
|
|
|
|
if (!tdata)
|
|
return 0;
|
|
|
|
total_sensors = tdata->num_sensors;
|
|
|
|
#ifdef CONFIG_THERMAL_EMULATION
|
|
total_sensors += tdata->num_virtual_sensors;
|
|
#endif
|
|
|
|
for (i = 0; i < total_sensors; i++)
|
|
thermal_zone_device_unregister(tdata->tzd[i]);
|
|
|
|
iio_channel_release_all(tdata->iio_chan);
|
|
free_irq(tdata->irq, tdata);
|
|
mutex_destroy(&tdata->thrm_irq_lock);
|
|
kfree(tdata->tzd);
|
|
kfree(tdata);
|
|
|
|
remove_ccove_thermal_debugfs();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Driver initialization and finalization
|
|
*********************************************************************/
|
|
|
|
static const struct dev_pm_ops thermal_pm_ops = {
|
|
.suspend = byt_thermal_suspend,
|
|
.resume = byt_thermal_resume,
|
|
};
|
|
|
|
static struct platform_driver byt_thermal_driver = {
|
|
.driver = {
|
|
.name = DEVICE_NAME,
|
|
.owner = THIS_MODULE,
|
|
.pm = &thermal_pm_ops,
|
|
},
|
|
.probe = byt_thermal_probe,
|
|
.remove = byt_thermal_remove,
|
|
};
|
|
|
|
static int byt_thermal_module_init(void)
|
|
{
|
|
return platform_driver_register(&byt_thermal_driver);
|
|
}
|
|
|
|
static void byt_thermal_module_exit(void)
|
|
{
|
|
platform_driver_unregister(&byt_thermal_driver);
|
|
}
|
|
|
|
late_initcall(byt_thermal_module_init);
|
|
module_exit(byt_thermal_module_exit);
|
|
|
|
MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>");
|
|
MODULE_DESCRIPTION("Intel Baytrail Platform Thermal Driver");
|
|
MODULE_LICENSE("GPL");
|