summaryrefslogtreecommitdiff
path: root/drivers/thermal
diff options
context:
space:
mode:
authorPreetham Chandru R <pchandru@nvidia.com>2013-12-12 18:42:46 +0530
committerLaxman Dewangan <ldewangan@nvidia.com>2013-12-19 05:21:03 -0800
commit7c751d0439730d6434cc5d58014e6ea54ff8093d (patch)
treeb7b076514ca022efe9948dfa31ca14d702525759 /drivers/thermal
parentbd7779e239de6415a2587e716303974aca686732 (diff)
thermal: tmp006: Add skin temperature support
Add tmp006 skin temperature monitoring support Bug 1397494 Signed-off-by: Preetham Chandru R <pchandru@nvidia.com> Change-Id: I5ebe460fbb0bef891547c25ef86a9521f6f3efbd Reviewed-on: http://git-master/r/331571 Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Diffstat (limited to 'drivers/thermal')
-rw-r--r--drivers/thermal/Kconfig9
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/tmp006.c486
3 files changed, 496 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 437865286189..04ae3736a95c 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -206,4 +206,13 @@ config PALMAS_THERMAL
different critical temperature thresholds from which we can notify
thermal core to orderly power off the system.
+config SENSORS_TMP006
+ tristate "Skin Temperature Sensor"
+ depends on I2C
+ help
+ Say yes here if you wish to include the skin Temperature sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tmp006. If unsure, say N here.
+
endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 5798a352faa3..d5cd7b92dc1f 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -27,4 +27,5 @@ obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
obj-$(CONFIG_PWM_FAN) += pwm_fan.o
obj-$(CONFIG_GENERIC_ADC_THERMAL) += generic_adc_thermal.o
obj-$(CONFIG_PALMAS_THERMAL) += palmas_thermal.o
+obj-$(CONFIG_SENSORS_TMP006) += tmp006.o
diff --git a/drivers/thermal/tmp006.c b/drivers/thermal/tmp006.c
new file mode 100644
index 000000000000..3fc2e0db5b31
--- /dev/null
+++ b/drivers/thermal/tmp006.c
@@ -0,0 +1,486 @@
+/*
+ * drivers/thermal/tmp006.c
+ *
+ * Driver for TMP006, Skin temperature monitoring device
+ *
+ * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/thermal.h>
+#include <mach/thermal.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
+
+/* Register Address */
+
+#define SENSOR_VOLTAGE 0X00
+#define AMBIENT_TEMP 0X01
+#define CONFIGURATION 0X02
+#define MANUFACTURE_ID 0XFE
+#define DEVICE_ID 0XFF
+
+#define MAX_STR_PRINT 50
+#define TMP006_POLL_INT 9000 /* 9 seconds */
+
+#define TMP006_TRANS_CORRECT
+#define TMP006_FILTER_OUTPUT
+
+struct tmp006_data {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct thermal_zone_device *thz_dev;
+};
+
+static const struct regmap_range tmp006_readable_ranges[] = {
+ regmap_reg_range(SENSOR_VOLTAGE, CONFIGURATION),
+ regmap_reg_range(MANUFACTURE_ID, DEVICE_ID),
+};
+
+static const struct regmap_access_table tmp006_readable_table = {
+ .yes_ranges = tmp006_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(tmp006_readable_ranges),
+};
+
+struct regmap_range tmp006_writable_ranges[] = {
+ regmap_reg_range(CONFIGURATION, CONFIGURATION),
+};
+
+struct regmap_access_table tmp006_writable_table = {
+ .yes_ranges = tmp006_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(tmp006_writable_ranges),
+};
+
+static const struct regmap_range tmp006_volatile_ranges[] = {
+ regmap_reg_range(SENSOR_VOLTAGE, CONFIGURATION),
+};
+
+static const struct regmap_access_table tmp006_volatile_table = {
+ .no_ranges = tmp006_volatile_ranges,
+ .n_no_ranges = ARRAY_SIZE(tmp006_volatile_ranges),
+};
+
+static const struct regmap_config tmp006_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = DEVICE_ID,
+ .rd_table = &tmp006_readable_table,
+ .wr_table = &tmp006_writable_table,
+ .volatile_table = &tmp006_volatile_table,
+};
+
+static u16 tmp006_write_word_data(const struct i2c_client *client, u8 command,
+ u16 write_data)
+{
+ struct tmp006_data *data = i2c_get_clientdata(client);
+
+ return regmap_write(data->regmap, command, write_data);
+}
+
+static u16 tmp006_read_word_data(const struct i2c_client *client, u8 command)
+{
+ struct tmp006_data *data = i2c_get_clientdata(client);
+ u32 read_word = 0;
+ int ret;
+
+ ret = regmap_read(data->regmap, command, &read_word);
+ return (u16) read_word;
+}
+
+/* Manufacture id for tmp006 is 0x5449*/
+static u16 tmp006_get_manufacture_id(const struct i2c_client *client)
+{
+ return tmp006_read_word_data(client, MANUFACTURE_ID);
+}
+
+/* Device id for tmp006 is 0x0067*/
+static u16 tmp006_get_device_id(const struct i2c_client *client)
+{
+ return tmp006_read_word_data(client, DEVICE_ID);
+}
+
+static int tmp006_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ return 0;
+}
+
+static int tmp006_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ return 0;
+}
+
+static int tmp006_get_trip_temp(struct thermal_zone_device *thz,
+ int trip, unsigned long *temp)
+{
+ *temp = 9000;
+ return 0;
+}
+
+static int tmp006_get_trip_type(struct thermal_zone_device *thz,
+ int trip, enum thermal_trip_type *type)
+{
+ return THERMAL_TRIP_PASSIVE;
+}
+
+/*
+* This function calculates the power 4 of a 16 bit number
+* and returns only the 32 bit msbs.
+*
+* It first calculates n squared.
+* It then splits the squared value to two 16 bit numbers.
+* The output is approximated as following
+* n2 = n * n
+* n2 = a + b
+* n4 = a2 + 2ab + b2
+*/
+static s32 tmp006_Power4(s32 n)
+{
+
+ s32 lsb;
+ s32 msb;
+ s32 n2;
+
+ n2 = n * n;
+ lsb = n2 & 0x00003FFF;
+ msb = n2 >> 14;
+ return (msb * msb) + (((msb * lsb) + ((lsb * lsb) >> 15)) >> 13);
+}
+
+/*
+* Calculate 4th order square root.
+* This function calculates its output using binary search.
+* Takes 14 iterations.
+*/
+static s32 tmp006_Sqrt4(s32 n)
+{
+ s32 val = 0x00000040;
+ s32 val2;
+ s32 val4;
+ /* Binary search to find the sqrt
+ (14 most significant bits with 1 LSB = 0.03125C)*/
+
+ /* Bit 14, The first branch in the binary tree can be predefined */
+ val4 = 16777216;
+ if (val4 > n)
+ val = 0x00;
+ /* Bit 13 */
+ val = val | 0x0020; /* Enable the next bit */
+ val2 = val * val;
+ val4 = val2 * val2; /* Calculate the power 4 */
+ if (val4 > n)
+ val = val & 0xFFDF;
+ /* Bit 12 */
+ val = val | 0x0010;
+ val2 = val * val;
+ val4 = val2 * val2;
+ if (val4 > n)
+ val = val & 0xFFEF;
+ /* Bit 11 */
+ val = val | 0x0008;
+ val2 = val * val;
+ val4 = val2 * val2;
+ if (val4 > n)
+ val = val & 0xFFF7;
+ /* Bit 10 */
+ val = val | 0x0004;
+ val2 = val * val;
+ val4 = val2 * val2;
+ if (val4 > n)
+ val = val & 0xFFFB;
+ /* Bit 9 */
+ val = val | 0x0002;
+ val2 = val * val;
+ val4 = val2 * val2;
+ if (val4 > n)
+ val = val & 0xFFFD;
+ /* Bit 8 */
+ val = val | 0x0001;
+ val2 = val * val;
+ val4 = val2 * val2;
+ if (val4 > n)
+ val = val & 0xFFFE;
+ /* Shift the 7 msb calculated so far to the left */
+ val = val << 7;
+ /* Bit 7 */
+ val = val | 0x0040;
+ /*
+ After the first 7 bits, the tmp006_Power4 function
+ is used to avoid running out of bits.
+ */
+ val4 = tmp006_Power4(val);
+ if (val4 > n)
+ val = val & 0xFFBF;
+ /* Bit 6 */
+ val = val | 0x0020;
+ val4 = tmp006_Power4(val);
+ if (val4 > n)
+ val = val & 0xFFDF;
+ /* Bit 5 */
+ val = val | 0x0010;
+ val4 = tmp006_Power4(val);
+ if (val4 > n)
+ val = val & 0xFFEF;
+ /* Bit 4 */
+ val = val | 0x0008;
+ val4 = tmp006_Power4(val);
+ if (val4 > n)
+ val = val & 0xFFF7;
+ /* Bit 3 */
+ val = val | 0x0004;
+ val4 = tmp006_Power4(val);
+ if (val4 > n)
+ val = val & 0xFFFB;
+ /* Bit 2 */
+ val = val | 0x0002;
+ val4 = tmp006_Power4(val);
+ if (val4 > n)
+ val = val & 0xFFFD;
+ /* Bit 1 */
+ val = val | 0x0001;
+ val4 = tmp006_Power4(val);
+ if (val4 > n)
+ val = val & 0xFFFE;
+
+ return val;
+}
+
+
+/*
+* Returns a two's complement binary number, where 1 LSB = 0.03125°C.
+* We must first perform a right shift of 2 bits on the result before
+* multiplying by 0.03125°C.
+*/
+static s16 tmp006_Calculate(s16 vout, s16 tdie)
+{
+ /* Constants */
+ /* (0.15625e-6 / s0) (Must be a positive number) */
+ s32 s0_inv = 2297794;
+ /* a1 * 2^18 (limited to +/- 0.12) */
+ s32 a1 = 459;
+ /* a2 * 2^24 (limited to +/- 0.0019) */
+ s32 a2 = -336;
+ /* b0 / 0.15625e-6 */
+ s32 b0 = -192;
+ /* (b1 / 0.15625e-6) * 2^10 (limited to +/- 4.9e-6) */
+ s32 b1 = -3932;
+ /* (b2 / 0.15625e-6) * 2^18 (limited to +/- 0.019e-6) */
+ s32 b2 = 419;
+
+ /* Filter constants */
+ s32 d1 = 3277; /* d1*2^14 */
+ s32 d2 = 9830; /* = d2*2^14 */
+ s32 e1 = 1638; /* = e1*2^14 */
+ s32 e2 = 13108; /* = e2*2^14 */
+
+ /* Transient correction constant
+ (f1 / 0.15625e-6)*2^7*(2*d1)/(1-d1)
+ */
+ s32 f1 = 131072;
+
+ /* Variables */
+ s32 s;
+ s32 tdie_32; /* 32 bit die temperature reading */
+ s32 vout_32; /* 32 bit die temperature reading */
+ s32 dtref; /* difference to reference temperature */
+ s32 dtref2; /* difference to reference temperature squared */
+ s32 fvout; /* Offset and transient corrected output */
+ s32 lsb; /* Used to capture lower bits of a number */
+ s32 msb; /* Used to capture upper bits of a number */
+ s32 tobj; /* Output */
+ s32 tdie_slope = 0; /* Assume initial slope to be zero */
+
+ static s32 tdie_filtered;
+ static s32 tdie_previous;
+ static s32 tobj_filtered;
+ static s32 tobj_previous;
+ static s32 first_run = 1;
+
+ vout_32 = (s32)vout;
+
+ /* Make sure negative numbers are handled correctly */
+ if (vout_32 >= 32768)
+ vout_32 = vout_32 - 65536;
+
+ tdie_32 = (s32)tdie >> 2; /* Get the 14 msb bits */
+ dtref = tdie_32 - 800; /* 800 = 25 / 0.03125 (Calculate Tdie - Tref) */
+ dtref2 = dtref * dtref; /* (Tdie - Tref) ^ 2 */
+
+ /* 8741 = 273.15 / 0.03125 (Convert to Kelvin) */
+ tdie_32 = tdie_32 + 8741;
+
+#ifdef TMP006_TRANS_CORRECT
+ /* Transient correction */
+ if (first_run) {
+ tdie_previous = tdie_32;
+ tdie_filtered = tdie_32 << 16;
+#ifndef TMP006_FILTER_OUTPUT
+ first_run = 0;
+#endif
+ }
+
+ lsb = tdie_filtered & 0x00003FFF; /* Get 14 lsb bits */
+ msb = tdie_filtered >> 14; /* Get 16 msb bits */
+
+ tdie_filtered = ((d1 * ((tdie_32 + tdie_previous) << 2))
+ + (d2 * msb) + ((d2 * lsb) >> 14));
+ tdie_slope = ((tdie_32 << 16) - tdie_filtered) >> 9;
+ tdie_previous = tdie_32;
+
+ lsb = (dtref2 & 0x00007FFF); /* Get the lsb bits */
+ msb = dtref2 >> 15; /* Get the msb bits */
+ fvout = ((vout_32 - b0) << 7) - ((((b1 * dtref) >> 2) + ((msb * b2))
+ + ((lsb * b2) >> 15) - ((tdie_slope * f1) >> 6)) >> 6);
+#else
+ /* No transient correction */
+ lsb = (dtref2 & 0x00007FFF); /* Get the lsb bits */
+ msb = dtref2 >> 15; /* Get the msb bits */
+ fvout = ((vout_32 - b0) << 7) - ((((b1 * dtref) >> 2) +
+ ((msb * b2)) + ((lsb * b2) >> 15)) >> 6);
+#endif
+ /* Core equation */
+ s = (1 << 15) + ((((dtref * a1) >> 4) + (msb * a2) +
+ ((lsb * a2) >> 15)) >> 4);
+ msb = s0_inv / s; /* Divide the numbers */
+ lsb = ((s0_inv % s) << 8) / s; /* Get the remainder */
+ fvout = (fvout * msb) + ((fvout * lsb) >> 8);
+ tobj = tmp006_Sqrt4(tmp006_Power4(tdie_32) + (fvout)) - 8741;
+
+#ifdef TMP006_FILTER_OUTPUT
+ /* Output filtering */
+ if (first_run) {
+ tobj_previous = tobj;
+ tobj_filtered = tobj << 14;
+ first_run = 0; /* Not the first run anymore */
+ }
+
+ lsb = tobj_filtered & 0x00003FFF;
+ msb = tobj_filtered >> 14;
+
+ tobj_filtered = (e1 * (tobj + tobj_previous)) + (e2 * msb) +
+ ((e2 * lsb) >> 14);
+ tobj_previous = tobj;
+
+ return tobj_filtered >> 12;
+#else
+ return tobj << 2;
+#endif
+
+}
+
+static int tmp006_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *t)
+{
+ struct tmp006_data *data = thermal->devdata;
+ struct i2c_client *client = data->client;
+ u16 cv = 0x0;
+ u16 sv = 0x0;
+ u16 at = 0x0;
+ s32 ret = 0x0;
+ int tk = 0;
+
+ cv = tmp006_read_word_data(client, CONFIGURATION);
+ ret = tmp006_write_word_data(client, CONFIGURATION, 0x7500);
+ do {
+ cv = tmp006_read_word_data(client, CONFIGURATION);
+ } while (!(cv & 0x0080));
+ sv = tmp006_read_word_data(client, SENSOR_VOLTAGE);
+ at = tmp006_read_word_data(client, AMBIENT_TEMP);
+ tk = tmp006_Calculate(sv, at);
+ *t = tk;
+ dev_info(&client->dev,
+ "Sensor voltage = %x ambient temp = %x Target object temp=%x\n",
+ sv, at, tk);
+ return 0;
+}
+
+/* bind callback functions to thermalzone */
+static struct thermal_zone_device_ops tmp006_dev_ops = {
+ .bind = tmp006_bind,
+ .unbind = tmp006_unbind,
+ .get_temp = tmp006_get_temp,
+ .get_trip_type = tmp006_get_trip_type,
+ .get_trip_temp = tmp006_get_trip_temp,
+};
+
+static int tmp006_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err = 0;
+ struct tmp006_data *data;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct tmp006_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ data->client = client;
+ data->regmap = devm_regmap_init_i2c(client, &tmp006_regmap_config);
+ if (IS_ERR(data->regmap)) {
+ err = PTR_ERR(data->regmap);
+ dev_err(&client->dev,
+ "%s(): regmap allocation failed with err %d\n",
+ __func__, err);
+ return err;
+ }
+ i2c_set_clientdata(client, data);
+ data->thz_dev = thermal_zone_device_register("tmp006", 1, 0, data,
+ &tmp006_dev_ops, NULL, 0, TMP006_POLL_INT);
+ if (IS_ERR(data->thz_dev)) {
+ err = PTR_ERR(data->thz_dev);
+ dev_err(&client->dev,
+ "\n thermal_zone_device_register error err=%d ", err);
+ return err;
+ }
+ return 0;
+}
+
+static int tmp006_remove(struct i2c_client *client)
+{
+ struct tmp006_data *data = i2c_get_clientdata(client);
+
+ thermal_zone_device_unregister(data->thz_dev);
+ return 0;
+}
+
+static const struct i2c_device_id tmp006_id[] = {
+ {"tmp006", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, tmp006_id);
+
+static struct i2c_driver tmp006_driver = {
+ .driver = {
+ .name = "tmp006",
+ .owner = THIS_MODULE,
+ },
+ .probe = tmp006_probe,
+ .remove = tmp006_remove,
+ .id_table = tmp006_id,
+};
+
+module_i2c_driver(tmp006_driver);
+
+MODULE_DESCRIPTION("Skin Temperature Sensor driver for tmp006");
+MODULE_AUTHOR("Preetham Chandru Ramchandra");
+MODULE_LICENSE("GPL v2");