483 lines
12 KiB
C
483 lines
12 KiB
C
/*
|
|
* intel_byt_cr_thermal.c - Intel Baytrail Platform Thermal Driver
|
|
*
|
|
* Copyright (C) 2014 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: Shravan B M <shravan.k.b.m@intel.com>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "intel_byt_cr_thermal: " fmt
|
|
|
|
#include <linux/acpi.h>
|
|
#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/platform_device.h>
|
|
|
|
#include <linux/mfd/intel_mid_pmic.h>
|
|
|
|
#include <asm/intel_mid_thermal.h>
|
|
|
|
#include <linux/iio/consumer.h>
|
|
|
|
#define DRIVER_NAME "byt_cr_thermal"
|
|
|
|
/* Number of Thermal sensors on the PMIC */
|
|
#define PMIC_THERMAL_SENSORS 2
|
|
|
|
/* ADC to Temperature conversion table length */
|
|
#define TABLE_LENGTH_XPWR 19
|
|
#define TABLE_LENGTH_TI 73
|
|
|
|
#define DC_TI_PMIC_OFFSET 0x51
|
|
|
|
static int adc_code_xpwr[2][TABLE_LENGTH_XPWR] = {
|
|
{682, 536, 425, 338, 272, 220, 179, 146, 120, 100, 83, 69, 58, 49, 41,
|
|
35, 30, 25, 22},
|
|
{-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50,
|
|
55, 60, 65, 70},
|
|
};
|
|
|
|
static int adc_code_ti[2][TABLE_LENGTH_TI] = {
|
|
{914, 910, 905, 900, 895, 889, 884, 878, 873, 867, 861, 854, 848, 842, 835,
|
|
828, 821, 814, 807, 800, 793, 785, 778, 770, 762, 754, 746, 738, 729, 721,
|
|
713, 704, 696, 687, 678, 669, 661, 652, 643, 634, 625, 616, 607, 598, 589,
|
|
580, 571, 562, 553, 544, 535, 526, 518, 509, 500, 491, 483, 474, 466, 457,
|
|
449, 441, 433, 425, 417, 409, 401, 393, 385, 378, 370, 335, 301},
|
|
{-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4,
|
|
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
|
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
|
|
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
|
|
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 65, 70},
|
|
};
|
|
|
|
static int table_length;
|
|
|
|
static int *adc_code[2];
|
|
|
|
static int (*adc_to_pmic_die_temp)(unsigned int);
|
|
|
|
static bool valid_entry;
|
|
|
|
static DEFINE_MUTEX(thrm_update_lock);
|
|
|
|
struct thermal_device_info {
|
|
struct intel_mid_thermal_sensor *sensor;
|
|
int sensor_index;
|
|
};
|
|
|
|
struct thermal_data {
|
|
struct platform_device *pdev;
|
|
struct iio_channel *iio_chan;
|
|
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;
|
|
struct intel_mid_thermal_sensor *sensors;
|
|
};
|
|
static struct thermal_data *tdata;
|
|
|
|
static int adc_to_pmic_die_temp_xpwr(unsigned int val)
|
|
{
|
|
/* return temperature in mC */
|
|
return -267700 + val * 100;
|
|
}
|
|
|
|
static int adc_to_pmic_die_temp_ti(unsigned int val)
|
|
{
|
|
s8 reg_val = (s8)intel_mid_pmic_readb(DC_TI_PMIC_OFFSET);
|
|
/* return temperature in mC */
|
|
return (val - reg_val - 470) * 675 + 27000;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
*tp = adc_to_pmic_die_temp(adc_val);
|
|
return 0;
|
|
}
|
|
|
|
/* If value out of range return appropriate value */
|
|
if (adc_code[0][0] < adc_val || adc_code[0][table_length - 1] > adc_val) {
|
|
*tp = adc_code[1][0] * 1000;
|
|
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;
|
|
}
|
|
|
|
static int update_temp(struct thermal_zone_device *tzd, long *temp)
|
|
{
|
|
int ret = 0;
|
|
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);
|
|
}
|
|
tdata->last_updated = jiffies;
|
|
tdata->is_initialized = true;
|
|
}
|
|
if (tdata->cached_vals[indx] == 0) {
|
|
*temp = 0;
|
|
return ret;
|
|
}
|
|
ret = adc_to_temp(td_info->sensor->direct,
|
|
tdata->cached_vals[indx], temp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (td_info->sensor->temp_correlation) {
|
|
ret = td_info->sensor->temp_correlation(td_info->sensor,
|
|
*temp, temp);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
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 acpi_status pmic_check(acpi_handle handle, u32 lvl, void *context, void **rv)
|
|
{
|
|
valid_entry = true;
|
|
return (acpi_status) 0x0000;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
return td_info;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_THERMAL
|
|
static int read_slope(struct thermal_zone_device *tzd, long *slope)
|
|
{
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
|
|
*slope = td_info->sensor->slope;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int update_slope(struct thermal_zone_device *tzd, long slope)
|
|
{
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
|
|
td_info->sensor->slope = slope;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_intercept(struct thermal_zone_device *tzd, long *intercept)
|
|
{
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
|
|
*intercept = td_info->sensor->intercept;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int update_intercept(struct thermal_zone_device *tzd, long intercept)
|
|
{
|
|
struct thermal_device_info *td_info = tzd->devdata;
|
|
|
|
td_info->sensor->intercept = intercept;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static struct thermal_zone_device_ops tzd_ops = {
|
|
.get_temp = show_temp,
|
|
#ifdef CONFIG_DEBUG_THERMAL
|
|
.get_slope = read_slope,
|
|
.set_slope = update_slope,
|
|
.get_intercept = read_intercept,
|
|
.set_intercept = update_intercept,
|
|
#endif
|
|
};
|
|
|
|
static int byt_cr_thermal_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
int i, size, ret;
|
|
struct intel_mid_thermal_platform_data *pdata;
|
|
|
|
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->sensors = pdata->sensors;
|
|
platform_set_drvdata(pdev, tdata);
|
|
|
|
size = sizeof(struct thermal_zone_device *) * tdata->num_sensors;
|
|
tdata->tzd = kzalloc(size, GFP_KERNEL);
|
|
if (!tdata->tzd) {
|
|
dev_err(&pdev->dev, "kzalloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto exit_free;
|
|
}
|
|
|
|
/* Register with IIO to sample temperature values */
|
|
tdata->iio_chan = iio_channel_get_all(&pdev->dev);
|
|
if (IS_ERR(tdata->iio_chan)) {
|
|
dev_err(&pdev->dev, "tdata->iio_chan is null\n");
|
|
ret = -EINVAL;
|
|
goto exit_tzd;
|
|
}
|
|
|
|
/* Check whether we got all the 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;
|
|
}
|
|
|
|
/* Assign values based on type of PMIC */
|
|
if (ACPI_SUCCESS(acpi_get_devices("INT33F5", pmic_check, NULL, NULL)) && valid_entry) {
|
|
dev_info(&pdev->dev, "TI PMIC ACPI entry[INT33F5] found\n");
|
|
*(adc_code + 0) = (int *)(adc_code_ti[0]);
|
|
*(adc_code + 1) = (int *)(adc_code_ti[1]);
|
|
table_length = TABLE_LENGTH_TI;
|
|
adc_to_pmic_die_temp = &adc_to_pmic_die_temp_ti;
|
|
} else if (ACPI_SUCCESS(acpi_get_devices("INT33F4", pmic_check, NULL, NULL))
|
|
&& valid_entry) {
|
|
dev_info(&pdev->dev, "X-Power PMIC ACPI entry[INT33F4] found\n");
|
|
*(adc_code + 0) = (int *)(adc_code_xpwr[0]);
|
|
*(adc_code + 1) = (int *)(adc_code_xpwr[1]);
|
|
table_length = TABLE_LENGTH_XPWR;
|
|
adc_to_pmic_die_temp = &adc_to_pmic_die_temp_xpwr;
|
|
} else {
|
|
*(adc_code + 0) = (int *)(adc_code_xpwr[0]);
|
|
*(adc_code + 1) = (int *)(adc_code_xpwr[1]);
|
|
table_length = TABLE_LENGTH_XPWR;
|
|
adc_to_pmic_die_temp = &adc_to_pmic_die_temp_xpwr;
|
|
}
|
|
|
|
/* Register each sensor with the generic thermal framework */
|
|
for (i = 0; i < tdata->num_sensors; i++) {
|
|
tdata->tzd[i] = thermal_zone_device_register(
|
|
tdata->sensors[i].name, 0, 0,
|
|
initialize_sensor(i, &tdata->sensors[i]),
|
|
&tzd_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;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
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 byt_cr_thermal_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int byt_cr_thermal_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int byt_cr_thermal_remove(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
struct thermal_data *tdata = platform_get_drvdata(pdev);
|
|
|
|
if (!tdata)
|
|
return 0;
|
|
|
|
for (i = 0; i < tdata->num_sensors; i++)
|
|
thermal_zone_device_unregister(tdata->tzd[i]);
|
|
|
|
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 = byt_cr_thermal_suspend,
|
|
.resume = byt_cr_thermal_resume,
|
|
};
|
|
|
|
static struct platform_driver byt_cr_thermal_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
.pm = &thermal_pm_ops,
|
|
},
|
|
.probe = byt_cr_thermal_probe,
|
|
.remove = byt_cr_thermal_remove,
|
|
};
|
|
|
|
static int byt_cr_thermal_module_init(void)
|
|
{
|
|
return platform_driver_register(&byt_cr_thermal_driver);
|
|
}
|
|
|
|
static void byt_cr_thermal_module_exit(void)
|
|
{
|
|
platform_driver_unregister(&byt_cr_thermal_driver);
|
|
}
|
|
|
|
late_initcall(byt_cr_thermal_module_init);
|
|
module_exit(byt_cr_thermal_module_exit);
|
|
|
|
MODULE_AUTHOR("Shravan B M <shravan.k.b.m@intel.com>");
|
|
MODULE_DESCRIPTION("Intel Baytrail CR Platform Thermal Driver");
|
|
MODULE_LICENSE("GPL");
|