930 lines
22 KiB
C
930 lines
22 KiB
C
/*
|
|
* intel_mrfl_thermal.c - Intel Merrifield Platform Thermal Driver
|
|
*
|
|
*
|
|
* Copyright (C) 2011 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: Durgadoss R <durgadoss.r@intel.com>
|
|
*
|
|
* DEVICE_NAME: Intel Merrifield platform - PMIC: Thermal Monitor
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "intel_mrfl_thermal: " fmt
|
|
|
|
#include <linux/pm.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/rpmsg.h>
|
|
#include <linux/module.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <asm/intel_scu_pmic.h>
|
|
#include <asm/intel_mid_rpmsg.h>
|
|
#include <asm/intel_mid_thermal.h>
|
|
#include <linux/iio/consumer.h>
|
|
|
|
#define DRIVER_NAME "bcove_thrm"
|
|
|
|
/* Number of Thermal sensors on the PMIC */
|
|
#define PMIC_THERMAL_SENSORS 4
|
|
|
|
/* Registers that govern Thermal Monitoring */
|
|
#define THRMMONCFG 0xB3
|
|
#define THRMMONCTL 0xB4
|
|
#define THRMIRQ 0x04
|
|
#define MTHRMIRQ 0x0F
|
|
#define STHRMIRQ 0xB2
|
|
#define IRQLVL1 0x01
|
|
#define MIRQLVL1 0x0C
|
|
#define IRQ_MASK_ALL 0x0F
|
|
|
|
/* PMIC SRAM base address and offset for Thermal register */
|
|
#define PMIC_SRAM_BASE_ADDR 0xFFFFF610
|
|
#define PMIC_SRAM_THRM_OFFSET 0x03
|
|
#define IOMAP_SIZE 0x04
|
|
|
|
/* NVM BANK REGISTER */
|
|
#define EEPROM_CTRL 0x1FE
|
|
#define EEPROM_REG15 0x1EE
|
|
#define EEPROM_BANK1_SELECT 0x02
|
|
#define EEPROM_BANK1_UNSELECT 0x00
|
|
|
|
#define PMICALRT (1 << 3)
|
|
#define SYS2ALRT (1 << 2)
|
|
#define SYS1ALRT (1 << 1)
|
|
#define SYS0ALRT (1 << 0)
|
|
#define THERM_EN (1 << 0)
|
|
#define THERM_ALRT (1 << 2)
|
|
|
|
/* ADC to Temperature conversion table length */
|
|
#define TABLE_LENGTH 34
|
|
#define TEMP_INTERVAL 5
|
|
|
|
/*
|
|
* 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)
|
|
|
|
/* Default _max 85 C */
|
|
#define DEFAULT_MAX_TEMP 85
|
|
|
|
/* Constants defined in BasinCove PMIC spec */
|
|
#define PMIC_DIE_ADC_MIN 395
|
|
#define PMIC_DIE_ADC_MAX 661
|
|
#define PMIC_DIE_TEMP_MIN -40
|
|
#define PMIC_DIE_TEMP_MAX 125
|
|
#define ADC_VAL_27C 470
|
|
#define ADC_COEFFICIENT 675
|
|
#define TEMP_OFFSET 27000
|
|
|
|
/* 'enum' of Thermal sensors */
|
|
enum thermal_sensors { SYS0, SYS1, SYS2, PMIC_DIE, _COUNT };
|
|
|
|
/*
|
|
* 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:6] of alert_regs_h.
|
|
* Order: SYS0 SYS1 SYS2 PMIC_DIE
|
|
*
|
|
* static const int alert_regs_l[] = { 0xB7, 0xB9, 0xBB, 0xC1 };
|
|
*/
|
|
static const int alert_regs_h[] = { 0xB6, 0xB8, 0xBA, 0xC0 };
|
|
|
|
/*
|
|
* 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] = {
|
|
{952, 932, 906, 877, 843, 804, 761, 714, 665, 614,
|
|
563, 512, 462, 415, 370, 329, 291, 257, 226, 199,
|
|
174, 153, 135, 119, 104, 92, 81, 72, 64, 56,
|
|
50, 45, 40, 36},
|
|
{-40, -35, -30, -25, -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},
|
|
};
|
|
|
|
static DEFINE_MUTEX(thrm_update_lock);
|
|
|
|
struct thermal_device_info {
|
|
struct intel_mid_thermal_sensor *sensor;
|
|
};
|
|
|
|
struct thermal_data {
|
|
struct platform_device *pdev;
|
|
struct iio_channel *iio_chan;
|
|
struct thermal_zone_device **tzd;
|
|
struct completion temp_write_complete;
|
|
void *thrm_addr;
|
|
unsigned int irq;
|
|
/* Caching information */
|
|
bool is_initialized;
|
|
unsigned long last_updated;
|
|
int cached_vals[PMIC_THERMAL_SENSORS];
|
|
int num_sensors;
|
|
int num_virtual_sensors;
|
|
struct intel_mid_thermal_sensor *sensors;
|
|
};
|
|
static struct thermal_data *tdata;
|
|
|
|
static inline int adc_to_pmic_die_temp(unsigned int val)
|
|
{
|
|
/* return temperature in mC */
|
|
return (val - ADC_VAL_27C) * ADC_COEFFICIENT + TEMP_OFFSET;
|
|
}
|
|
|
|
static inline int pmic_die_temp_to_adc(int temp)
|
|
{
|
|
/* 'temp' is in C, convert to mC and then do calculations */
|
|
return ((temp * 1000) - TEMP_OFFSET) / ADC_COEFFICIENT + ADC_VAL_27C;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
int8_t pmic_temp_offset;
|
|
|
|
/* Direct conversion for pmic die temperature */
|
|
if (direct) {
|
|
if (adc_val < PMIC_DIE_ADC_MIN || adc_val > PMIC_DIE_ADC_MAX)
|
|
return -EINVAL;
|
|
|
|
/* An offset added for pmic temp from NVM in TNG B0 */
|
|
intel_scu_ipc_iowrite8(EEPROM_CTRL, EEPROM_BANK1_SELECT);
|
|
intel_scu_ipc_ioread8(EEPROM_REG15, &pmic_temp_offset);
|
|
intel_scu_ipc_iowrite8(EEPROM_CTRL, EEPROM_BANK1_UNSELECT);
|
|
|
|
adc_val = adc_val + pmic_temp_offset;
|
|
|
|
*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_tmax - sets the given 'adc_val' to the 'alert_reg'
|
|
* @alert_reg: register address
|
|
* @adc_val: ADC value to be programmed
|
|
*
|
|
* Not protected. Calling function should handle synchronization.
|
|
* Can sleep
|
|
*/
|
|
static int set_tmax(int alert_reg, int adc_val)
|
|
{
|
|
int ret;
|
|
|
|
/* Set bits[0:1] of alert_reg_h to bits[8:9] of 'adc_val' */
|
|
ret = intel_scu_ipc_update_register(alert_reg, (adc_val >> 8), 0x03);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Extract bits[0:7] of 'adc_val' and write them into alert_reg_l */
|
|
return intel_scu_ipc_iowrite8(alert_reg + 1, adc_val & 0xFF);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
int pmic_die_val, adc_val;
|
|
|
|
ret = temp_to_adc(0, DEFAULT_MAX_TEMP, &adc_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = temp_to_adc(1, DEFAULT_MAX_TEMP, &pmic_die_val);
|
|
if (ret)
|
|
return ret;
|
|
/*
|
|
* Since this function sets max value, do for all sensors even if
|
|
* the sensor does not register as a thermal zone.
|
|
*/
|
|
for (i = 0; i < PMIC_THERMAL_SENSORS - 1; i++) {
|
|
ret = set_tmax(alert_regs_h[i], adc_val);
|
|
if (ret)
|
|
goto exit_err;
|
|
}
|
|
|
|
/* Set _max for pmic die sensor */
|
|
ret = set_tmax(alert_regs_h[i], pmic_die_val);
|
|
if (ret)
|
|
goto exit_err;
|
|
|
|
return ret;
|
|
|
|
exit_err:
|
|
dev_err(dev, "set_tmax for channel %d failed:%d\n", i, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int store_trip_hyst(struct thermal_zone_device *tzd,
|
|
int trip, long hyst)
|
|
{
|
|
int ret;
|
|
uint8_t data;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg = alert_regs_h[td_info->sensor->index];
|
|
|
|
/* Hysteresis value is 5 bits wide */
|
|
if (hyst > 31)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
ret = intel_scu_ipc_ioread8(alert_reg, &data);
|
|
if (ret)
|
|
goto ipc_fail;
|
|
|
|
/* Set bits [2:6] to value of hyst */
|
|
data = (data & 0x83) | (hyst << 2);
|
|
|
|
ret = intel_scu_ipc_iowrite8(alert_reg, data);
|
|
|
|
ipc_fail:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int show_trip_hyst(struct thermal_zone_device *tzd,
|
|
int trip, long *hyst)
|
|
{
|
|
int ret;
|
|
uint8_t data;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg = alert_regs_h[td_info->sensor->index];
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
ret = intel_scu_ipc_ioread8(alert_reg, &data);
|
|
if (!ret)
|
|
*hyst = (data >> 2) & 0x1F; /* Extract bits[2:6] of data */
|
|
|
|
mutex_unlock(&thrm_update_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int store_trip_temp(struct thermal_zone_device *tzd,
|
|
int trip, long trip_temp)
|
|
{
|
|
int ret, adc_val;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg = alert_regs_h[td_info->sensor->index];
|
|
|
|
if (trip_temp != 0 && trip_temp < 1000) {
|
|
dev_err(&tzd->device, "Temperature should be in mC\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
/* Convert from mC to C */
|
|
trip_temp /= 1000;
|
|
|
|
ret = temp_to_adc(td_info->sensor->direct, (int)trip_temp, &adc_val);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = set_tmax(alert_reg, adc_val);
|
|
exit:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int show_trip_temp(struct thermal_zone_device *tzd,
|
|
int trip, long *trip_temp)
|
|
{
|
|
int ret, adc_val;
|
|
uint8_t l, h;
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
int alert_reg = alert_regs_h[td_info->sensor->index];
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
ret = intel_scu_ipc_ioread8(alert_reg, &h);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = intel_scu_ipc_ioread8(alert_reg + 1, &l);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
/* Concatenate 'h' and 'l' to get 10-bit ADC code */
|
|
adc_val = ((h & 0x03) << 8) | l;
|
|
|
|
ret = adc_to_temp(td_info->sensor->direct, adc_val, trip_temp);
|
|
exit:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
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_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;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
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) {
|
|
dev_err(&tzd->device, "ADC sampling failed:%d\n", ret);
|
|
goto exit;
|
|
}
|
|
tdata->last_updated = jiffies;
|
|
tdata->is_initialized = true;
|
|
}
|
|
|
|
ret = adc_to_temp(td_info->sensor->direct, tdata->cached_vals[indx],
|
|
temp);
|
|
exit:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int show_emul_temp(struct thermal_zone_device *tzd, long *temp)
|
|
{
|
|
int ret = 0;
|
|
char *thermal_event[3];
|
|
unsigned long timeout;
|
|
|
|
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(tdata->temp_write_complete);
|
|
kobject_uevent_env(&tzd->device.kobj, KOBJ_CHANGE, thermal_event);
|
|
|
|
timeout = wait_for_completion_timeout(&tdata->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 int store_emul_temp(struct thermal_zone_device *tzd,
|
|
unsigned long temp)
|
|
{
|
|
tzd->emul_temperature = temp;
|
|
complete(&tdata->temp_write_complete);
|
|
return 0;
|
|
}
|
|
|
|
static int enable_tm(void)
|
|
{
|
|
int ret;
|
|
uint8_t data;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
ret = intel_scu_ipc_ioread8(THRMMONCTL, &data);
|
|
if (ret)
|
|
goto ipc_fail;
|
|
|
|
ret = intel_scu_ipc_iowrite8(THRMMONCTL, data | THERM_EN);
|
|
|
|
ipc_fail:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static struct thermal_device_info *initialize_sensor(
|
|
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;
|
|
|
|
init_completion(&tdata->temp_write_complete);
|
|
return td_info;
|
|
}
|
|
|
|
static irqreturn_t thermal_intrpt(int irq, void *dev_data)
|
|
{
|
|
int ret, sensor, event_type;
|
|
uint8_t irq_status;
|
|
unsigned int irq_data;
|
|
struct thermal_data *tdata = (struct thermal_data *)dev_data;
|
|
|
|
if (!tdata)
|
|
return IRQ_NONE;
|
|
|
|
mutex_lock(&thrm_update_lock);
|
|
|
|
irq_data = ioread8(tdata->thrm_addr + PMIC_SRAM_THRM_OFFSET);
|
|
|
|
ret = intel_scu_ipc_ioread8(STHRMIRQ, &irq_status);
|
|
if (ret)
|
|
goto ipc_fail;
|
|
|
|
dev_dbg(&tdata->pdev->dev, "STHRMIRQ: %.2x\n", irq_status);
|
|
|
|
/*
|
|
* -1 for invalid interrupt
|
|
* 1 for LOW to HIGH temperature alert
|
|
* 0 for HIGH to LOW temperature alert
|
|
*/
|
|
event_type = -1;
|
|
|
|
/* Check which interrupt occured and for what event */
|
|
if (irq_data & PMICALRT) {
|
|
event_type = !!(irq_status & PMICALRT);
|
|
sensor = PMIC_DIE;
|
|
} else if (irq_data & SYS2ALRT) {
|
|
event_type = !!(irq_status & SYS2ALRT);
|
|
sensor = SYS2;
|
|
} else if (irq_data & SYS1ALRT) {
|
|
event_type = !!(irq_status & SYS1ALRT);
|
|
sensor = SYS1;
|
|
} else if (irq_data & SYS0ALRT) {
|
|
event_type = !!(irq_status & SYS0ALRT);
|
|
sensor = SYS0;
|
|
} else {
|
|
dev_err(&tdata->pdev->dev, "Invalid Interrupt\n");
|
|
ret = IRQ_HANDLED;
|
|
goto ipc_fail;
|
|
}
|
|
|
|
if (event_type != -1) {
|
|
dev_info(&tdata->pdev->dev,
|
|
"%s interrupt for thermal sensor %d\n",
|
|
event_type ? "HIGH" : "LOW", sensor);
|
|
}
|
|
|
|
/* Notify using UEvent */
|
|
kobject_uevent(&tdata->pdev->dev.kobj, KOBJ_CHANGE);
|
|
|
|
/* Unmask Thermal Interrupt in the mask register */
|
|
ret = intel_scu_ipc_update_register(MIRQLVL1, 0xFF, THERM_ALRT);
|
|
if (ret)
|
|
goto ipc_fail;
|
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
ipc_fail:
|
|
mutex_unlock(&thrm_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
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 irqreturn_t mrfl_thermal_intrpt_handler(int irq, void* dev_data)
|
|
{
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
static int mrfl_thermal_probe(struct platform_device *pdev)
|
|
{
|
|
int i, size, ret;
|
|
int total_sensors; /* real + virtual sensors */
|
|
struct intel_mid_thermal_platform_data *pdata;
|
|
|
|
pdata = pdev->dev.platform_data;
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "platform data not found\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);
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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
|
|
*
|
|
* Order of the channels obtained from adc:
|
|
* "SYSTHERM0", "SYSTHERM1", "SYSTHERM2", "PMICDIE"
|
|
*/
|
|
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, 1, 1,
|
|
initialize_sensor(&tdata->sensors[i]),
|
|
&tzd_ops, NULL, 0, 0);
|
|
} else {
|
|
tdata->tzd[i] = thermal_zone_device_register(
|
|
tdata->sensors[i].name, 0, 0,
|
|
initialize_sensor(&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;
|
|
}
|
|
}
|
|
|
|
tdata->thrm_addr = ioremap_nocache(PMIC_SRAM_BASE_ADDR, IOMAP_SIZE);
|
|
if (!tdata->thrm_addr) {
|
|
ret = -ENOMEM;
|
|
dev_err(&pdev->dev, "ioremap_nocache failed\n");
|
|
goto exit_reg;
|
|
}
|
|
|
|
/* Register for Interrupt Handler */
|
|
ret = request_threaded_irq(tdata->irq, mrfl_thermal_intrpt_handler, thermal_intrpt,
|
|
IRQF_TRIGGER_RISING,
|
|
DRIVER_NAME, tdata);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "request_threaded_irq failed:%d\n", ret);
|
|
goto exit_ioremap;
|
|
}
|
|
|
|
/* Enable Thermal Monitoring */
|
|
ret = enable_tm();
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Enabling TM failed:%d\n", ret);
|
|
goto exit_irq;
|
|
}
|
|
|
|
return 0;
|
|
|
|
exit_irq:
|
|
free_irq(tdata->irq, tdata);
|
|
exit_ioremap:
|
|
iounmap(tdata->thrm_addr);
|
|
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:
|
|
kfree(tdata);
|
|
return ret;
|
|
}
|
|
|
|
static int mrfl_thermal_resume(struct device *dev)
|
|
{
|
|
dev_info(dev, "resume called.\n");
|
|
return 0;
|
|
}
|
|
|
|
static int mrfl_thermal_suspend(struct device *dev)
|
|
{
|
|
dev_info(dev, "suspend called.\n");
|
|
return 0;
|
|
}
|
|
|
|
static int mrfl_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]);
|
|
|
|
free_irq(tdata->irq, tdata);
|
|
iounmap(tdata->thrm_addr);
|
|
iio_channel_release_all(tdata->iio_chan);
|
|
kfree(tdata->tzd);
|
|
kfree(tdata);
|
|
return 0;
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Driver initialization and finalization
|
|
*********************************************************************/
|
|
|
|
static const struct dev_pm_ops thermal_pm_ops = {
|
|
.suspend = mrfl_thermal_suspend,
|
|
.resume = mrfl_thermal_resume,
|
|
};
|
|
|
|
static struct platform_driver mrfl_thermal_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
.pm = &thermal_pm_ops,
|
|
},
|
|
.probe = mrfl_thermal_probe,
|
|
.remove = mrfl_thermal_remove,
|
|
};
|
|
|
|
static int mrfl_thermal_module_init(void)
|
|
{
|
|
return platform_driver_register(&mrfl_thermal_driver);
|
|
}
|
|
|
|
static void mrfl_thermal_module_exit(void)
|
|
{
|
|
platform_driver_unregister(&mrfl_thermal_driver);
|
|
}
|
|
|
|
/* RPMSG related functionality */
|
|
static int mrfl_thermal_rpmsg_probe(struct rpmsg_channel *rpdev)
|
|
{
|
|
if (!rpdev) {
|
|
pr_err("rpmsg channel not created\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev_info(&rpdev->dev, "Probed mrfl_thermal rpmsg device\n");
|
|
|
|
return mrfl_thermal_module_init();
|
|
}
|
|
|
|
static void mrfl_thermal_rpmsg_remove(struct rpmsg_channel *rpdev)
|
|
{
|
|
mrfl_thermal_module_exit();
|
|
dev_info(&rpdev->dev, "Removed mrfl_thermal rpmsg device\n");
|
|
}
|
|
|
|
static void mrfl_thermal_rpmsg_cb(struct rpmsg_channel *rpdev, void *data,
|
|
int len, void *priv, u32 src)
|
|
{
|
|
dev_warn(&rpdev->dev, "unexpected, message\n");
|
|
|
|
print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1,
|
|
data, len, true);
|
|
}
|
|
|
|
static struct rpmsg_device_id mrfl_thermal_id_table[] = {
|
|
{ .name = "rpmsg_mrfl_thermal" },
|
|
{ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(rpmsg, mrfl_thermal_id_table);
|
|
|
|
static struct rpmsg_driver mrfl_thermal_rpmsg = {
|
|
.drv.name = DRIVER_NAME,
|
|
.drv.owner = THIS_MODULE,
|
|
.probe = mrfl_thermal_rpmsg_probe,
|
|
.callback = mrfl_thermal_rpmsg_cb,
|
|
.remove = mrfl_thermal_rpmsg_remove,
|
|
.id_table = mrfl_thermal_id_table,
|
|
};
|
|
|
|
static int __init mrfl_thermal_rpmsg_init(void)
|
|
{
|
|
return register_rpmsg_driver(&mrfl_thermal_rpmsg);
|
|
}
|
|
|
|
static void __exit mrfl_thermal_rpmsg_exit(void)
|
|
{
|
|
return unregister_rpmsg_driver(&mrfl_thermal_rpmsg);
|
|
}
|
|
|
|
module_init(mrfl_thermal_rpmsg_init);
|
|
module_exit(mrfl_thermal_rpmsg_exit);
|
|
|
|
MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>");
|
|
MODULE_DESCRIPTION("Intel Merrifield Platform Thermal Driver");
|
|
MODULE_LICENSE("GPL");
|