summaryrefslogtreecommitdiff
path: root/drivers/hwmon
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig33
-rw-r--r--drivers/hwmon/Makefile4
-rw-r--r--drivers/hwmon/adt7461.c809
-rw-r--r--drivers/hwmon/ina219.c414
-rw-r--r--drivers/hwmon/ina230.c561
-rw-r--r--drivers/hwmon/tegra-tsensor.c1991
6 files changed, 3812 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 0b62c3c6b7ce..1f46b8d3a791 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -179,6 +179,16 @@ config SENSORS_ADT7411
This driver can also be built as a module. If so, the module
will be called adt7411.
+config SENSORS_ADT7461
+ tristate "Analog Devices ADT7461"
+ depends on I2C && EXPERIMENTAL
+ help
+ If you say yes here you get support for the Analog Devices
+ ADT7461 temperature monitoring chips.
+
+ This driver can also be built as a module. If so, the module
+ will be called adt7461.
+
config SENSORS_ADT7462
tristate "Analog Devices ADT7462"
depends on I2C && EXPERIMENTAL
@@ -1058,6 +1068,14 @@ config SENSORS_AMC6821
This driver can also be build as a module. If so, the module
will be called amc6821.
+config SENSORS_TEGRA_TSENSOR
+ tristate "Nvidia Tegra Integrated temperature sensor"
+ depends on ARCH_TEGRA_3x_SOC
+ default n
+ help
+ If you say yes here you get support for integrated
+ temperature sensor in Nvidia tegra chipset.
+
config SENSORS_THMC50
tristate "Texas Instruments THMC50 / Analog Devices ADM1022"
depends on I2C
@@ -1327,6 +1345,21 @@ config SENSORS_MC13783_ADC
help
Support for the A/D converter on MC13783 PMIC.
+config SENSORS_INA219
+ tristate "Texas Instruments INA219 POWER MONITOR SENSOR DRIVER"
+ depends on I2C
+ default n
+ help
+ Support for the TI INA219 power monitor sensor.
+
+config SENSORS_INA230
+ tristate "Texas Instruments INA230 POWER MONITOR SENSOR DRIVER"
+ depends on I2C
+ help
+ Support for the TI INA230 power monitor sensor.
+ (also works for TI INA226)
+
+
if ACPI
comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 3c9ccefea791..d9797487180d 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_SENSORS_ADS1015) += ads1015.o
obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o
obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o
obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o
+obj-$(CONFIG_SENSORS_ADT7461) += adt7461.o
obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
@@ -123,6 +124,9 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
+obj-$(CONFIG_SENSORS_INA219) += ina219.o
+obj-$(CONFIG_SENSORS_INA230) += ina230.o
+obj-$(CONFIG_SENSORS_TEGRA_TSENSOR) += tegra-tsensor.o
obj-$(CONFIG_PMBUS) += pmbus/
diff --git a/drivers/hwmon/adt7461.c b/drivers/hwmon/adt7461.c
new file mode 100644
index 000000000000..bf71f993cd2c
--- /dev/null
+++ b/drivers/hwmon/adt7461.c
@@ -0,0 +1,809 @@
+/*
+ * adt7461.c - Linux kernel modules for hardware
+ * monitoring
+ * Copyright (C) 2003-2010 Jean Delvare <khali@linux-fr.org>
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/regulator/consumer.h>
+#include <linux/i2c.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/adt7461.h>
+
+#define DRIVER_NAME "adt7461"
+
+/*
+ * The ADT7461 registers
+ */
+
+#define ADT7461_REG_R_MAN_ID 0xFE
+#define ADT7461_REG_R_CHIP_ID 0xFF
+#define ADT7461_REG_R_CONFIG1 0x03
+#define ADT7461_REG_W_CONFIG1 0x09
+#define ADT7461_REG_R_CONVRATE 0x04
+#define ADT7461_REG_W_CONVRATE 0x0A
+#define ADT7461_REG_R_STATUS 0x02
+#define ADT7461_REG_R_LOCAL_TEMP 0x00
+#define ADT7461_REG_R_LOCAL_HIGH 0x05
+#define ADT7461_REG_W_LOCAL_HIGH 0x0B
+#define ADT7461_REG_R_LOCAL_LOW 0x06
+#define ADT7461_REG_W_LOCAL_LOW 0x0C
+#define ADT7461_REG_R_LOCAL_CRIT 0x20
+#define ADT7461_REG_W_LOCAL_CRIT 0x20
+#define ADT7461_REG_R_REMOTE_TEMPH 0x01
+#define ADT7461_REG_R_REMOTE_TEMPL 0x10
+#define ADT7461_REG_R_REMOTE_OFFSH 0x11
+#define ADT7461_REG_W_REMOTE_OFFSH 0x11
+#define ADT7461_REG_R_REMOTE_OFFSL 0x12
+#define ADT7461_REG_W_REMOTE_OFFSL 0x12
+#define ADT7461_REG_R_REMOTE_HIGHH 0x07
+#define ADT7461_REG_W_REMOTE_HIGHH 0x0D
+#define ADT7461_REG_R_REMOTE_HIGHL 0x13
+#define ADT7461_REG_W_REMOTE_HIGHL 0x13
+#define ADT7461_REG_R_REMOTE_LOWH 0x08
+#define ADT7461_REG_W_REMOTE_LOWH 0x0E
+#define ADT7461_REG_R_REMOTE_LOWL 0x14
+#define ADT7461_REG_W_REMOTE_LOWL 0x14
+#define ADT7461_REG_R_REMOTE_CRIT 0x19
+#define ADT7461_REG_W_REMOTE_CRIT 0x19
+#define ADT7461_REG_R_TCRIT_HYST 0x21
+#define ADT7461_REG_W_TCRIT_HYST 0x21
+
+/* Configuration Register Bits */
+#define EXTENDED_RANGE_BIT BIT(2)
+#define THERM2_BIT BIT(5)
+#define STANDBY_BIT BIT(6)
+#define ALERT_BIT BIT(7)
+
+/* Max Temperature Measurements */
+#define EXTENDED_RANGE_OFFSET 64U
+#define STANDARD_RANGE_MAX 127U
+#define EXTENDED_RANGE_MAX (150U + EXTENDED_RANGE_OFFSET)
+
+/*
+ * Device flags
+ */
+#define ADT7461_FLAG_ADT7461_EXT 0x01 /* ADT7461 extended mode */
+#define ADT7461_FLAG_THERM2 0x02 /* Pin 6 as Therm2 */
+
+/*
+ * Client data
+ */
+
+struct adt7461_data {
+ struct work_struct work;
+ struct i2c_client *client;
+ struct device *hwmon_dev;
+ struct mutex update_lock;
+ struct regulator *regulator;
+ char valid; /* zero until following fields are valid */
+ unsigned long last_updated; /* in jiffies */
+ int flags;
+
+ u8 config; /* configuration register value */
+ u8 alert_alarms; /* Which alarm bits trigger ALERT# */
+
+ /* registers values */
+ s8 temp8[4]; /* 0: local low limit
+ 1: local high limit
+ 2: local critical limit
+ 3: remote critical limit */
+ s16 temp11[5]; /* 0: remote input
+ 1: remote low limit
+ 2: remote high limit
+ 3: remote offset
+ 4: local input */
+ u8 temp_hyst;
+ u8 alarms; /* bitvector */
+ void (*alarm_fn)(bool raised);
+};
+
+/*
+ * Conversions
+ */
+
+static inline int temp_from_s8(s8 val)
+{
+ return val * 1000;
+}
+
+static u8 hyst_to_reg(long val)
+{
+ if (val <= 0)
+ return 0;
+ if (val >= 30500)
+ return 31;
+ return (val + 500) / 1000;
+}
+
+/*
+ * ADT7461 attempts to write values that are outside the range
+ * 0 < temp < 127 are treated as the boundary value.
+ *
+ * ADT7461 in "extended mode" operation uses unsigned integers offset by
+ * 64 (e.g., 0 -> -64 degC). The range is restricted to -64..191 degC.
+ */
+static inline int temp_from_u8(struct adt7461_data *data, u8 val)
+{
+ if (data->flags & ADT7461_FLAG_ADT7461_EXT)
+ return (val - 64) * 1000;
+ else
+ return temp_from_s8(val);
+}
+
+static inline int temp_from_u16(struct adt7461_data *data, u16 val)
+{
+ if (data->flags & ADT7461_FLAG_ADT7461_EXT)
+ return (val - 0x4000) / 64 * 250;
+ else
+ return val / 32 * 125;
+}
+
+static u8 temp_to_u8(struct adt7461_data *data, long val)
+{
+ if (data->flags & ADT7461_FLAG_ADT7461_EXT) {
+ if (val <= -64000)
+ return 0;
+ if (val >= 191000)
+ return 0xFF;
+ return (val + 500 + 64000) / 1000;
+ } else {
+ if (val <= 0)
+ return 0;
+ if (val >= 127000)
+ return 127;
+ return (val + 500) / 1000;
+ }
+}
+
+static u16 temp_to_u16(struct adt7461_data *data, long val)
+{
+ if (data->flags & ADT7461_FLAG_ADT7461_EXT) {
+ if (val <= -64000)
+ return 0;
+ if (val >= 191750)
+ return 0xFFC0;
+ return (val + 64000 + 125) / 250 * 64;
+ } else {
+ if (val <= 0)
+ return 0;
+ if (val >= 127750)
+ return 0x7FC0;
+ return (val + 125) / 250 * 64;
+ }
+}
+
+static int adt7461_read_reg(struct i2c_client* client, u8 reg, u8 *value)
+{
+ int err;
+
+ err = i2c_smbus_read_byte_data(client, reg);
+ if (err < 0) {
+ pr_err("adt7461_read_reg:Register %#02x read failed (%d)\n",
+ reg, err);
+ return err;
+ }
+ *value = err;
+
+ return 0;
+}
+
+static int adt7461_read16(struct i2c_client *client, u8 regh, u8 regl,
+ u16 *value)
+{
+ int err;
+ u8 oldh, newh, l;
+
+ /*
+ * There is a trick here. We have to read two registers to have the
+ * sensor temperature, but we have to beware a conversion could occur
+ * inbetween the readings. The datasheet says we should either use
+ * the one-shot conversion register, which we don't want to do
+ * (disables hardware monitoring) or monitor the busy bit, which is
+ * impossible (we can't read the values and monitor that bit at the
+ * exact same time). So the solution used here is to read the high
+ * byte once, then the low byte, then the high byte again. If the new
+ * high byte matches the old one, then we have a valid reading. Else
+ * we have to read the low byte again, and now we believe we have a
+ * correct reading.
+ */
+ if ((err = adt7461_read_reg(client, regh, &oldh))
+ || (err = adt7461_read_reg(client, regl, &l))
+ || (err = adt7461_read_reg(client, regh, &newh)))
+ return err;
+ if (oldh != newh) {
+ err = adt7461_read_reg(client, regl, &l);
+ if (err)
+ return err;
+ }
+ *value = (newh << 8) | l;
+
+ return 0;
+}
+
+static struct adt7461_data *adt7461_update_device(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct adt7461_data *data = i2c_get_clientdata(client);
+
+ mutex_lock(&data->update_lock);
+
+ if (time_after(jiffies, data->last_updated + HZ / 2 + HZ / 10)
+ || !data->valid) {
+ u8 h, l;
+
+ adt7461_read_reg(client, ADT7461_REG_R_LOCAL_LOW, &data->temp8[0]);
+ adt7461_read_reg(client, ADT7461_REG_R_LOCAL_HIGH, &data->temp8[1]);
+ adt7461_read_reg(client, ADT7461_REG_R_LOCAL_CRIT, &data->temp8[2]);
+ adt7461_read_reg(client, ADT7461_REG_R_REMOTE_CRIT, &data->temp8[3]);
+ adt7461_read_reg(client, ADT7461_REG_R_TCRIT_HYST, &data->temp_hyst);
+
+ if (adt7461_read_reg(client, ADT7461_REG_R_LOCAL_TEMP, &h) == 0)
+ data->temp11[4] = h << 8;
+
+ adt7461_read16(client, ADT7461_REG_R_REMOTE_TEMPH,
+ ADT7461_REG_R_REMOTE_TEMPL, &data->temp11[0]);
+
+ if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_LOWH, &h) == 0) {
+ data->temp11[1] = h << 8;
+ if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_LOWL, &l) == 0)
+ data->temp11[1] |= l;
+ }
+ if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_HIGHH, &h) == 0) {
+ data->temp11[2] = h << 8;
+ if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_HIGHL, &l) == 0)
+ data->temp11[2] |= l;
+ }
+
+ if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_OFFSH,
+ &h) == 0
+ && adt7461_read_reg(client, ADT7461_REG_R_REMOTE_OFFSL,
+ &l) == 0)
+ data->temp11[3] = (h << 8) | l;
+ adt7461_read_reg(client, ADT7461_REG_R_STATUS, &data->alarms);
+
+ /* Re-enable ALERT# output if relevant alarms are all clear */
+ if (!(data->flags & ADT7461_FLAG_THERM2)
+ && (data->alarms & data->alert_alarms) == 0) {
+ u8 config;
+
+ adt7461_read_reg(client, ADT7461_REG_R_CONFIG1, &config);
+ if (config & 0x80) {
+ pr_err("adt7461_update_device:Re-enabling ALERT#\n");
+ i2c_smbus_write_byte_data(client,
+ ADT7461_REG_W_CONFIG1,
+ config & ~ALERT_BIT);
+ }
+ }
+
+ data->last_updated = jiffies;
+ data->valid = 1;
+ }
+
+ mutex_unlock(&data->update_lock);
+
+ return data;
+}
+
+/*
+ * Sysfs stuff
+ */
+
+static ssize_t show_temp8(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct adt7461_data *data = adt7461_update_device(dev);
+ int temp;
+
+ temp = temp_from_u8(data, data->temp8[attr->index]);
+
+ return sprintf(buf, "%d\n", temp);
+}
+
+static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ static const u8 reg[4] = {
+ ADT7461_REG_W_LOCAL_LOW,
+ ADT7461_REG_W_LOCAL_HIGH,
+ ADT7461_REG_W_LOCAL_CRIT,
+ ADT7461_REG_W_REMOTE_CRIT,
+ };
+
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct i2c_client *client = to_i2c_client(dev);
+ struct adt7461_data *data = i2c_get_clientdata(client);
+ long val = simple_strtol(buf, NULL, 10);
+ int nr = attr->index;
+
+ mutex_lock(&data->update_lock);
+ data->temp8[nr] = temp_to_u8(data, val);
+ i2c_smbus_write_byte_data(client, reg[nr], data->temp8[nr]);
+ mutex_unlock(&data->update_lock);
+ return count;
+}
+
+static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct adt7461_data *data = adt7461_update_device(dev);
+ int temp;
+
+ temp = temp_from_u16(data, data->temp11[attr->index]);
+
+ return sprintf(buf, "%d\n", temp);
+}
+
+static ssize_t set_temp11(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ static const u8 reg[6] = {
+ ADT7461_REG_W_REMOTE_LOWH,
+ ADT7461_REG_W_REMOTE_LOWL,
+ ADT7461_REG_W_REMOTE_HIGHH,
+ ADT7461_REG_W_REMOTE_HIGHL,
+ ADT7461_REG_W_REMOTE_OFFSH,
+ ADT7461_REG_W_REMOTE_OFFSL,
+ };
+
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct i2c_client *client = to_i2c_client(dev);
+ struct adt7461_data *data = i2c_get_clientdata(client);
+ long val = simple_strtol(buf, NULL, 10);
+ int nr = attr->index;
+
+ mutex_lock(&data->update_lock);
+ data->temp11[nr] = temp_to_u16(data, val);
+
+ i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2],
+ data->temp11[nr] >> 8);
+ i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2 + 1],
+ data->temp11[nr] & 0xff);
+ mutex_unlock(&data->update_lock);
+ return count;
+}
+
+static ssize_t show_temphyst(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct adt7461_data *data = adt7461_update_device(dev);
+ int temp;
+
+ temp = temp_from_u8(data, data->temp8[attr->index]);
+
+ return sprintf(buf, "%d\n", temp - temp_from_s8(data->temp_hyst));
+}
+
+static ssize_t set_temphyst(struct device *dev, struct device_attribute *dummy,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct adt7461_data *data = i2c_get_clientdata(client);
+ long val = simple_strtol(buf, NULL, 10);
+ int temp;
+
+ mutex_lock(&data->update_lock);
+ temp = temp_from_u8(data, data->temp8[2]);
+ data->temp_hyst = hyst_to_reg(temp - val);
+ i2c_smbus_write_byte_data(client, ADT7461_REG_W_TCRIT_HYST,
+ data->temp_hyst);
+ mutex_unlock(&data->update_lock);
+ return count;
+}
+
+static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy,
+ char *buf)
+{
+ struct adt7461_data *data = adt7461_update_device(dev);
+ return sprintf(buf, "%d\n", data->alarms);
+}
+
+static ssize_t show_alarm(struct device *dev, struct device_attribute
+ *devattr, char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct adt7461_data *data = adt7461_update_device(dev);
+ int bitnr = attr->index;
+
+ return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp11, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp11, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp8,
+ set_temp8, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp11,
+ set_temp11, 1);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp8,
+ set_temp8, 1);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp11,
+ set_temp11, 2);
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp8,
+ set_temp8, 2);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp8,
+ set_temp8, 3);
+static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temphyst,
+ set_temphyst, 2);
+static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_temphyst, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp2_offset, S_IWUSR | S_IRUGO, show_temp11,
+ set_temp11, 3);
+
+/* Individual alarm files */
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 5);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6);
+/* Raw alarm file for compatibility */
+static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+static struct attribute *adt7461_attributes[] = {
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_min.dev_attr.attr,
+ &sensor_dev_attr_temp2_min.dev_attr.attr,
+ &sensor_dev_attr_temp1_max.dev_attr.attr,
+ &sensor_dev_attr_temp2_max.dev_attr.attr,
+ &sensor_dev_attr_temp1_crit.dev_attr.attr,
+ &sensor_dev_attr_temp2_crit.dev_attr.attr,
+ &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
+ &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
+
+ &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp2_fault.dev_attr.attr,
+ &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+ &dev_attr_alarms.attr,
+ NULL
+};
+
+static const struct attribute_group adt7461_group = {
+ .attrs = adt7461_attributes,
+};
+
+static void adt7461_work_func(struct work_struct *work)
+{
+ struct adt7461_data *data =
+ container_of(work, struct adt7461_data, work);
+ int irq = data->client->irq;
+
+ if (data->alarm_fn) {
+ /* Therm2 line is active low */
+ data->alarm_fn(!gpio_get_value(irq_to_gpio(irq)));
+ }
+}
+
+static irqreturn_t adt7461_irq(int irq, void *dev_id)
+{
+ struct adt7461_data *data = dev_id;
+ schedule_work(&data->work);
+
+ return IRQ_HANDLED;
+}
+
+static void adt7461_regulator_enable(struct i2c_client *client)
+{
+ struct adt7461_data *data = i2c_get_clientdata(client);
+
+ data->regulator = regulator_get(NULL, "vdd_vcore_temp");
+ if (IS_ERR_OR_NULL(data->regulator)) {
+ pr_err("adt7461_regulator_enable:Couldn't get regulator vdd_vcore_temp\n");
+ data->regulator = NULL;
+ } else {
+ regulator_enable(data->regulator);
+ /* Optimal time to get the regulator turned on
+ * before initializing adt7461 chip*/
+ mdelay(5);
+ }
+}
+
+static void adt7461_regulator_disable(struct i2c_client *client)
+{
+ struct adt7461_data *data = i2c_get_clientdata(client);
+ struct regulator *adt7461_reg = data->regulator;
+ int ret;
+
+ if (adt7461_reg) {
+ ret = regulator_is_enabled(adt7461_reg);
+ if (ret > 0)
+ regulator_disable(adt7461_reg);
+ regulator_put(adt7461_reg);
+ }
+ data->regulator = NULL;
+}
+
+static void adt7461_enable(struct i2c_client *client)
+{
+ struct adt7461_data *data = i2c_get_clientdata(client);
+
+ i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONFIG1,
+ data->config & ~STANDBY_BIT);
+}
+
+static void adt7461_disable(struct i2c_client *client)
+{
+ struct adt7461_data *data = i2c_get_clientdata(client);
+
+ i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONFIG1,
+ data->config | STANDBY_BIT);
+}
+
+static int adt7461_init_client(struct i2c_client *client)
+{
+ struct adt7461_data *data = i2c_get_clientdata(client);
+ struct adt7461_platform_data *pdata = client->dev.platform_data;
+ u8 config = 0;
+ u8 value;
+ int err;
+
+ if (!pdata || !pdata->supported_hwrev)
+ return -ENODEV;
+
+ if (pdata->therm2)
+ data->flags |= ADT7461_FLAG_THERM2;
+
+ if (pdata->ext_range)
+ data->flags |= ADT7461_FLAG_ADT7461_EXT;
+
+ adt7461_regulator_enable(client);
+
+ /* Start the conversions. */
+ err = i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONVRATE,
+ pdata->conv_rate);
+ if (err < 0)
+ goto error;
+
+ /* External temperature h/w shutdown limit */
+ value = temp_to_u8(data, pdata->shutdown_ext_limit * 1000);
+ err = i2c_smbus_write_byte_data(client,
+ ADT7461_REG_W_REMOTE_CRIT, value);
+ if (err < 0)
+ goto error;
+
+ /* Local temperature h/w shutdown limit */
+ value = temp_to_u8(data, pdata->shutdown_local_limit * 1000);
+ err = i2c_smbus_write_byte_data(client, ADT7461_REG_W_LOCAL_CRIT,
+ value);
+ if (err < 0)
+ goto error;
+
+ /* External Temperature Throttling limit */
+ value = temp_to_u8(data, pdata->throttling_ext_limit * 1000);
+ err = i2c_smbus_write_byte_data(client, ADT7461_REG_W_REMOTE_HIGHH,
+ value);
+ if (err < 0)
+ goto error;
+
+ /* Local Temperature Throttling limit */
+ value = (data->flags & ADT7461_FLAG_ADT7461_EXT) ?
+ EXTENDED_RANGE_MAX : STANDARD_RANGE_MAX;
+ err = i2c_smbus_write_byte_data(client, ADT7461_REG_W_LOCAL_HIGH,
+ value);
+ if (err < 0)
+ goto error;
+
+ /* Remote channel offset */
+ err = i2c_smbus_write_byte_data(client, ADT7461_REG_W_REMOTE_OFFSH,
+ pdata->offset);
+ if (err < 0)
+ goto error;
+
+ /* THERM hysteresis */
+ err = i2c_smbus_write_byte_data(client, ADT7461_REG_W_TCRIT_HYST,
+ pdata->hysteresis);
+ if (err < 0)
+ goto error;
+
+ if (data->flags & ADT7461_FLAG_THERM2) {
+ data->alarm_fn = pdata->alarm_fn;
+ config = (THERM2_BIT | STANDBY_BIT);
+ } else {
+ config = (~ALERT_BIT & ~THERM2_BIT & STANDBY_BIT);
+ }
+
+ err = i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONFIG1, config);
+ if (err < 0)
+ goto error;
+
+ data->config = config;
+ return 0;
+
+error:
+ pr_err("adt7461_init_client:Initialization failed!\n");
+ return err;
+}
+
+static int adt7461_init_irq(struct adt7461_data *data)
+{
+ INIT_WORK(&data->work, adt7461_work_func);
+
+ return request_irq(data->client->irq, adt7461_irq, IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING, DRIVER_NAME, data);
+}
+
+static int adt7461_probe(struct i2c_client *new_client,
+ const struct i2c_device_id *id)
+{
+ struct adt7461_data *data;
+ int err;
+
+ data = kzalloc(sizeof(struct adt7461_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = new_client;
+ i2c_set_clientdata(new_client, data);
+ mutex_init(&data->update_lock);
+
+ data->alert_alarms = 0x7c;
+
+ /* Initialize the ADT7461 chip */
+ err = adt7461_init_client(new_client);
+ if (err < 0)
+ goto exit_free;
+
+ if (data->flags & ADT7461_FLAG_THERM2) {
+ err = adt7461_init_irq(data);
+ if (err < 0)
+ goto exit_free;
+ }
+
+ /* Register sysfs hooks */
+ if ((err = sysfs_create_group(&new_client->dev.kobj, &adt7461_group)))
+ goto exit_free;
+ if ((err = device_create_file(&new_client->dev,
+ &sensor_dev_attr_temp2_offset.dev_attr)))
+ goto exit_remove_files;
+
+ data->hwmon_dev = hwmon_device_register(&new_client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ goto exit_remove_files;
+ }
+
+ adt7461_enable(new_client);
+ return 0;
+
+exit_remove_files:
+ sysfs_remove_group(&new_client->dev.kobj, &adt7461_group);
+exit_free:
+ kfree(data);
+ return err;
+}
+
+static int adt7461_remove(struct i2c_client *client)
+{
+ struct adt7461_data *data = i2c_get_clientdata(client);
+
+ free_irq(client->irq, data);
+ cancel_work_sync(&data->work);
+ hwmon_device_unregister(data->hwmon_dev);
+ sysfs_remove_group(&client->dev.kobj, &adt7461_group);
+ device_remove_file(&client->dev,
+ &sensor_dev_attr_temp2_offset.dev_attr);
+ adt7461_regulator_disable(client);
+
+ kfree(data);
+ return 0;
+}
+
+static void adt7461_alert(struct i2c_client *client, unsigned int flag)
+{
+ struct adt7461_data *data = i2c_get_clientdata(client);
+ u8 config, alarms;
+
+ adt7461_read_reg(client, ADT7461_REG_R_STATUS, &alarms);
+ if ((alarms & 0x7f) == 0) {
+ pr_err("adt7461_alert:Everything OK\n");
+ } else {
+ if (alarms & 0x61)
+ pr_err("adt7461_alert:temp%d out of range, please check!\n", 1);
+ if (alarms & 0x1a)
+ pr_err("adt7461_alert:temp%d out of range, please check!\n", 2);
+ if (alarms & 0x04)
+ pr_err("adt7461_alert:temp%d diode open, please check!\n", 2);
+
+ /* Disable ALERT# output, because these chips don't implement
+ SMBus alert correctly; they should only hold the alert line
+ low briefly. */
+ if (!(data->flags & ADT7461_FLAG_THERM2)
+ && (alarms & data->alert_alarms)) {
+ pr_err("adt7461_alert:Disabling ALERT#\n");
+ adt7461_read_reg(client, ADT7461_REG_R_CONFIG1, &config);
+ i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONFIG1,
+ config | ALERT_BIT);
+ }
+ }
+}
+
+#ifdef CONFIG_PM
+static int adt7461_suspend(struct i2c_client *client, pm_message_t state)
+{
+ disable_irq(client->irq);
+ adt7461_disable(client);
+
+ return 0;
+}
+
+static int adt7461_resume(struct i2c_client *client)
+{
+ adt7461_enable(client);
+ enable_irq(client->irq);
+
+ return 0;
+}
+#endif
+
+/*
+ * Driver data
+ */
+static const struct i2c_device_id adt7461_id[] = {
+ { DRIVER_NAME, 0 },
+};
+
+MODULE_DEVICE_TABLE(i2c, adt7461_id);
+
+static struct i2c_driver adt7461_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+ .probe = adt7461_probe,
+ .remove = adt7461_remove,
+ .alert = adt7461_alert,
+ .id_table = adt7461_id,
+#ifdef CONFIG_PM
+ .suspend = adt7461_suspend,
+ .resume = adt7461_resume,
+#endif
+};
+
+static int __init sensors_adt7461_init(void)
+{
+ return i2c_add_driver(&adt7461_driver);
+}
+
+static void __exit sensors_adt7461_exit(void)
+{
+ i2c_del_driver(&adt7461_driver);
+}
+
+MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
+MODULE_DESCRIPTION("ADT7461 driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_adt7461_init);
+module_exit(sensors_adt7461_exit);
diff --git a/drivers/hwmon/ina219.c b/drivers/hwmon/ina219.c
new file mode 100644
index 000000000000..cc5b85fdcf84
--- /dev/null
+++ b/drivers/hwmon/ina219.c
@@ -0,0 +1,414 @@
+/*
+ * ina219.c - driver for TI INA219 current / power monitor sensor
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * The INA219 is a sensor chip made by Texas Instruments. It measures
+ * power, voltage and current on a power rail.
+ * Complete datasheet can be obtained from website:
+ * http://focus.ti.com/lit/ds/symlink/ina219.pdf
+ *
+ * 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.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/hrtimer.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/device.h>
+#include <linux/sysdev.h>
+#include "linux/ina219.h"
+#include <linux/init.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+
+#define DRIVER_NAME "ina219"
+
+/* INA219 register offsets */
+#define INA219_CONFIG 0
+#define INA219_SHUNT 1
+#define INA219_VOLTAGE 2
+#define INA219_POWER 3
+#define INA219_CURRENT 4
+#define INA219_CAL 5
+
+/*
+ INA219 Sensor defines
+ Config info for ina219s
+ D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
+ rst BRNG PG1 PG0 BADC4 .-.BADC1 SADC4 - SADC1 MODE3 - MODE1
+ reset D15
+ bus_range=0 (d13)
+ pga_gain=0 (D12:D11)
+ bus_adc_setting=0x3 (d10:D7) 12-bit w/o oversampling (532uS)
+ shunt_adc_setting=0xb (D6:D3) 8x oversampling (4.26ms)
+ mode=0x7 (D2:D0) continuous shunt & bus
+*/
+#define INA219_CONFIG_DATA 0x1df
+#define INA219_RESET 0x8000
+
+struct power_mon_data {
+ s32 voltage;
+ s32 currentInMillis;
+ s32 power;
+};
+
+struct ina219_data {
+ struct device *hwmon_dev;
+ struct i2c_client *client;
+ struct ina219_platform_data *pInfo;
+ struct power_mon_data pm_data;
+ struct mutex mutex;
+};
+
+/* Set non-zero to enable debug prints */
+#define INA219_DEBUG_PRINTS 0
+
+#if INA219_DEBUG_PRINTS
+#define DEBUG_INA219(x) printk x
+#else
+#define DEBUG_INA219(x)
+#endif
+
+static s16 reorder_bytes(s16 a)
+{
+ s16 ret = ((a >> 8) & 0xff) | ((a & 0xff) << 8);
+ return ret;
+}
+
+/* set ina219 to power down mode */
+static s32 power_down_INA219(struct i2c_client *client)
+{
+ s32 retval;
+ retval = i2c_smbus_write_word_data(client, INA219_CONFIG, 0);
+ if (retval < 0)
+ dev_err(&client->dev, "power down failure sts: 0x%x\n", retval);
+ return retval;
+}
+
+static s32 show_rail_name(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina219_data *data = i2c_get_clientdata(client);
+ return sprintf(buf, "%s\n", data->pInfo->rail_name);
+}
+
+static s32 show_voltage(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina219_data *data = i2c_get_clientdata(client);
+ s32 retval;
+ s32 voltage_mV;
+
+ /* fill config data */
+ retval = i2c_smbus_write_word_data(client, INA219_CONFIG,
+ reorder_bytes(INA219_CONFIG_DATA));
+ if (retval < 0) {
+ dev_err(dev, "config data write failed sts: 0x%x\n", retval);
+ goto error;
+ }
+
+ /* fill calibration data */
+ retval = i2c_smbus_write_word_data(client, INA219_CAL,
+ reorder_bytes(data->pInfo->calibration_data));
+ if (retval < 0) {
+ dev_err(dev, "calib data write failed sts: 0x%x\n", retval);
+ goto error;
+ }
+
+ /* getting voltage readings in milli volts*/
+ voltage_mV =
+ reorder_bytes(i2c_smbus_read_word_data(client,
+ INA219_VOLTAGE));
+ DEBUG_INA219(("Ina219 voltage reg Value: 0x%x\n", voltage_mV));
+ if (voltage_mV < 0)
+ goto error;
+ voltage_mV = voltage_mV >> 1;
+ DEBUG_INA219(("Ina219 voltage in mv: %d\n", voltage_mV));
+
+ /* set ina219 to power down mode */
+ retval = power_down_INA219(client);
+ if (retval < 0)
+ goto error;
+
+ DEBUG_INA219(("%s volt = %d\n", __func__, voltage_mV));
+ return sprintf(buf, "%d mV\n", voltage_mV);
+error:
+ dev_err(dev, "%s: failed\n", __func__);
+ return retval;
+}
+
+
+static s32 show_power(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina219_data *data = i2c_get_clientdata(client);
+ s32 retval;
+ s32 power_mW;
+ s32 voltage_mV;
+ s32 overflow, conversion;
+
+ /* fill config data */
+ retval = i2c_smbus_write_word_data(client, INA219_CONFIG,
+ reorder_bytes(INA219_CONFIG_DATA));
+ if (retval < 0) {
+ dev_err(dev, "config data write failed sts: 0x%x\n", retval);
+ goto error;
+ }
+
+ /* fill calib data */
+ retval = i2c_smbus_write_word_data(client, INA219_CAL,
+ reorder_bytes(data->pInfo->calibration_data));
+ if (retval < 0) {
+ dev_err(dev, "calibration data write failed sts: 0x%x\n",
+ retval);
+ goto error;
+ }
+
+ /* check if the readings are valid */
+ do {
+ /* read power register to clear conversion bit */
+ retval = reorder_bytes(i2c_smbus_read_word_data(client,
+ INA219_POWER));
+ if (retval < 0) {
+ dev_err(dev, "CNVR bit clearing failure sts: 0x%x\n",
+ retval);
+ goto error;
+ }
+
+ voltage_mV =
+ reorder_bytes(i2c_smbus_read_word_data(client,
+ INA219_VOLTAGE));
+ DEBUG_INA219(("Ina219 voltage reg Value: 0x%x\n", voltage_mV));
+ overflow = voltage_mV & 1;
+ if (overflow) {
+ dev_err(dev, "overflow error\n");
+ return 0;
+ }
+ conversion = (voltage_mV >> 1) & 1;
+ DEBUG_INA219(("\n ina219 CNVR value:%d", conversion));
+ } while (!conversion);
+
+ /* getting power readings in milli watts*/
+ power_mW = reorder_bytes(i2c_smbus_read_word_data(client,
+ INA219_POWER));
+ DEBUG_INA219(("Ina219 power Reg: 0x%x\n", power_mW));
+ power_mW *= data->pInfo->power_lsb;
+ DEBUG_INA219(("Ina219 power Val: %d\n", power_mW));
+ if (power_mW < 0)
+ goto error;
+
+ /* set ina219 to power down mode */
+ retval = power_down_INA219(client);
+ if (retval < 0)
+ goto error;
+
+ DEBUG_INA219(("%s pow = %d\n", __func__, power_mW));
+ return sprintf(buf, "%d mW\n", power_mW);
+error:
+ dev_err(dev, "%s: failed\n", __func__);
+ return retval;
+}
+
+static s32 show_current(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina219_data *data = i2c_get_clientdata(client);
+ s32 retval;
+ s32 current_mA;
+ s32 voltage_mV;
+ s32 overflow, conversion;
+
+ /* fill config data */
+ retval = i2c_smbus_write_word_data(client, INA219_CONFIG,
+ reorder_bytes(INA219_CONFIG_DATA));
+ if (retval < 0) {
+ dev_err(dev, "config data write failed sts: 0x%x\n", retval);
+ goto error;
+ }
+
+ /* fill calib data */
+ retval = i2c_smbus_write_word_data(client, INA219_CAL,
+ reorder_bytes(data->pInfo->calibration_data));
+ if (retval < 0) {
+ dev_err(dev, "calibration data write failed sts: 0x%x\n",
+ retval);
+ goto error;
+ }
+
+ /* check if the readings are valid */
+ do {
+ /* read power register to clear conversion bit */
+ retval = reorder_bytes(i2c_smbus_read_word_data(client,
+ INA219_POWER));
+ if (retval < 0) {
+ dev_err(dev, "CNVR bit clearing failure sts: 0x%x\n",
+ retval);
+ goto error;
+ }
+
+ voltage_mV =
+ reorder_bytes(i2c_smbus_read_word_data(client,
+ INA219_VOLTAGE));
+ DEBUG_INA219(("Ina219 voltage reg Value: 0x%x\n", voltage_mV));
+ overflow = voltage_mV & 1;
+ if (overflow) {
+ dev_err(dev, "overflow error\n");
+ return 0;
+ }
+ conversion = (voltage_mV >> 1) & 1;
+ DEBUG_INA219(("\n ina219 CNVR value:%d", conversion));
+ } while (!conversion);
+
+ /* getting current readings in milli amps*/
+ current_mA = reorder_bytes(i2c_smbus_read_word_data(client,
+ INA219_CURRENT));
+ DEBUG_INA219(("Ina219 current Reg: 0x%x\n", current_mA));
+ if (current_mA < 0)
+ goto error;
+ current_mA =
+ (current_mA * data->pInfo->power_lsb) / data->pInfo->divisor;
+ DEBUG_INA219(("Ina219 current Value: %d\n", current_mA));
+
+ /* set ina219 to power down mode */
+ retval = power_down_INA219(client);
+ if (retval < 0)
+ goto error;
+
+
+ DEBUG_INA219(("%s current = %d\n", __func__, current_mA));
+ return sprintf(buf, "%d mA\n", current_mA);
+error:
+ dev_err(dev, "%s: failed\n", __func__);
+ return retval;
+}
+
+static struct sensor_device_attribute ina219[] = {
+ SENSOR_ATTR(rail_name, S_IRUGO, show_rail_name, NULL, 0),
+ SENSOR_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 0),
+ SENSOR_ATTR(curr1_input, S_IRUGO, show_current, NULL, 0),
+ SENSOR_ATTR(power1_input, S_IRUGO, show_power, NULL, 0),
+};
+
+static int __devinit ina219_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ina219_data *data;
+ int err;
+ u8 i;
+ data = kzalloc(sizeof(struct ina219_data), GFP_KERNEL);
+ if (!data) {
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ i2c_set_clientdata(client, data);
+ data->pInfo = client->dev.platform_data;
+ mutex_init(&data->mutex);
+ /* reset ina219 */
+ err = i2c_smbus_write_word_data(client, INA219_CONFIG,
+ reorder_bytes(INA219_RESET));
+ if (err < 0) {
+ dev_err(&client->dev, "ina219 reset failure status: 0x%x\n",
+ err);
+ goto exit_free;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ina219); i++) {
+ err = device_create_file(&client->dev, &ina219[i].dev_attr);
+ if (err) {
+ dev_err(&client->dev, "device_create_file failed.\n");
+ goto exit_free;
+ }
+ }
+
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ goto exit_remove;
+ }
+
+ /* set ina219 to power down mode */
+ err = power_down_INA219(client);
+ if (err < 0)
+ goto exit_remove;
+
+ return 0;
+
+exit_remove:
+ for (i = 0; i < ARRAY_SIZE(ina219); i++)
+ device_remove_file(&client->dev, &ina219[i].dev_attr);
+exit_free:
+ kfree(data);
+exit:
+ return err;
+}
+
+static int __devexit ina219_remove(struct i2c_client *client)
+{
+ u8 i;
+ struct ina219_data *data = i2c_get_clientdata(client);
+ hwmon_device_unregister(data->hwmon_dev);
+ for (i = 0; i < ARRAY_SIZE(ina219); i++)
+ device_remove_file(&client->dev, &ina219[i].dev_attr);
+ kfree(data);
+ return 0;
+}
+
+static const struct i2c_device_id ina219_id[] = {
+ {DRIVER_NAME, 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ina219_id);
+
+static struct i2c_driver ina219_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+ .probe = ina219_probe,
+ .remove = __devexit_p(ina219_remove),
+ .id_table = ina219_id,
+};
+
+static int __init ina219_init(void)
+{
+ return i2c_add_driver(&ina219_driver);
+}
+
+static void __exit ina219_exit(void)
+{
+ i2c_del_driver(&ina219_driver);
+}
+
+module_init(ina219_init);
+module_exit(ina219_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/ina230.c b/drivers/hwmon/ina230.c
new file mode 100644
index 000000000000..3591a9463108
--- /dev/null
+++ b/drivers/hwmon/ina230.c
@@ -0,0 +1,561 @@
+/*
+ * ina230.c - driver for TI INA230 current / power monitor sensor
+ * (also compatible with TI INA226)
+ *
+ *
+ * Copyright (c) 2011, NVIDIA 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ * The INA230(/INA226) is a sensor chip made by Texas Instruments. It measures
+ * power, voltage and current on a power rail and provides an alert on
+ * over voltage/power
+ * Complete datasheet can be obtained from ti.com
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/hrtimer.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/device.h>
+#include <linux/sysdev.h>
+#include <linux/platform_data/ina230.h>
+#include <linux/init.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/cpu.h>
+
+
+#define DRIVER_NAME "ina230"
+#define MEASURE_BUS_VOLT 0
+
+/* ina230 (/ ina226)register offsets */
+#define INA230_CONFIG 0
+#define INA230_SHUNT 1
+#define INA230_VOLTAGE 2
+#define INA230_POWER 3
+#define INA230_CURRENT 4
+#define INA230_CAL 5
+#define INA230_MASK 6
+#define INA230_ALERT 7
+
+/*
+Config register for ina230 (/ ina226):
+D15|D14 D13 D12|D11 D10 D09|D08 D07 D06|D05 D04 D03|D02 D01 D00
+rst|- - - |AVG |Vbus_CT |Vsh_CT |MODE
+*/
+#define INA230_RESET (1 << 15)
+#define INA230_AVG (0 << 9) /* 0 Averages */
+#define INA230_VBUS_CT (0 << 6) /* Vbus 140us conversion time */
+#define INA230_VSH_CT (0 << 3) /* Vshunt 140us conversion time */
+
+#if MEASURE_BUS_VOLT
+#define INA230_CONT_MODE 5 /* Continuous Shunt measurement */
+#define INA230_TRIG_MODE 1 /* Triggered Shunt measurement */
+#else
+#define INA230_CONT_MODE 7 /* Continuous Bus and shunt measure */
+#define INA230_TRIG_MODE 3 /* Triggered Bus and shunt measure */
+#endif
+
+#define INA230_POWER_DOWN 0
+#define INA230_CONT_CONFIG (INA230_AVG | INA230_VBUS_CT | \
+ INA230_VSH_CT | INA230_CONT_MODE)
+#define INA230_TRIG_CONFIG (INA230_AVG | INA230_VBUS_CT | \
+ INA230_VSH_CT | INA230_TRIG_MODE)
+
+/*
+Mask register for ina230 (/ina 226):
+D15|D14|D13|D12|D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00
+SOL|SUL|BOL|BUL|POL|CVR|- - - - - |AFF|CVF|OVF|APO|LEN
+*/
+#define INA230_MASK_SOL (1 << 15)
+#define INA230_MASK_SUL (1 << 14)
+
+
+struct ina230_data {
+ struct device *hwmon_dev;
+ struct i2c_client *client;
+ struct ina230_platform_data *pdata;
+ struct mutex mutex;
+ bool running;
+ struct notifier_block nb;
+};
+
+
+/* bus voltage resolution: 1.25mv */
+#define busv_register_to_mv(x) (((x) * 5) >> 2)
+
+/* shunt voltage resolution: 2.5uv */
+#define shuntv_register_to_uv(x) (((x) * 5) >> 1)
+#define uv_to_alert_register(x) (((x) << 1) / 5)
+
+
+
+static s32 ensure_enabled_start(struct i2c_client *client)
+{
+ struct ina230_data *data = i2c_get_clientdata(client);
+ int retval;
+
+ if (data->running)
+ return 0;
+
+ retval = i2c_smbus_write_word_data(client, INA230_CONFIG,
+ __constant_cpu_to_be16(INA230_TRIG_CONFIG));
+ if (retval < 0)
+ dev_err(&client->dev, "config data write failed sts: 0x%x\n",
+ retval);
+
+ return retval;
+}
+
+
+static void ensure_enabled_end(struct i2c_client *client)
+{
+ struct ina230_data *data = i2c_get_clientdata(client);
+ int retval;
+
+ if (data->running)
+ return;
+
+ retval = i2c_smbus_write_word_data(client, INA230_CONFIG,
+ __constant_cpu_to_be16(INA230_POWER_DOWN));
+ if (retval < 0)
+ dev_err(&client->dev, "power down failure sts: 0x%x\n",
+ retval);
+}
+
+
+static s32 __locked_power_down_ina230(struct i2c_client *client)
+{
+ s32 retval;
+ struct ina230_data *data = i2c_get_clientdata(client);
+
+ if (!data->running)
+ return 0;
+
+ retval = i2c_smbus_write_word_data(client, INA230_MASK, 0);
+ if (retval < 0)
+ dev_err(&client->dev, "mask write failure sts: 0x%x\n",
+ retval);
+
+ retval = i2c_smbus_write_word_data(client, INA230_CONFIG,
+ __constant_cpu_to_be16(INA230_POWER_DOWN));
+ if (retval < 0)
+ dev_err(&client->dev, "power down failure sts: 0x%x\n",
+ retval);
+
+ data->running = false;
+
+ return retval;
+}
+
+
+static s32 power_down_ina230(struct i2c_client *client)
+{
+ s32 retval;
+ struct ina230_data *data = i2c_get_clientdata(client);
+
+ mutex_lock(&data->mutex);
+ retval = __locked_power_down_ina230(client);
+ mutex_unlock(&data->mutex);
+
+ return retval;
+}
+
+
+static s32 __locked_start_current_mon(struct i2c_client *client)
+{
+ s32 retval;
+ s16 shunt_limit;
+ s16 alert_mask;
+ struct ina230_data *data = i2c_get_clientdata(client);
+
+ if (!data->pdata->current_threshold) {
+ dev_err(&client->dev, "no current threshold specified\n");
+ return -EINVAL;
+ }
+
+ retval = i2c_smbus_write_word_data(client, INA230_CONFIG,
+ __constant_cpu_to_be16(INA230_CONT_CONFIG));
+ if (retval < 0) {
+ dev_err(&client->dev, "config data write failed sts: 0x%x\n",
+ retval);
+ return retval;
+ }
+
+ shunt_limit = uv_to_alert_register(data->pdata->resistor *
+ data->pdata->current_threshold);
+
+ retval = i2c_smbus_write_word_data(client, INA230_ALERT,
+ cpu_to_be16(shunt_limit));
+ if (retval < 0) {
+ dev_err(&client->dev, "alert data write failed sts: 0x%x\n",
+ retval);
+ return retval;
+ }
+
+ alert_mask = shunt_limit >= 0 ? INA230_MASK_SOL : INA230_MASK_SUL;
+ retval = i2c_smbus_write_word_data(client, INA230_MASK,
+ cpu_to_be16(alert_mask));
+ if (retval < 0) {
+ dev_err(&client->dev, "mask data write failed sts: 0x%x\n",
+ retval);
+ return retval;
+ }
+
+ data->running = true;
+
+ return 0;
+}
+
+
+static void __locked_evaluate_state(struct i2c_client *client)
+{
+ struct ina230_data *data = i2c_get_clientdata(client);
+ int cpus = num_online_cpus();
+
+ if (data->running) {
+ if (cpus < data->pdata->min_cores_online ||
+ !data->pdata->current_threshold)
+ __locked_power_down_ina230(client);
+ } else {
+ if (cpus >= data->pdata->min_cores_online &&
+ data->pdata->current_threshold)
+ __locked_start_current_mon(client);
+ }
+}
+
+
+static void evaluate_state(struct i2c_client *client)
+{
+ struct ina230_data *data = i2c_get_clientdata(client);
+
+ mutex_lock(&data->mutex);
+ __locked_evaluate_state(client);
+ mutex_unlock(&data->mutex);
+}
+
+
+static s32 show_rail_name(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina230_data *data = i2c_get_clientdata(client);
+ return sprintf(buf, "%s\n", data->pdata->rail_name);
+}
+
+
+static s32 show_current_threshold(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina230_data *data = i2c_get_clientdata(client);
+ return sprintf(buf, "%d mA\n", data->pdata->current_threshold);
+}
+
+
+static s32 set_current_threshold(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina230_data *data = i2c_get_clientdata(client);
+ s32 retval;
+
+ mutex_lock(&data->mutex);
+
+ if (strict_strtol(buf, 10, (long *)&(data->pdata->current_threshold))) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ if (data->pdata->current_threshold) {
+ if (data->running) {
+ /* force restart */
+ retval = __locked_start_current_mon(client);
+ } else {
+ __locked_evaluate_state(client);
+ retval = 0;
+ }
+ } else {
+ retval = __locked_power_down_ina230(client);
+ }
+
+out:
+ mutex_unlock(&data->mutex);
+ if (retval >= 0)
+ return count;
+ return retval;
+}
+
+
+
+
+#if MEASURE_BUS_VOLT
+static s32 show_bus_voltage(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina230_data *data = i2c_get_clientdata(client);
+ s32 voltage_mV;
+ int retval;
+
+ mutex_lock(&data->mutex);
+ retval = ensure_enabled_start(client);
+ if (retval < 0) {
+ mutex_unlock(&data->mutex);
+ return retval;
+ }
+
+ /* getting voltage readings in milli volts*/
+ voltage_mV =
+ (s16)be16_to_cpu(i2c_smbus_read_word_data(client,
+ INA230_VOLTAGE));
+
+ ensure_enabled_end(client);
+ mutex_unlock(&data->mutex);
+
+ if (voltage_mV < 0) {
+ dev_err(dev, "%s: failed\n", __func__);
+ return -1;
+ }
+
+ voltage_mV = busv_register_to_mv(voltage_mV);
+
+ return sprintf(buf, "%d mV\n", voltage_mV);
+}
+#endif
+
+
+
+
+static s32 show_shunt_voltage(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina230_data *data = i2c_get_clientdata(client);
+ s32 voltage_uV;
+ int retval;
+
+ mutex_lock(&data->mutex);
+ retval = ensure_enabled_start(client);
+ if (retval < 0) {
+ mutex_unlock(&data->mutex);
+ return retval;
+ }
+
+ voltage_uV =
+ (s16)be16_to_cpu(i2c_smbus_read_word_data(client,
+ INA230_SHUNT));
+
+ ensure_enabled_end(client);
+ mutex_unlock(&data->mutex);
+
+ voltage_uV = shuntv_register_to_uv(voltage_uV);
+
+ return sprintf(buf, "%d uV\n", voltage_uV);
+}
+
+
+static s32 show_current(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ina230_data *data = i2c_get_clientdata(client);
+ s32 voltage_uV;
+ s32 current_mA;
+ int retval;
+
+ mutex_lock(&data->mutex);
+ retval = ensure_enabled_start(client);
+ if (retval < 0) {
+ mutex_unlock(&data->mutex);
+ return retval;
+ }
+
+ voltage_uV =
+ (s16)be16_to_cpu(i2c_smbus_read_word_data(client,
+ INA230_SHUNT));
+
+ ensure_enabled_end(client);
+ mutex_unlock(&data->mutex);
+
+ voltage_uV = shuntv_register_to_uv(voltage_uV);
+ current_mA = voltage_uV / data->pdata->resistor;
+
+ return sprintf(buf, "%d mA\n", current_mA);
+}
+
+
+static int ina230_hotplug_notify(struct notifier_block *nb, unsigned long event,
+ void *hcpu)
+{
+ struct ina230_data *data = container_of(nb, struct ina230_data,
+ nb);
+ struct i2c_client *client = data->client;
+
+ if (event == CPU_ONLINE || event == CPU_DEAD)
+ evaluate_state(client);
+
+ return 0;
+}
+
+
+
+static struct sensor_device_attribute ina230[] = {
+ SENSOR_ATTR(rail_name, S_IRUGO, show_rail_name, NULL, 0),
+ SENSOR_ATTR(current_threshold, S_IWUSR | S_IRUGO,
+ show_current_threshold, set_current_threshold, 0),
+ SENSOR_ATTR(shuntvolt1_input, S_IRUGO, show_shunt_voltage, NULL, 0),
+ SENSOR_ATTR(current1_input, S_IRUGO, show_current, NULL, 0),
+#if MEASURE_BUS_VOLT
+ SENSOR_ATTR(busvolt1_input, S_IRUGO, show_bus_voltage, NULL, 0),
+#endif
+};
+
+
+static int __devinit ina230_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ina230_data *data;
+ int err;
+ u8 i;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct ina230_data),
+ GFP_KERNEL);
+ if (!data) {
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ i2c_set_clientdata(client, data);
+ data->pdata = client->dev.platform_data;
+ data->running = false;
+ data->nb.notifier_call = ina230_hotplug_notify;
+ data->client = client;
+ mutex_init(&data->mutex);
+
+ err = i2c_smbus_write_word_data(client, INA230_CONFIG,
+ __constant_cpu_to_be16(INA230_RESET));
+ if (err < 0) {
+ dev_err(&client->dev, "ina230 reset failure status: 0x%x\n",
+ err);
+ goto exit;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ina230); i++) {
+ err = device_create_file(&client->dev, &ina230[i].dev_attr);
+ if (err) {
+ dev_err(&client->dev, "device_create_file failed.\n");
+ goto exit_remove;
+ }
+ }
+
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ goto exit_remove;
+ }
+
+ register_hotcpu_notifier(&(data->nb));
+
+ evaluate_state(client);
+
+ return 0;
+
+exit_remove:
+ for (i = 0; i < ARRAY_SIZE(ina230); i++)
+ device_remove_file(&client->dev, &ina230[i].dev_attr);
+exit:
+ return err;
+}
+
+
+static int __devexit ina230_remove(struct i2c_client *client)
+{
+ u8 i;
+ struct ina230_data *data = i2c_get_clientdata(client);
+ unregister_hotcpu_notifier(&(data->nb));
+ power_down_ina230(client);
+ hwmon_device_unregister(data->hwmon_dev);
+ for (i = 0; i < ARRAY_SIZE(ina230); i++)
+ device_remove_file(&client->dev, &ina230[i].dev_attr);
+ return 0;
+}
+
+
+static int ina230_suspend(struct i2c_client *client)
+{
+ return power_down_ina230(client);
+}
+
+
+static int ina230_resume(struct i2c_client *client)
+{
+ evaluate_state(client);
+ return 0;
+}
+
+
+static const struct i2c_device_id ina230_id[] = {
+ {DRIVER_NAME, 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ina230_id);
+
+
+static struct i2c_driver ina230_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+ .probe = ina230_probe,
+ .remove = __devexit_p(ina230_remove),
+ .suspend = ina230_suspend,
+ .resume = ina230_resume,
+ .id_table = ina230_id,
+};
+
+
+static int __init ina230_init(void)
+{
+ return i2c_add_driver(&ina230_driver);
+}
+
+
+static void __exit ina230_exit(void)
+{
+ i2c_del_driver(&ina230_driver);
+}
+
+
+module_init(ina230_init);
+module_exit(ina230_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/tegra-tsensor.c b/drivers/hwmon/tegra-tsensor.c
new file mode 100644
index 000000000000..e4792cba4937
--- /dev/null
+++ b/drivers/hwmon/tegra-tsensor.c
@@ -0,0 +1,1991 @@
+/*
+ * NVIDIA Tegra SOC - temperature sensor driver
+ *
+ * Copyright (C) 2011 NVIDIA Corporation
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+
+#include <mach/iomap.h>
+#include <mach/clk.h>
+#include <mach/delay.h>
+#include <mach/tsensor.h>
+#include <mach/tegra_fuse.h>
+
+/* macro to enable tsensor hw reset */
+/* FIXME: till tsensor temperature is reliable this should be 0 */
+#define ENABLE_TSENSOR_HW_RESET 0
+
+/* tsensor instance used for temperature calculation */
+#define TSENSOR_FUSE_REV1 8
+#define TSENSOR_FUSE_REV2 21
+
+/* version where tsensor temperature reading is accurate */
+#define STABLE_TSENSOR_FUSE_REV TSENSOR_FUSE_REV2
+
+/* We have multiple tsensor instances with following registers */
+#define SENSOR_CFG0 0x40
+#define SENSOR_CFG1 0x48
+#define SENSOR_CFG2 0x4c
+#define SENSOR_STATUS0 0x58
+#define SENSOR_TS_STATUS1 0x5c
+#define SENSOR_TS_STATUS2 0x60
+
+/* interrupt mask in tsensor status register */
+#define TSENSOR_SENSOR_X_STATUS0_0_INTR_MASK (1 << 8)
+
+#define SENSOR_CFG0_M_MASK 0xffff
+#define SENSOR_CFG0_M_SHIFT 8
+#define SENSOR_CFG0_N_MASK 0xff
+#define SENSOR_CFG0_N_SHIFT 24
+#define SENSOR_CFG0_RST_INTR_SHIFT 6
+#define SENSOR_CFG0_HW_DIV2_INTR_SHIFT 5
+#define SENSOR_CFG0_OVERFLOW_INTR 4
+#define SENSOR_CFG0_DVFS_INTR_SHIFT 3
+#define SENSOR_CFG0_RST_ENABLE_SHIFT 2
+#define SENSOR_CFG0_HW_DIV2_ENABLE_SHIFT 1
+#define SENSOR_CFG0_STOP_SHIFT 0
+
+#define SENSOR_CFG_X_TH_X_MASK 0xffff
+#define SENSOR_CFG1_TH2_SHIFT 16
+#define SENSOR_CFG1_TH1_SHIFT 0
+#define SENSOR_CFG2_TH3_SHIFT 0
+#define SENSOR_CFG2_TH0_SHIFT 16
+
+#define SENSOR_STATUS_AVG_VALID_SHIFT 10
+#define SENSOR_STATUS_CURR_VALID_SHIFT 9
+
+#define STATE_MASK 0x7
+#define STATUS0_STATE_SHIFT 0
+#define STATUS0_PREV_STATE_SHIFT 4
+
+#define LOCAL_STR_SIZE1 60
+#define MAX_STR_LINE 100
+#define MAX_TSENSOR_LOOP1 (1000 * 2)
+
+#define TSENSOR_COUNTER_TOLERANCE 100
+
+#define SENSOR_CTRL_RST_SHIFT 1
+#define RST_SRC_MASK 0x7
+#define RST_SRC_SENSOR 2
+#define TEGRA_REV_REG_OFFSET 0x804
+#define CCLK_G_BURST_POLICY_REG_REL_OFFSET 0x368
+#define TSENSOR_SLOWDOWN_BIT 23
+
+/* macros used for temperature calculations */
+#define get_temperature_int(X) ((X) / 100)
+#define get_temperature_fraction(X) (((int)(abs(X))) % 100)
+#define get_temperature_round(X) DIV_ROUND_CLOSEST(X, 100)
+
+#define MILLICELSIUS_TO_CELSIUS(i) ((i) / 1000)
+#define CELSIUS_TO_MILLICELSIUS(i) ((i) * 1000)
+
+#define get_ts_state(data) tsensor_get_reg_field(data,\
+ ((data->tsensor_index << 16) | SENSOR_STATUS0), \
+ STATUS0_STATE_SHIFT, STATE_MASK)
+
+/* tsensor states */
+enum ts_state {
+ TS_INVALID = 0,
+ TS_LEVEL0,
+ TS_LEVEL1,
+ TS_LEVEL2,
+ TS_LEVEL3,
+ TS_OVERFLOW,
+ TS_MAX_STATE = TS_OVERFLOW
+};
+
+enum {
+ /* temperature is sensed from 2 points on tegra */
+ TSENSOR_COUNT = 2,
+ TSENSOR_INSTANCE1 = 0,
+ TSENSOR_INSTANCE2 = 1,
+ /* divide by 2 temperature threshold */
+ DIV2_CELSIUS_TEMP_THRESHOLD_DEFAULT = 70,
+ /* reset chip temperature threshold */
+ RESET_CELSIUS_TEMP_THRESHOLD_DEFAULT = 75,
+ /* tsensor frequency in Hz for clk src CLK_M and divisor=24 */
+ DEFAULT_TSENSOR_CLK_HZ = 500000,
+ DEFAULT_TSENSOR_N = 255,
+ DEFAULT_TSENSOR_M = 12500,
+ /* tsensor instance offset */
+ TSENSOR_INSTANCE_OFFSET = 0x40,
+ MIN_THRESHOLD = 0x0,
+ MAX_THRESHOLD = 0xffff,
+ DEFAULT_THRESHOLD_TH0 = MAX_THRESHOLD,
+ DEFAULT_THRESHOLD_TH1 = MAX_THRESHOLD,
+ DEFAULT_THRESHOLD_TH2 = MAX_THRESHOLD,
+ DEFAULT_THRESHOLD_TH3 = MAX_THRESHOLD,
+};
+
+/* constants used to implement sysfs interface */
+enum tsensor_params {
+ TSENSOR_PARAM_TH1 = 0,
+ TSENSOR_PARAM_TH2,
+ TSENSOR_PARAM_TH3,
+ TSENSOR_TEMPERATURE,
+ TSENSOR_STATE,
+ TSENSOR_LIMITS,
+};
+
+enum tsensor_thresholds {
+ TSENSOR_TH0 = 0,
+ TSENSOR_TH1,
+ TSENSOR_TH2,
+ TSENSOR_TH3
+};
+
+/*
+ * For each registered chip, we need to keep some data in memory.
+ * The structure is dynamically allocated.
+ */
+struct tegra_tsensor_data {
+ struct delayed_work work;
+ struct workqueue_struct *workqueue;
+ struct mutex mutex;
+ struct device *hwmon_dev;
+ spinlock_t tsensor_lock;
+ struct clk *dev_clk;
+ /* tsensor register space */
+ void __iomem *base;
+ unsigned long phys;
+ unsigned long phys_end;
+ /* pmc register space */
+ void __iomem *pmc_rst_base;
+ unsigned long pmc_phys;
+ unsigned long pmc_phys_end;
+ /* clk register space */
+ void __iomem *clk_rst_base;
+ int irq;
+ unsigned int int_status[TSENSOR_COUNT];
+
+ /* save configuration before suspend and restore after resume */
+ unsigned int config0[TSENSOR_COUNT];
+ unsigned int config1[TSENSOR_COUNT];
+ unsigned int config2[TSENSOR_COUNT];
+ /* temperature readings from tsensor_index tsensor - 0/1 */
+ unsigned int tsensor_index;
+ int A_e_minus6;
+ int B_e_minus2;
+ unsigned int fuse_T1;
+ unsigned int fuse_F1;
+ unsigned int fuse_T2;
+ unsigned int fuse_F2;
+ /* Quadratic fit coefficients: m=-0.003512 n=1.528943 p=-11.1 */
+ int m_e_minus6;
+ int n_e_minus6;
+ int p_e_minus2;
+
+ long current_hi_limit;
+ long current_lo_limit;
+
+ bool is_edp_supported;
+
+ void (*alert_func)(void *);
+ void *alert_data;
+};
+
+enum {
+ TSENSOR_COEFF_SET1 = 0,
+ TSENSOR_COEFF_SET2,
+ TSENSOR_COEFF_END
+};
+
+struct tegra_tsensor_coeff {
+ int e_minus6_m;
+ int e_minus6_n;
+ int e_minus2_p;
+};
+
+static struct tegra_tsensor_coeff coeff_table[] = {
+ /* Quadratic fit coefficients: m=-0.002775 n=1.338811 p=-7.30 */
+ [TSENSOR_COEFF_SET1] = {
+ -2775,
+ 1338811,
+ -730
+ },
+ /* Quadratic fit coefficients: m=-0.003512 n=1.528943 p=-11.1 */
+ [TSENSOR_COEFF_SET2] = {
+ -3512,
+ 1528943,
+ -1110
+ }
+ /* FIXME: add tsensor coefficients after chip characterization */
+};
+
+/* pTemperature returned in 100 * Celsius */
+static int tsensor_count_2_temp(struct tegra_tsensor_data *data,
+ unsigned int count, int *p_temperature);
+static unsigned int tsensor_get_threshold_counter(
+ struct tegra_tsensor_data *data, int temp);
+
+/* tsensor register access functions */
+
+static void tsensor_writel(struct tegra_tsensor_data *data, u32 val,
+ unsigned long reg)
+{
+ unsigned int reg_offset = reg & 0xffff;
+ unsigned char inst = (reg >> 16) & 0xffff;
+ writel(val, data->base + (inst * TSENSOR_INSTANCE_OFFSET) +
+ reg_offset);
+ return;
+}
+
+static unsigned int tsensor_readl(struct tegra_tsensor_data *data,
+ unsigned long reg)
+{
+ unsigned int reg_offset = reg & 0xffff;
+ unsigned char inst = (reg >> 16) & 0xffff;
+ return readl(data->base +
+ (inst * TSENSOR_INSTANCE_OFFSET) + reg_offset);
+}
+
+static unsigned int tsensor_get_reg_field(
+ struct tegra_tsensor_data *data, unsigned int reg,
+ unsigned int shift, unsigned int mask)
+{
+ unsigned int reg_val;
+ reg_val = tsensor_readl(data, reg);
+ return (reg_val & (mask << shift)) >> shift;
+}
+
+static int tsensor_set_reg_field(
+ struct tegra_tsensor_data *data, unsigned int value,
+ unsigned int reg, unsigned int shift, unsigned int mask)
+{
+ unsigned int reg_val;
+ unsigned int rd_val;
+ reg_val = tsensor_readl(data, reg);
+ reg_val &= ~(mask << shift);
+ reg_val |= ((value & mask) << shift);
+ tsensor_writel(data, reg_val, reg);
+ rd_val = tsensor_readl(data, reg);
+ if (rd_val == reg_val)
+ return 0;
+ else
+ return -EINVAL;
+}
+
+/* enable argument is true to enable reset, false disables pmc reset */
+static void pmc_rst_enable(struct tegra_tsensor_data *data, bool enable)
+{
+ unsigned int val;
+ /* mapped first pmc reg is SENSOR_CTRL */
+ val = readl(data->pmc_rst_base);
+ if (enable)
+ val |= (1 << SENSOR_CTRL_RST_SHIFT);
+ else
+ val &= ~(1 << SENSOR_CTRL_RST_SHIFT);
+ writel(val, data->pmc_rst_base);
+}
+
+/* true returned when pmc reset source is tsensor */
+static bool pmc_check_rst_sensor(struct tegra_tsensor_data *data)
+{
+ unsigned int val;
+ unsigned char src;
+ val = readl(data->pmc_rst_base + 4);
+ src = (unsigned char)(val & RST_SRC_MASK);
+ if (src == RST_SRC_SENSOR)
+ return true;
+ else
+ return false;
+}
+
+/* function to get chip revision */
+static void get_chip_rev(unsigned short *p_id, unsigned short *p_major,
+ unsigned short *p_minor)
+{
+ unsigned int reg;
+
+ reg = readl(IO_TO_VIRT(TEGRA_APB_MISC_BASE) +
+ TEGRA_REV_REG_OFFSET);
+ *p_id = (reg >> 8) & 0xff;
+ *p_major = (reg >> 4) & 0xf;
+ *p_minor = (reg >> 16) & 0xf;
+ pr_info("Tegra chip revision for tsensor detected as: "
+ "Chip Id=%x, Major=%d, Minor=%d\n", (int)*p_id,
+ (int)*p_major, (int)*p_minor);
+}
+
+/*
+ * function to get chip revision specific tsensor coefficients
+ * obtained after chip characterization
+ */
+static void get_chip_tsensor_coeff(struct tegra_tsensor_data *data)
+{
+ unsigned short chip_id, major_rev, minor_rev;
+ unsigned short coeff_index;
+
+ get_chip_rev(&chip_id, &major_rev, &minor_rev);
+ switch (minor_rev) {
+ default:
+ pr_info("Warning: tsensor coefficient for chip pending\n");
+ case 1:
+ coeff_index = TSENSOR_COEFF_SET1;
+ break;
+ }
+ if (data->tsensor_index == TSENSOR_INSTANCE1)
+ coeff_index = TSENSOR_COEFF_SET2;
+ data->m_e_minus6 = coeff_table[coeff_index].e_minus6_m;
+ data->n_e_minus6 = coeff_table[coeff_index].e_minus6_n;
+ data->p_e_minus2 = coeff_table[coeff_index].e_minus2_p;
+}
+
+/* tsensor counter read function */
+static int tsensor_read_counter(
+ struct tegra_tsensor_data *data,
+ unsigned int *p_counter)
+{
+ unsigned int status_reg;
+ unsigned int config0;
+ int iter_count = 0;
+ const int max_loop = 50;
+
+ do {
+ config0 = tsensor_readl(data, ((data->tsensor_index << 16) |
+ SENSOR_CFG0));
+ if (config0 & (1 << SENSOR_CFG0_STOP_SHIFT)) {
+ dev_dbg(data->hwmon_dev, "Error: tsensor "
+ "counter read with STOP bit not supported\n");
+ *p_counter = 0;
+ return 0;
+ }
+
+ status_reg = tsensor_readl(data,
+ (data->tsensor_index << 16) | SENSOR_STATUS0);
+ if (status_reg & (1 <<
+ SENSOR_STATUS_CURR_VALID_SHIFT)) {
+ *p_counter = tsensor_readl(data, (data->tsensor_index
+ << 16) | SENSOR_TS_STATUS1);
+ break;
+ }
+ if (!(iter_count % 10))
+ dev_dbg(data->hwmon_dev, "retry %d\n", iter_count);
+
+ msleep(21);
+ iter_count++;
+ } while (iter_count < max_loop);
+
+ if (iter_count == max_loop)
+ return -ENODEV;
+
+ return 0;
+}
+
+/* tsensor threshold print function */
+static void dump_threshold(struct tegra_tsensor_data *data)
+{
+ unsigned int TH_2_1, TH_0_3;
+ unsigned int curr_avg;
+ int err;
+
+ TH_2_1 = tsensor_readl(data, (data->tsensor_index << 16) | SENSOR_CFG1);
+ TH_0_3 = tsensor_readl(data, (data->tsensor_index << 16) | SENSOR_CFG2);
+ dev_dbg(data->hwmon_dev, "Tsensor: TH_2_1=0x%x, "
+ "TH_0_3=0x%x\n", TH_2_1, TH_0_3);
+ err = tsensor_read_counter(data, &curr_avg);
+ if (err < 0)
+ pr_err("Error: tsensor counter read, "
+ "err=%d\n", err);
+ else
+ dev_dbg(data->hwmon_dev, "Tsensor: "
+ "curr_avg=0x%x\n", curr_avg);
+}
+
+static int tsensor_get_temperature(
+ struct tegra_tsensor_data *data,
+ int *pTemp, unsigned int *pCounter)
+{
+ int err = 0;
+ unsigned int curr_avg;
+
+ err = tsensor_read_counter(data, &curr_avg);
+ if (err < 0)
+ goto error;
+
+ *pCounter = ((curr_avg & 0xFFFF0000) >> 16);
+ err = tsensor_count_2_temp(data, *pCounter, pTemp);
+
+error:
+ return err;
+}
+
+static ssize_t tsensor_show_state(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ int state;
+ struct tegra_tsensor_data *data = dev_get_drvdata(dev);
+
+ state = get_ts_state(data);
+
+ return snprintf(buf, 50, "%d\n", state);
+}
+
+static ssize_t tsensor_show_limits(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct tegra_tsensor_data *data = dev_get_drvdata(dev);
+ return snprintf(buf, 50, "%ld %ld\n",
+ data->current_lo_limit, data->current_hi_limit);
+}
+
+/* tsensor temperature show function */
+static ssize_t tsensor_show_counters(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ unsigned int curr_avg;
+ char err_str[] = "error-sysfs-counter-read\n";
+ char fixed_str[MAX_STR_LINE];
+ struct tegra_tsensor_data *data = dev_get_drvdata(dev);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ int err;
+ int temp;
+
+ if (attr->index == TSENSOR_TEMPERATURE) {
+ /* use current counter value to calculate temperature */
+ err = tsensor_read_counter(data, &curr_avg);
+ if (err < 0)
+ goto error;
+ err = tsensor_count_2_temp(data,
+ ((curr_avg & 0xFFFF0000) >> 16), &temp);
+ if (err < 0)
+ goto error;
+
+ dev_vdbg(data->hwmon_dev, "%s has curr_avg=0x%x, "
+ "temp0=%d\n", __func__, curr_avg, temp);
+
+ snprintf(buf, (((LOCAL_STR_SIZE1 << 1) + 3) +
+ strlen(fixed_str)),
+ "%d.%02dC\n",
+ get_temperature_int(temp),
+ get_temperature_fraction(temp));
+ }
+ return strlen(buf);
+error:
+ return snprintf(buf, strlen(err_str), "%s", err_str);
+}
+
+/* utility function to check hw clock divide by 2 condition */
+static bool cclkg_check_hwdiv2_sensor(struct tegra_tsensor_data *data)
+{
+ unsigned int val;
+ val = readl(IO_ADDRESS(TEGRA_CLK_RESET_BASE +
+ CCLK_G_BURST_POLICY_REG_REL_OFFSET));
+ if ((1 << TSENSOR_SLOWDOWN_BIT) & val) {
+ dev_err(data->hwmon_dev, "Warning: ***** tsensor "
+ "slowdown bit detected\n");
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/*
+ * function with table to return register, field shift and mask
+ * values for supported parameters
+ */
+static int get_param_values(
+ struct tegra_tsensor_data *data, unsigned int indx,
+ unsigned int *p_reg, unsigned int *p_sft, unsigned int *p_msk,
+ char *info, size_t info_len)
+{
+ switch (indx) {
+ case TSENSOR_PARAM_TH1:
+ *p_reg = ((data->tsensor_index << 16) | SENSOR_CFG1);
+ *p_sft = SENSOR_CFG1_TH1_SHIFT;
+ *p_msk = SENSOR_CFG_X_TH_X_MASK;
+ snprintf(info, info_len, "TH1[%d]: ",
+ data->tsensor_index);
+ break;
+ case TSENSOR_PARAM_TH2:
+ *p_reg = ((data->tsensor_index << 16) | SENSOR_CFG1);
+ *p_sft = SENSOR_CFG1_TH2_SHIFT;
+ *p_msk = SENSOR_CFG_X_TH_X_MASK;
+ snprintf(info, info_len, "TH2[%d]: ",
+ data->tsensor_index);
+ break;
+ case TSENSOR_PARAM_TH3:
+ *p_reg = ((data->tsensor_index << 16) | SENSOR_CFG2);
+ *p_sft = SENSOR_CFG2_TH3_SHIFT;
+ *p_msk = SENSOR_CFG_X_TH_X_MASK;
+ snprintf(info, info_len, "TH3[%d]: ",
+ data->tsensor_index);
+ break;
+ default:
+ return -ENOENT;
+ }
+ return 0;
+}
+
+/* tsensor driver sysfs show function */
+static ssize_t show_tsensor_param(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ unsigned int val;
+ struct tegra_tsensor_data *data = dev_get_drvdata(dev);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ unsigned int reg;
+ unsigned int sft;
+ unsigned int msk;
+ int err;
+ int temp;
+ char info[LOCAL_STR_SIZE1];
+
+ err = get_param_values(data, attr->index, &reg, &sft, &msk,
+ info, sizeof(info));
+ if (err < 0)
+ goto labelErr;
+ val = tsensor_get_reg_field(data, reg, sft, msk);
+ if (val == MAX_THRESHOLD)
+ snprintf(buf, PAGE_SIZE, "%s un-initialized threshold\n", info);
+ else {
+ err = tsensor_count_2_temp(data, val, &temp);
+ if (err != 0)
+ goto labelErr;
+ snprintf(buf, PAGE_SIZE, "%s threshold: %d.%d Celsius\n", info,
+ get_temperature_int(temp),
+ get_temperature_fraction(temp));
+ }
+ return strlen(buf);
+
+labelErr:
+ snprintf(buf, PAGE_SIZE, "ERROR:");
+ return strlen(buf);
+}
+
+/* tsensor driver sysfs store function */
+static ssize_t set_tsensor_param(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ int num;
+ struct tegra_tsensor_data *data = dev_get_drvdata(dev);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ unsigned int reg;
+ unsigned int sft;
+ unsigned int msk;
+ int err;
+ unsigned int counter;
+ unsigned int val;
+ char info[LOCAL_STR_SIZE1];
+
+ if (kstrtoint(buf, 0, &num)) {
+ dev_err(dev, "file: %s, line=%d return %s()\n",
+ __FILE__, __LINE__, __func__);
+ return -EINVAL;
+ }
+
+ counter = tsensor_get_threshold_counter(data, num);
+
+ err = get_param_values(data, attr->index, &reg, &sft, &msk,
+ info, sizeof(info));
+ if (err < 0)
+ goto labelErr;
+
+ err = tsensor_set_reg_field(data, counter, reg, sft, msk);
+ if (err < 0)
+ goto labelErr;
+
+ /* TH2 clk divide check */
+ if (attr->index == TSENSOR_PARAM_TH2) {
+ msleep(21);
+ (void)cclkg_check_hwdiv2_sensor(data);
+ }
+ val = tsensor_get_reg_field(data, reg, sft, msk);
+ dev_dbg(dev, "%s 0x%x\n", info, val);
+ return count;
+labelErr:
+ dev_err(dev, "file: %s, line=%d, %s(), error=0x%x\n", __FILE__,
+ __LINE__, __func__, err);
+ return 0;
+}
+
+static struct sensor_device_attribute tsensor_nodes[] = {
+ SENSOR_ATTR(tsensor_TH1, S_IRUGO | S_IWUSR,
+ show_tsensor_param, set_tsensor_param, TSENSOR_PARAM_TH1),
+ SENSOR_ATTR(tsensor_TH2, S_IRUGO | S_IWUSR,
+ show_tsensor_param, set_tsensor_param, TSENSOR_PARAM_TH2),
+ SENSOR_ATTR(tsensor_TH3, S_IRUGO | S_IWUSR,
+ show_tsensor_param, set_tsensor_param, TSENSOR_PARAM_TH3),
+ SENSOR_ATTR(tsensor_temperature, S_IRUGO | S_IWUSR,
+ tsensor_show_counters, NULL, TSENSOR_TEMPERATURE),
+ SENSOR_ATTR(tsensor_state, S_IRUGO | S_IWUSR,
+ tsensor_show_state, NULL, TSENSOR_STATE),
+ SENSOR_ATTR(tsensor_limits, S_IRUGO | S_IWUSR,
+ tsensor_show_limits, NULL, TSENSOR_LIMITS),
+};
+
+int tsensor_thermal_get_temp(struct tegra_tsensor_data *data,
+ long *milli_temp)
+{
+ int counter, temp, err;
+ int temp_state, ts_state;
+
+ err = tsensor_get_temperature(data,
+ &temp,
+ &counter);
+ if (err)
+ return err;
+
+ temp *= 10;
+
+ mutex_lock(&data->mutex);
+
+ /* This section of logic is done in order to make sure that
+ * the temperature read corresponds to the current hw state.
+ * If it is not, return the nearest temperature
+ */
+ if ((data->current_lo_limit != 0) ||
+ (data->current_hi_limit)) {
+
+ if (temp <= data->current_lo_limit)
+ temp_state = TS_LEVEL0;
+ else if (temp < data->current_hi_limit)
+ temp_state = TS_LEVEL1;
+ else
+ temp_state = TS_LEVEL2;
+
+ ts_state = get_ts_state(data);
+
+ if (ts_state != temp_state) {
+
+ switch (ts_state) {
+ case TS_LEVEL0:
+ temp = data->current_lo_limit - 1;
+ break;
+ case TS_LEVEL1:
+ if (temp_state == TS_LEVEL0)
+ temp = data->current_lo_limit + 1;
+ else
+ temp = data->current_hi_limit - 1;
+ break;
+ case TS_LEVEL2:
+ temp = data->current_hi_limit + 1;
+ break;
+ }
+
+ }
+
+ }
+
+ mutex_unlock(&data->mutex);
+
+ *milli_temp = temp;
+
+ return 0;
+}
+
+/* tsensor driver interrupt handler */
+static irqreturn_t tegra_tsensor_isr(int irq, void *arg_data)
+{
+ struct tegra_tsensor_data *data =
+ (struct tegra_tsensor_data *)arg_data;
+ unsigned long flags;
+ unsigned int val;
+ int new_state;
+
+ spin_lock_irqsave(&data->tsensor_lock, flags);
+
+ val = tsensor_readl(data, (data->tsensor_index << 16) | SENSOR_STATUS0);
+ if (val & TSENSOR_SENSOR_X_STATUS0_0_INTR_MASK) {
+ new_state = get_ts_state(data);
+
+ /* counter overflow check */
+ if (new_state == TS_OVERFLOW)
+ dev_err(data->hwmon_dev, "Warning: "
+ "***** OVERFLOW tsensor\n");
+
+ /* We only care if we go above hi or below low thresholds */
+ if (data->is_edp_supported && new_state != TS_LEVEL1)
+ queue_delayed_work(data->workqueue, &data->work, 0);
+ }
+
+ tsensor_writel(data, val, (data->tsensor_index << 16) | SENSOR_STATUS0);
+
+ spin_unlock_irqrestore(&data->tsensor_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * function to read fuse registers and give - T1, T2, F1 and F2
+ */
+static int read_tsensor_fuse_regs(struct tegra_tsensor_data *data)
+{
+ unsigned int reg1;
+ unsigned int T1 = 0, T2 = 0;
+ unsigned int spare_bits;
+ int err;
+
+ /* read tsensor calibration register */
+ /*
+ * High (~90 DegC) Temperature Calibration value (upper 16 bits of
+ * FUSE_TSENSOR_CALIB_0) - F2
+ * Low (~25 deg C) Temperature Calibration value (lower 16 bits of
+ * FUSE_TSENSOR_CALIB_0) - F1
+ */
+ err = tegra_fuse_get_tsensor_calibration_data(&reg1);
+ if (err)
+ goto errLabel;
+ data->fuse_F1 = reg1 & 0xFFFF;
+ data->fuse_F2 = (reg1 >> 16) & 0xFFFF;
+
+ err = tegra_fuse_get_tsensor_spare_bits(&spare_bits);
+ if (err) {
+ pr_err("tsensor spare bit fuse read error=%d\n", err);
+ goto errLabel;
+ }
+
+ /*
+ * FUSE_TJ_ADT_LOWT = T1, FUSE_TJ_ADJ = T2
+ */
+
+ /*
+ * Low temp is:
+ * FUSE_TJ_ADT_LOWT = bits [20:14] or’ed with bits [27:21]
+ */
+ T1 = ((spare_bits >> 14) & 0x7F) |
+ ((spare_bits >> 21) & 0x7F);
+ dev_vdbg(data->hwmon_dev, "Tsensor low temp (T1) fuse :\n");
+
+ /*
+ * High temp is:
+ * FUSE_TJ_ADJ = bits [6:0] or’ed with bits [13:7]
+ */
+ dev_vdbg(data->hwmon_dev, "Tsensor low temp (T2) fuse :\n");
+ T2 = (spare_bits & 0x7F) | ((spare_bits >> 7) & 0x7F);
+ pr_info("Tsensor fuse calibration F1=%d, F2=%d, T1=%d, T2=%d\n"
+ , data->fuse_F1, data->fuse_F2, T1, T2);
+ data->fuse_T1 = T1;
+ data->fuse_T2 = T2;
+ return 0;
+errLabel:
+ return err;
+}
+
+/* function to calculate interim temperature */
+static int calc_interim_temp(struct tegra_tsensor_data *data,
+ unsigned int counter, int *p_interim_temp)
+{
+ int val1;
+ /*
+ * T-int = A * Counter + B
+ * (Counter is the sensor frequency output)
+ */
+ if ((data->fuse_F2 - data->fuse_F1) <= (data->fuse_T2 -
+ data->fuse_T1)) {
+ dev_err(data->hwmon_dev, "Error: F2=%d, F1=%d "
+ "difference unexpectedly low. "
+ "Aborting temperature processing\n", data->fuse_F2,
+ data->fuse_F1);
+ return -EINVAL;
+ } else {
+ /* expression modified after assuming s_A is 10^6 times,
+ * s_B is 10^2 times and want end result to be 10^2 times
+ * actual value
+ */
+ val1 = DIV_ROUND_CLOSEST((data->A_e_minus6 * counter) , 10000);
+ dev_vdbg(data->hwmon_dev, "A*counter / 100 = %d\n",
+ val1);
+ *p_interim_temp = (val1 + data->B_e_minus2);
+ }
+ dev_dbg(data->hwmon_dev, "tsensor: counter=0x%x, interim "
+ "temp*100=%d\n",
+ counter, *p_interim_temp);
+ return 0;
+}
+
+/*
+ * function to calculate final temperature, given
+ * interim temperature
+ */
+static void calc_final_temp(struct tegra_tsensor_data *data,
+ int interim_temp, int *p_final_temp)
+{
+ int temp1, temp2, temp;
+ /*
+ * T-final = m * T-int ^2 + n * T-int + p
+ * m = -0.002775
+ * n = 1.338811
+ * p = -7.3
+ */
+
+ dev_vdbg(data->hwmon_dev, "interim_temp=%d\n", interim_temp);
+ temp1 = (DIV_ROUND_CLOSEST((interim_temp * interim_temp) , 100));
+ dev_vdbg(data->hwmon_dev, "temp1=%d\n", temp1);
+ temp1 *= (DIV_ROUND_CLOSEST(data->m_e_minus6 , 10));
+ dev_vdbg(data->hwmon_dev, "m*T-int^2=%d\n", temp1);
+ temp1 = (DIV_ROUND_CLOSEST(temp1, 10000));
+ /* we want to keep 3 decimal point digits */
+ dev_vdbg(data->hwmon_dev, "m*T-int^2 / 10000=%d\n", temp1);
+ dev_dbg(data->hwmon_dev, "temp1*100=%d\n", temp1);
+
+ temp2 = (DIV_ROUND_CLOSEST(interim_temp * (
+ DIV_ROUND_CLOSEST(data->n_e_minus6, 100)
+ ), 1000)); /* 1000 times actual */
+ dev_vdbg(data->hwmon_dev, "n*T-int =%d\n", temp2);
+
+ temp = temp1 + temp2;
+ dev_vdbg(data->hwmon_dev, "m*T-int^2 + n*T-int =%d\n", temp);
+ temp += (data->p_e_minus2 * 10);
+ temp = DIV_ROUND_CLOSEST(temp, 10);
+ /* final temperature(temp) is 100 times actual value
+ * to preserve 2 decimal digits and enable fixed point
+ * computation
+ */
+ dev_vdbg(data->hwmon_dev, "m*T-int^2 + n*T-int + p =%d\n",
+ temp);
+ dev_dbg(data->hwmon_dev, "Final temp=%d.%d\n",
+ get_temperature_int(temp), get_temperature_fraction(temp));
+ *p_final_temp = (int)(temp);
+}
+
+/*
+ * Function to compute constants A and B needed for temperature
+ * calculation
+ * A = (T2-T1) / (F2-F1)
+ * B = T1 – A * F1
+ */
+static int tsensor_get_const_AB(struct tegra_tsensor_data *data)
+{
+ int err;
+
+ /*
+ * 1. Find fusing registers for 25C (T1, F1) and 90C (T2, F2);
+ */
+ err = read_tsensor_fuse_regs(data);
+ if (err) {
+ dev_err(data->hwmon_dev, "Fuse register read required "
+ "for internal tsensor returns err=%d\n", err);
+ return err;
+ }
+
+ if (data->fuse_F2 != data->fuse_F1) {
+ if ((data->fuse_F2 - data->fuse_F1) <= (data->fuse_T2 -
+ data->fuse_T1)) {
+ dev_err(data->hwmon_dev, "Error: F2=%d, "
+ "F1=%d, difference"
+ " unexpectedly low. Aborting temperature"
+ "computation\n", data->fuse_F2, data->fuse_F1);
+ return -EINVAL;
+ } else {
+ data->A_e_minus6 = ((data->fuse_T2 - data->fuse_T1) *
+ 1000000);
+ data->A_e_minus6 /= (data->fuse_F2 - data->fuse_F1);
+ data->B_e_minus2 = (data->fuse_T1 * 100) - (
+ DIV_ROUND_CLOSEST((data->A_e_minus6 *
+ data->fuse_F1), 10000));
+ /* B is 100 times now */
+ }
+ }
+ dev_dbg(data->hwmon_dev, "A_e_minus6 = %d\n", data->A_e_minus6);
+ dev_dbg(data->hwmon_dev, "B_e_minus2 = %d\n", data->B_e_minus2);
+ return 0;
+}
+
+/*
+ * function calculates expected temperature corresponding to
+ * given tsensor counter value
+ * Value returned is 100 times calculated temperature since the
+ * calculations are using fixed point arithmetic instead of floating point
+ */
+static int tsensor_count_2_temp(struct tegra_tsensor_data *data,
+ unsigned int count, int *p_temperature)
+{
+ int interim_temp;
+ int err;
+
+ /*
+ *
+ * 2. Calculate interim temperature:
+ */
+ err = calc_interim_temp(data, count, &interim_temp);
+ if (err < 0) {
+ dev_err(data->hwmon_dev, "tsensor: cannot read temperature\n");
+ *p_temperature = -1;
+ return err;
+ }
+
+ /*
+ *
+ * 3. Calculate final temperature:
+ */
+ calc_final_temp(data, interim_temp, p_temperature);
+ return 0;
+}
+
+/*
+ * utility function implements ceil to power of 10 -
+ * e.g. given 987 it returns 1000
+ */
+static int my_ceil_pow10(int num)
+{
+ int tmp;
+ int val = 1;
+ tmp = (num < 0) ? -num : num;
+ if (tmp == 0)
+ return 0;
+ while (tmp > 1) {
+ val *= 10;
+ tmp /= 10;
+ }
+ return val;
+}
+
+/*
+ * function to solve quadratic roots of equation
+ * used to get counter corresponding to given temperature
+ */
+static void get_quadratic_roots(struct tegra_tsensor_data *data,
+ int temp, unsigned int *p_counter1,
+ unsigned int *p_counter2)
+{
+ /* expr1 = 2 * m * B + n */
+ int expr1_e_minus6;
+ /* expr2 = expr1^2 */
+ int expr2_e_minus6;
+ /* expr3 = m * B^2 + n * B + p */
+ int expr3_e_minus4_1;
+ int expr3_e_minus4_2;
+ int expr3_e_minus4;
+ int expr4_e_minus6;
+ int expr4_e_minus2_1;
+ int expr4_e_minus6_2;
+ int expr4_e_minus6_3;
+ int expr5_e_minus6, expr5_e_minus6_1, expr6, expr7;
+ int expr8_e_minus6, expr9_e_minus6;
+ int multiplier;
+ const int multiplier2 = 1000000;
+ int expr10_e_minus6, expr11_e_minus6;
+ int expr12, expr13;
+
+ dev_vdbg(data->hwmon_dev, "A_e_minus6=%d, B_e_minus2=%d, "
+ "m_e_minus6=%d, n_e_minus6=%d, p_e_minus2=%d, "
+ "temp=%d\n", data->A_e_minus6, data->B_e_minus2,
+ data->m_e_minus6,
+ data->n_e_minus6, data->p_e_minus2, (int)temp);
+ expr1_e_minus6 = (DIV_ROUND_CLOSEST((2 * data->m_e_minus6 *
+ data->B_e_minus2), 100) + data->n_e_minus6);
+ dev_vdbg(data->hwmon_dev, "2_m_B_plun_e_minus6=%d\n",
+ expr1_e_minus6);
+ expr2_e_minus6 = (DIV_ROUND_CLOSEST(expr1_e_minus6, 1000)) *
+ (DIV_ROUND_CLOSEST(expr1_e_minus6, 1000));
+ dev_vdbg(data->hwmon_dev, "expr1^2=%d\n", expr2_e_minus6);
+ expr3_e_minus4_1 = (DIV_ROUND_CLOSEST((
+ (DIV_ROUND_CLOSEST((data->m_e_minus6 * data->B_e_minus2),
+ 1000)) * (DIV_ROUND_CLOSEST(data->B_e_minus2, 10))), 100));
+ dev_vdbg(data->hwmon_dev, "expr3_e_minus4_1=%d\n",
+ expr3_e_minus4_1);
+ expr3_e_minus4_2 = DIV_ROUND_CLOSEST(
+ (DIV_ROUND_CLOSEST(data->n_e_minus6, 100) * data->B_e_minus2),
+ 100);
+ dev_vdbg(data->hwmon_dev, "expr3_e_minus4_2=%d\n",
+ expr3_e_minus4_2);
+ expr3_e_minus4 = expr3_e_minus4_1 + expr3_e_minus4_2;
+ dev_vdbg(data->hwmon_dev, "expr3=%d\n", expr3_e_minus4);
+ expr4_e_minus2_1 = DIV_ROUND_CLOSEST((expr3_e_minus4 +
+ (data->p_e_minus2 * 100)), 100);
+ dev_vdbg(data->hwmon_dev, "expr4_e_minus2_1=%d\n",
+ expr4_e_minus2_1);
+ expr4_e_minus6_2 = (4 * data->m_e_minus6);
+ dev_vdbg(data->hwmon_dev, "expr4_e_minus6_2=%d\n",
+ expr4_e_minus6_2);
+ expr4_e_minus6 = DIV_ROUND_CLOSEST((expr4_e_minus2_1 *
+ expr4_e_minus6_2), 100);
+ dev_vdbg(data->hwmon_dev, "expr4_minus6=%d\n", expr4_e_minus6);
+ expr5_e_minus6_1 = expr2_e_minus6 - expr4_e_minus6;
+ dev_vdbg(data->hwmon_dev, "expr5_e_minus6_1=%d\n",
+ expr5_e_minus6_1);
+ expr4_e_minus6_3 = (expr4_e_minus6_2 * temp);
+ dev_vdbg(data->hwmon_dev, "expr4_e_minus6_3=%d\n",
+ expr4_e_minus6_3);
+ expr5_e_minus6 = (expr5_e_minus6_1 + expr4_e_minus6_3);
+ dev_vdbg(data->hwmon_dev, "expr5_e_minus6=%d\n",
+ expr5_e_minus6);
+ multiplier = my_ceil_pow10(expr5_e_minus6);
+ dev_vdbg(data->hwmon_dev, "multiplier=%d\n", multiplier);
+ expr6 = int_sqrt(expr5_e_minus6);
+ dev_vdbg(data->hwmon_dev, "sqrt top=%d\n", expr6);
+ expr7 = int_sqrt(multiplier);
+ dev_vdbg(data->hwmon_dev, "sqrt bot=%d\n", expr7);
+ if (expr7 == 0) {
+ pr_err("Error: %s line=%d, expr7=%d\n",
+ __func__, __LINE__, expr7);
+ return;
+ } else {
+ expr8_e_minus6 = (expr6 * multiplier2) / expr7;
+ }
+ dev_vdbg(data->hwmon_dev, "sqrt final=%d\n", expr8_e_minus6);
+ dev_vdbg(data->hwmon_dev, "2_m_B_plus_n_e_minus6=%d\n",
+ expr1_e_minus6);
+ expr9_e_minus6 = DIV_ROUND_CLOSEST((2 * data->m_e_minus6 *
+ data->A_e_minus6), 1000000);
+ dev_vdbg(data->hwmon_dev, "denominator=%d\n", expr9_e_minus6);
+ if (expr9_e_minus6 == 0) {
+ pr_err("Error: %s line=%d, expr9_e_minus6=%d\n",
+ __func__, __LINE__, expr9_e_minus6);
+ return;
+ }
+ expr10_e_minus6 = -expr1_e_minus6 - expr8_e_minus6;
+ dev_vdbg(data->hwmon_dev, "expr10_e_minus6=%d\n",
+ expr10_e_minus6);
+ expr11_e_minus6 = -expr1_e_minus6 + expr8_e_minus6;
+ dev_vdbg(data->hwmon_dev, "expr11_e_minus6=%d\n",
+ expr11_e_minus6);
+ expr12 = (expr10_e_minus6 / expr9_e_minus6);
+ dev_vdbg(data->hwmon_dev, "counter1=%d\n", expr12);
+ expr13 = (expr11_e_minus6 / expr9_e_minus6);
+ dev_vdbg(data->hwmon_dev, "counter2=%d\n", expr13);
+ *p_counter1 = expr12;
+ *p_counter2 = expr13;
+}
+
+/*
+ * function returns tsensor expected counter corresponding to input
+ * temperature in degree Celsius.
+ * e.g. for temperature of 35C, temp=35
+ */
+static void tsensor_temp_2_count(struct tegra_tsensor_data *data,
+ int temp,
+ unsigned int *p_counter1,
+ unsigned int *p_counter2)
+{
+ if (temp > 0) {
+ dev_dbg(data->hwmon_dev, "Trying to calculate counter"
+ " for requested temperature"
+ " threshold=%d\n", temp);
+ /*
+ * calculate the constants needed to get roots of
+ * following quadratic eqn:
+ * m * A^2 * Counter^2 +
+ * A * (2 * m * B + n) * Counter +
+ * (m * B^2 + n * B + p - Temperature) = 0
+ */
+ get_quadratic_roots(data, temp, p_counter1, p_counter2);
+ /*
+ * checked at current temperature=35 the counter=11418
+ * for 50 deg temperature: counter1=22731, counter2=11817
+ * at 35 deg temperature: counter1=23137, counter2=11411
+ * hence, for above values we are assuming counter2 has
+ * the correct value
+ */
+ } else {
+ *p_counter1 = DEFAULT_THRESHOLD_TH3;
+ *p_counter2 = DEFAULT_THRESHOLD_TH3;
+ }
+}
+
+/*
+ * function to compare computed and expected values with
+ * certain tolerance setting hard coded here
+ */
+static bool cmp_counter(
+ struct tegra_tsensor_data *data,
+ unsigned int actual, unsigned int exp)
+{
+ unsigned int smaller;
+ unsigned int larger;
+ smaller = (actual > exp) ? exp : actual;
+ larger = (smaller == actual) ? exp : actual;
+ if ((larger - smaller) > TSENSOR_COUNTER_TOLERANCE) {
+ dev_dbg(data->hwmon_dev, "actual=%d, exp=%d, larger=%d, "
+ "smaller=%d, tolerance=%d\n", actual, exp, larger, smaller,
+ TSENSOR_COUNTER_TOLERANCE);
+ return false;
+ }
+ return true;
+}
+
+/* function to print chart of temperature to counter values */
+static void print_temperature_2_counter_table(
+ struct tegra_tsensor_data *data)
+{
+ int i;
+ /* static list of temperature tested */
+ int temp_list[] = {
+ 30,
+ 35,
+ 40,
+ 45,
+ 50,
+ 55,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 70,
+ 75,
+ 80,
+ 85,
+ 90,
+ 95,
+ 100,
+ 105,
+ 110,
+ 115,
+ 120
+ };
+ unsigned int counter1, counter2;
+ dev_dbg(data->hwmon_dev, "Temperature and counter1 and "
+ "counter2 chart **********\n");
+ for (i = 0; i < ARRAY_SIZE(temp_list); i++) {
+ tsensor_temp_2_count(data, temp_list[i],
+ &counter1, &counter2);
+ dev_dbg(data->hwmon_dev, "temperature[%d]=%d, "
+ "counter1=0x%x, counter2=0x%x\n",
+ i, temp_list[i], counter1, counter2);
+ }
+ dev_dbg(data->hwmon_dev, "\n\n");
+}
+
+static void dump_a_tsensor_reg(struct tegra_tsensor_data *data,
+ unsigned int addr)
+{
+ dev_dbg(data->hwmon_dev, "tsensor[%d][0x%x]: 0x%x\n", (addr >> 16),
+ addr & 0xFFFF, tsensor_readl(data, addr));
+}
+
+static void dump_tsensor_regs(struct tegra_tsensor_data *data)
+{
+ int i;
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ /* if STOP bit is set skip this check */
+ dump_a_tsensor_reg(data, ((i << 16) | SENSOR_CFG0));
+ dump_a_tsensor_reg(data, ((i << 16) | SENSOR_CFG1));
+ dump_a_tsensor_reg(data, ((i << 16) | SENSOR_CFG2));
+ dump_a_tsensor_reg(data, ((i << 16) | SENSOR_STATUS0));
+ dump_a_tsensor_reg(data, ((i << 16) | SENSOR_TS_STATUS1));
+ dump_a_tsensor_reg(data, ((i << 16) | SENSOR_TS_STATUS2));
+ dump_a_tsensor_reg(data, ((i << 16) | 0x0));
+ dump_a_tsensor_reg(data, ((i << 16) | 0x44));
+ dump_a_tsensor_reg(data, ((i << 16) | 0x50));
+ dump_a_tsensor_reg(data, ((i << 16) | 0x54));
+ dump_a_tsensor_reg(data, ((i << 16) | 0x64));
+ dump_a_tsensor_reg(data, ((i << 16) | 0x68));
+ }
+}
+
+/*
+ * function to test if conversion of counter to temperature
+ * and vice-versa is working
+ */
+static int test_temperature_algo(struct tegra_tsensor_data *data)
+{
+ unsigned int actual_counter;
+ unsigned int curr_avg;
+ unsigned int counter1, counter2;
+ int T1;
+ int err = 0;
+ bool result1, result2;
+ bool result = false;
+
+ /* read actual counter */
+ err = tsensor_read_counter(data, &curr_avg);
+ if (err < 0) {
+ pr_err("Error: tsensor0 counter read, err=%d\n", err);
+ goto endLabel;
+ }
+ actual_counter = ((curr_avg & 0xFFFF0000) >> 16);
+ dev_dbg(data->hwmon_dev, "counter read=0x%x\n", actual_counter);
+
+ /* calculate temperature */
+ err = tsensor_count_2_temp(data, actual_counter, &T1);
+ dev_dbg(data->hwmon_dev, "%s actual counter=0x%x, calculated "
+ "temperature=%d.%d\n", __func__,
+ actual_counter, get_temperature_int(T1),
+ get_temperature_fraction(T1));
+ if (err < 0) {
+ pr_err("Error: calculate temperature step\n");
+ goto endLabel;
+ }
+
+ /* calculate counter corresponding to read temperature */
+ tsensor_temp_2_count(data, get_temperature_round(T1),
+ &counter1, &counter2);
+ dev_dbg(data->hwmon_dev, "given temperature=%d, counter1=0x%x,"
+ " counter2=0x%x\n",
+ get_temperature_round(T1), counter1, counter2);
+
+ err = tsensor_count_2_temp(data, actual_counter, &T1);
+ dev_dbg(data->hwmon_dev, "%s 2nd time actual counter=0x%x, "
+ "calculated temperature=%d.%d\n", __func__,
+ actual_counter, get_temperature_int(T1),
+ get_temperature_fraction(T1));
+ if (err < 0) {
+ pr_err("Error: calculate temperature step\n");
+ goto endLabel;
+ }
+
+ /* compare counter calculated with actual original counter */
+ result1 = cmp_counter(data, actual_counter, counter1);
+ result2 = cmp_counter(data, actual_counter, counter2);
+ if (result1) {
+ dev_dbg(data->hwmon_dev, "counter1 matches: actual=%d,"
+ " calc=%d\n", actual_counter, counter1);
+ result = true;
+ }
+ if (result2) {
+ dev_dbg(data->hwmon_dev, "counter2 matches: actual=%d,"
+ " calc=%d\n", actual_counter, counter2);
+ result = true;
+ }
+ if (!result) {
+ pr_info("NO Match: actual=%d,"
+ " calc counter2=%d, counter1=%d\n", actual_counter,
+ counter2, counter1);
+ err = -EIO;
+ }
+
+endLabel:
+ return err;
+}
+
+/* tsensor threshold temperature to threshold counter conversion function */
+static unsigned int tsensor_get_threshold_counter(
+ struct tegra_tsensor_data *data,
+ int temp_threshold)
+{
+ unsigned int counter1, counter2;
+ unsigned int counter;
+
+ if (temp_threshold < 0)
+ return MAX_THRESHOLD;
+
+ tsensor_temp_2_count(data, temp_threshold, &counter1, &counter2);
+
+ counter = counter2;
+
+ return counter;
+}
+
+/* tsensor temperature threshold setup function */
+static void tsensor_threshold_setup(struct tegra_tsensor_data *data,
+ unsigned char index)
+{
+ unsigned long config0;
+ unsigned char i = index;
+ unsigned int th2_count = DEFAULT_THRESHOLD_TH2;
+ unsigned int th3_count = DEFAULT_THRESHOLD_TH3;
+ unsigned int th1_count = DEFAULT_THRESHOLD_TH1;
+ int th0_diff = 0;
+
+ dev_dbg(data->hwmon_dev, "started tsensor_threshold_setup %d\n",
+ index);
+ config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
+
+ dev_dbg(data->hwmon_dev, "before threshold program TH dump:\n");
+ dump_threshold(data);
+ dev_dbg(data->hwmon_dev, "th3=0x%x, th2=0x%x, th1=0x%x, th0=0x%x\n",
+ th3_count, th2_count, th1_count, th0_diff);
+ config0 = (((th2_count & SENSOR_CFG_X_TH_X_MASK)
+ << SENSOR_CFG1_TH2_SHIFT) |
+ ((th1_count & SENSOR_CFG_X_TH_X_MASK) <<
+ SENSOR_CFG1_TH1_SHIFT));
+ tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG1));
+ config0 = (((th0_diff & SENSOR_CFG_X_TH_X_MASK)
+ << SENSOR_CFG2_TH0_SHIFT) |
+ ((th3_count & SENSOR_CFG_X_TH_X_MASK) <<
+ SENSOR_CFG2_TH3_SHIFT));
+ tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG2));
+ dev_dbg(data->hwmon_dev, "after threshold program TH dump:\n");
+ dump_threshold(data);
+}
+
+/* tsensor config programming function */
+static int tsensor_config_setup(struct tegra_tsensor_data *data)
+{
+ unsigned int config0;
+ unsigned int i;
+ unsigned int status_reg;
+ unsigned int no_resp_count;
+ int err = 0;
+
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ /*
+ * Pre-read setup:
+ * Set M and N values
+ * Enable HW features HW_FREQ_DIV_EN, THERMAL_RST_EN
+ */
+ config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
+ config0 &= ~((SENSOR_CFG0_M_MASK << SENSOR_CFG0_M_SHIFT) |
+ (SENSOR_CFG0_N_MASK << SENSOR_CFG0_N_SHIFT) |
+ (1 << SENSOR_CFG0_OVERFLOW_INTR) |
+ (1 << SENSOR_CFG0_RST_INTR_SHIFT) |
+ (1 << SENSOR_CFG0_DVFS_INTR_SHIFT) |
+ (1 << SENSOR_CFG0_HW_DIV2_INTR_SHIFT) |
+ (1 << SENSOR_CFG0_RST_ENABLE_SHIFT) |
+ (1 << SENSOR_CFG0_HW_DIV2_ENABLE_SHIFT)
+ );
+ /* Set STOP bit */
+ /* Set M and N values */
+ /* Enable HW features HW_FREQ_DIV_EN, THERMAL_RST_EN */
+ config0 |= (((DEFAULT_TSENSOR_M & SENSOR_CFG0_M_MASK) <<
+ SENSOR_CFG0_M_SHIFT) |
+ ((DEFAULT_TSENSOR_N & SENSOR_CFG0_N_MASK) <<
+ SENSOR_CFG0_N_SHIFT) |
+ (1 << SENSOR_CFG0_OVERFLOW_INTR) |
+ (1 << SENSOR_CFG0_DVFS_INTR_SHIFT) |
+ (1 << SENSOR_CFG0_HW_DIV2_INTR_SHIFT) |
+#if ENABLE_TSENSOR_HW_RESET
+ (1 << SENSOR_CFG0_RST_ENABLE_SHIFT) |
+#endif
+ (1 << SENSOR_CFG0_STOP_SHIFT));
+
+ tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG0));
+ tsensor_threshold_setup(data, i);
+ }
+
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
+ /* Enables interrupts and clears sensor stop */
+ /*
+ * Interrupts not enabled as software handling is not
+ * needed in rev1 driver
+ */
+ /* Disable sensor stop bit */
+ config0 &= ~(1 << SENSOR_CFG0_STOP_SHIFT);
+ tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG0));
+ }
+
+ /* Check if counters are getting updated */
+ no_resp_count = 0;
+
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ /* if STOP bit is set skip this check */
+ config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
+ if (!(config0 & (1 << SENSOR_CFG0_STOP_SHIFT))) {
+ unsigned int loop_count = 0;
+ do {
+ status_reg = tsensor_readl(data,
+ (i << 16) | SENSOR_STATUS0);
+ if ((status_reg & (1 <<
+ SENSOR_STATUS_AVG_VALID_SHIFT)) &&
+ (status_reg & (1 <<
+ SENSOR_STATUS_CURR_VALID_SHIFT))) {
+ msleep(21);
+ loop_count++;
+ if (!(loop_count % 200))
+ dev_err(data->hwmon_dev,
+ " Warning: Tsensor Counter "
+ "sensor%d not Valid yet.\n", i);
+ if (loop_count > MAX_TSENSOR_LOOP1) {
+ no_resp_count++;
+ break;
+ }
+ }
+ } while (!(status_reg &
+ (1 << SENSOR_STATUS_AVG_VALID_SHIFT)) ||
+ (!(status_reg &
+ (1 << SENSOR_STATUS_CURR_VALID_SHIFT))));
+ if (no_resp_count == TSENSOR_COUNT) {
+ err = -ENODEV;
+ goto skip_all;
+ }
+ }
+ }
+ /* initialize tsensor chip coefficients */
+ get_chip_tsensor_coeff(data);
+skip_all:
+ return err;
+}
+
+/* function to enable tsensor clock */
+static int tsensor_clk_enable(
+ struct tegra_tsensor_data *data,
+ bool enable)
+{
+ int err = 0;
+ unsigned long rate;
+ struct clk *clk_m;
+
+ if (enable) {
+ clk_enable(data->dev_clk);
+ rate = clk_get_rate(data->dev_clk);
+ clk_m = clk_get_sys(NULL, "clk_m");
+ if (clk_get_parent(data->dev_clk) != clk_m) {
+ err = clk_set_parent(data->dev_clk, clk_m);
+ if (err < 0)
+ goto fail;
+ }
+ rate = DEFAULT_TSENSOR_CLK_HZ;
+ if (rate != clk_get_rate(clk_m)) {
+ err = clk_set_rate(data->dev_clk, rate);
+ if (err < 0)
+ goto fail;
+ }
+ } else {
+ clk_disable(data->dev_clk);
+ clk_put(data->dev_clk);
+ }
+fail:
+ return err;
+}
+
+/*
+ * function to set counter threshold corresponding to
+ * given temperature
+ */
+static void tsensor_set_limits(
+ struct tegra_tsensor_data *data,
+ int temp,
+ int threshold_index)
+{
+ unsigned int th_count;
+ unsigned int config;
+ unsigned short sft, offset;
+ unsigned int th1_count;
+
+ th_count = tsensor_get_threshold_counter(data, temp);
+ dev_dbg(data->hwmon_dev, "%s : input temp=%d, counter=0x%x\n", __func__,
+ temp, th_count);
+ switch (threshold_index) {
+ case TSENSOR_TH0:
+ sft = 16;
+ offset = SENSOR_CFG2;
+ /* assumed TH1 set before TH0, else we program
+ * TH0 as TH1 which means hysteresis will be
+ * same as TH1. Also, caller expected to pass
+ * (TH1 - hysteresis) as temp argument for this case */
+ th1_count = tsensor_readl(data,
+ ((data->tsensor_index << 16) |
+ SENSOR_CFG1));
+ th_count = (th1_count > th_count) ?
+ (th1_count - th_count) :
+ th1_count;
+ break;
+ case TSENSOR_TH1:
+ default:
+ sft = 0;
+ offset = SENSOR_CFG1;
+ break;
+ case TSENSOR_TH2:
+ sft = 16;
+ offset = SENSOR_CFG1;
+ break;
+ case TSENSOR_TH3:
+ sft = 0;
+ offset = SENSOR_CFG2;
+ break;
+ }
+ config = tsensor_readl(data, ((data->tsensor_index << 16) | offset));
+ dev_dbg(data->hwmon_dev, "%s: old config=0x%x, sft=%d, offset=0x%x\n",
+ __func__, config, sft, offset);
+ config &= ~(SENSOR_CFG_X_TH_X_MASK << sft);
+ config |= ((th_count & SENSOR_CFG_X_TH_X_MASK) << sft);
+ dev_dbg(data->hwmon_dev, "new config=0x%x\n", config);
+ tsensor_writel(data, config, ((data->tsensor_index << 16) | offset));
+}
+
+int tsensor_thermal_set_limits(struct tegra_tsensor_data *data,
+ long lo_limit_milli,
+ long hi_limit_milli)
+{
+ long lo_limit = MILLICELSIUS_TO_CELSIUS(lo_limit_milli);
+ long hi_limit = MILLICELSIUS_TO_CELSIUS(hi_limit_milli);
+ int i, j, hi_limit_first;
+
+ if (lo_limit_milli == hi_limit_milli)
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+
+ if (data->current_lo_limit == lo_limit_milli &&
+ data->current_hi_limit == hi_limit_milli) {
+ goto done;
+ }
+
+ /* If going up, change hi limit first. If going down, change lo
+ limit first */
+ hi_limit_first = hi_limit_milli > data->current_hi_limit;
+
+ for (i = 0; i < 2; i++) {
+ j = (i + hi_limit_first) % 2;
+
+ switch (j) {
+ case 0:
+ tsensor_set_limits(data, hi_limit, TSENSOR_TH2);
+ data->current_hi_limit = hi_limit_milli;
+ break;
+ case 1:
+ tsensor_set_limits(data, lo_limit, TSENSOR_TH1);
+ data->current_lo_limit = lo_limit_milli;
+ break;
+ }
+ }
+
+
+done:
+ mutex_unlock(&data->mutex);
+ return 0;
+}
+
+int tsensor_thermal_set_alert(struct tegra_tsensor_data *data,
+ void (*alert_func)(void *),
+ void *alert_data)
+{
+ mutex_lock(&data->mutex);
+
+ data->alert_data = alert_data;
+ data->alert_func = alert_func;
+
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+int tsensor_thermal_set_shutdown_temp(struct tegra_tsensor_data *data,
+ long shutdown_temp_milli)
+{
+ long shutdown_temp = MILLICELSIUS_TO_CELSIUS(shutdown_temp_milli);
+ tsensor_set_limits(data, shutdown_temp, TSENSOR_TH3);
+
+ return 0;
+}
+
+static int tsensor_within_limits(struct tegra_tsensor_data *data)
+{
+ int ts_state = get_ts_state(data);
+
+ return (ts_state == TS_LEVEL1) ||
+ (ts_state == TS_LEVEL0 && data->current_lo_limit == 0);
+}
+
+static void tsensor_work_func(struct work_struct *work)
+{
+ struct tegra_tsensor_data *data = container_of(work,
+ struct tegra_tsensor_data, work);
+
+ if (!data->alert_func)
+ return;
+
+ if (!tsensor_within_limits(data)) {
+ data->alert_func(data->alert_data);
+
+ if (!tsensor_within_limits(data))
+ queue_delayed_work(data->workqueue, &data->work,
+ HZ * DEFAULT_TSENSOR_M /
+ DEFAULT_TSENSOR_CLK_HZ);
+ }
+}
+
+/*
+ * This function enables the tsensor using default configuration
+ * 1. We would need some configuration APIs to calibrate
+ * the tsensor counters to right temperature
+ * 2. hardware triggered divide cpu clock by 2 as well pmu reset is enabled
+ * implementation. No software actions are enabled at this point
+ */
+static int tegra_tsensor_setup(struct platform_device *pdev)
+{
+ struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
+ struct resource *r;
+ int err = 0;
+ struct tegra_tsensor_platform_data *tsensor_data;
+ unsigned int reg;
+
+ data->dev_clk = clk_get(&pdev->dev, NULL);
+ if ((!data->dev_clk) || ((int)data->dev_clk == -(ENOENT))) {
+ dev_err(&pdev->dev, "Couldn't get the clock\n");
+ err = PTR_ERR(data->dev_clk);
+ goto fail;
+ }
+
+ /* Enable tsensor clock */
+ err = tsensor_clk_enable(data, true);
+ if (err < 0)
+ goto err_irq;
+
+ /* Reset tsensor */
+ dev_dbg(&pdev->dev, "before tsensor reset %s\n", __func__);
+ tegra_periph_reset_assert(data->dev_clk);
+ udelay(100);
+ tegra_periph_reset_deassert(data->dev_clk);
+ udelay(100);
+
+ dev_dbg(&pdev->dev, "before tsensor chk pmc reset %s\n",
+ __func__);
+ /* Check for previous resets in pmc */
+ if (pmc_check_rst_sensor(data)) {
+ dev_err(data->hwmon_dev, "Warning: ***** Last PMC "
+ "Reset source: tsensor detected\n");
+ }
+
+ dev_dbg(&pdev->dev, "before tsensor pmc reset enable %s\n",
+ __func__);
+ /* Enable the sensor reset in PMC */
+ pmc_rst_enable(data, true);
+
+ dev_dbg(&pdev->dev, "before tsensor get platform data %s\n",
+ __func__);
+ dev_dbg(&pdev->dev, "tsensor platform_data=0x%x\n",
+ (unsigned int)pdev->dev.platform_data);
+ tsensor_data = pdev->dev.platform_data;
+
+ /* register interrupt */
+ r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!r) {
+ dev_err(&pdev->dev, "Failed to get IRQ\n");
+ err = -ENXIO;
+ goto err_irq;
+ }
+ data->irq = r->start;
+ err = request_irq(data->irq, tegra_tsensor_isr,
+ IRQF_DISABLED, pdev->name, data);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Failed to register IRQ\n");
+ goto err_irq;
+ }
+
+ dev_dbg(&pdev->dev, "tsensor platform_data=0x%x\n",
+ (unsigned int)pdev->dev.platform_data);
+
+ dev_dbg(&pdev->dev, "before tsensor_config_setup\n");
+ err = tsensor_config_setup(data);
+ if (err) {
+ dev_err(&pdev->dev, "[%s,line=%d]: tsensor counters dead!\n",
+ __func__, __LINE__);
+ goto err_setup;
+ }
+ dev_dbg(&pdev->dev, "before tsensor_get_const_AB\n");
+ /* calculate constants needed for temperature conversion */
+ err = tsensor_get_const_AB(data);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Failed to extract temperature\n"
+ "const\n");
+ goto err_setup;
+ }
+
+ /* test if counter-to-temperature and temperature-to-counter
+ * are matching */
+ err = test_temperature_algo(data);
+ if (err) {
+ dev_err(&pdev->dev, "Error: read temperature\n"
+ "algorithm broken\n");
+ goto err_setup;
+ }
+
+ print_temperature_2_counter_table(data);
+
+ /* EDP and throttling support using tsensor enabled
+ * based on fuse revision */
+ err = tegra_fuse_get_revision(&reg);
+ if (err)
+ goto err_setup;
+
+ data->is_edp_supported = (reg >= STABLE_TSENSOR_FUSE_REV);
+
+ if (data->is_edp_supported) {
+ data->workqueue = create_singlethread_workqueue("tsensor");
+ INIT_DELAYED_WORK(&data->work, tsensor_work_func);
+ }
+
+ return 0;
+err_setup:
+ free_irq(data->irq, data);
+err_irq:
+ tsensor_clk_enable(data, false);
+fail:
+ dev_err(&pdev->dev, "%s error=%d returned\n", __func__, err);
+ return err;
+}
+
+static int __devinit tegra_tsensor_probe(struct platform_device *pdev)
+{
+ struct tegra_tsensor_data *data;
+ struct resource *r;
+ int err;
+ unsigned int reg;
+ u8 i;
+ struct tegra_tsensor_platform_data *tsensor_data;
+
+ data = kzalloc(sizeof(struct tegra_tsensor_data), GFP_KERNEL);
+ if (!data) {
+ dev_err(&pdev->dev, "[%s,line=%d]: Failed to allocate "
+ "memory\n", __func__, __LINE__);
+ err = -ENOMEM;
+ goto exit;
+ }
+ mutex_init(&data->mutex);
+ platform_set_drvdata(pdev, data);
+
+ /* Register sysfs hooks */
+ for (i = 0; i < ARRAY_SIZE(tsensor_nodes); i++) {
+ err = device_create_file(&pdev->dev,
+ &tsensor_nodes[i].dev_attr);
+ if (err) {
+ dev_err(&pdev->dev, "device_create_file failed.\n");
+ goto err0;
+ }
+ }
+
+ data->hwmon_dev = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ goto err1;
+ }
+
+ dev_set_drvdata(data->hwmon_dev, data);
+
+ spin_lock_init(&data->tsensor_lock);
+
+ /* map tsensor register space */
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL) {
+ dev_err(&pdev->dev, "[%s,line=%d]: Failed to get io "
+ "resource\n", __func__, __LINE__);
+ err = -ENODEV;
+ goto err2;
+ }
+
+ if (!request_mem_region(r->start, (r->end - r->start) + 1,
+ dev_name(&pdev->dev))) {
+ dev_err(&pdev->dev, "[%s,line=%d]: Error mem busy\n",
+ __func__, __LINE__);
+ err = -EBUSY;
+ goto err2;
+ }
+
+ data->phys = r->start;
+ data->phys_end = r->end;
+ data->base = ioremap(r->start, r->end - r->start + 1);
+ if (!data->base) {
+ dev_err(&pdev->dev, "[%s, line=%d]: can't ioremap "
+ "tsensor iomem\n", __FILE__, __LINE__);
+ err = -ENOMEM;
+ goto err3;
+ }
+
+ /* map pmc rst_status register */
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (r == NULL) {
+ dev_err(&pdev->dev, "[%s,line=%d]: Failed to get io "
+ "resource\n", __func__, __LINE__);
+ err = -ENODEV;
+ goto err4;
+ }
+
+ if (!request_mem_region(r->start, (r->end - r->start) + 1,
+ dev_name(&pdev->dev))) {
+ dev_err(&pdev->dev, "[%s, line=%d]: Error mem busy\n",
+ __func__, __LINE__);
+ err = -EBUSY;
+ goto err4;
+ }
+
+ data->pmc_phys = r->start;
+ data->pmc_phys_end = r->end;
+ data->pmc_rst_base = ioremap(r->start, r->end - r->start + 1);
+ if (!data->pmc_rst_base) {
+ dev_err(&pdev->dev, "[%s, line=%d]: can't ioremap "
+ "pmc iomem\n", __FILE__, __LINE__);
+ err = -ENOMEM;
+ goto err5;
+ }
+
+ /* fuse revisions less than TSENSOR_FUSE_REV1
+ bypass tsensor driver init */
+ /* tsensor active instance decided based on fuse revision */
+ err = tegra_fuse_get_revision(&reg);
+ if (err)
+ goto err6;
+ /* check for higher revision done first */
+ /* instance 0 is used for fuse revision
+ TSENSOR_FUSE_REV2 onwards */
+ if (reg >= TSENSOR_FUSE_REV2)
+ data->tsensor_index = TSENSOR_INSTANCE1;
+ /* instance 1 is used for fuse revision
+ TSENSOR_FUSE_REV1 till
+ TSENSOR_FUSE_REV2 */
+ else if (reg >= TSENSOR_FUSE_REV1)
+ data->tsensor_index = TSENSOR_INSTANCE2;
+ pr_info("tsensor active instance=%d\n", data->tsensor_index);
+
+ /* tegra tsensor - setup and init */
+ err = tegra_tsensor_setup(pdev);
+ if (err)
+ goto err6;
+
+ dump_tsensor_regs(data);
+ dev_dbg(&pdev->dev, "end tegra_tsensor_probe\n");
+
+ tsensor_data = pdev->dev.platform_data;
+ if (tsensor_data->probe_callback)
+ tsensor_data->probe_callback(data);
+
+ return 0;
+err6:
+ iounmap(data->pmc_rst_base);
+err5:
+ release_mem_region(data->pmc_phys, (data->pmc_phys_end -
+ data->pmc_phys) + 1);
+err4:
+ iounmap(data->base);
+err3:
+ release_mem_region(data->phys, (data->phys_end -
+ data->phys) + 1);
+err2:
+ hwmon_device_unregister(data->hwmon_dev);
+err1:
+ for (i = 0; i < ARRAY_SIZE(tsensor_nodes); i++)
+ device_remove_file(&pdev->dev, &tsensor_nodes[i].dev_attr);
+err0:
+ kfree(data);
+exit:
+ dev_err(&pdev->dev, "%s error=%d returned\n", __func__, err);
+ return err;
+}
+
+static int __devexit tegra_tsensor_remove(struct platform_device *pdev)
+{
+ struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
+ u8 i;
+
+ hwmon_device_unregister(data->hwmon_dev);
+ for (i = 0; i < ARRAY_SIZE(tsensor_nodes); i++)
+ device_remove_file(&pdev->dev, &tsensor_nodes[i].dev_attr);
+
+ if (data->is_edp_supported) {
+ cancel_delayed_work_sync(&data->work);
+ destroy_workqueue(data->workqueue);
+ data->workqueue = NULL;
+ }
+
+ free_irq(data->irq, data);
+
+ iounmap(data->pmc_rst_base);
+ release_mem_region(data->pmc_phys, (data->pmc_phys_end -
+ data->pmc_phys) + 1);
+ iounmap(data->base);
+ release_mem_region(data->phys, (data->phys_end -
+ data->phys) + 1);
+
+ kfree(data);
+
+ return 0;
+}
+
+static void save_tsensor_regs(struct tegra_tsensor_data *data)
+{
+ int i;
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ data->config0[i] = tsensor_readl(data,
+ ((i << 16) | SENSOR_CFG0));
+ data->config1[i] = tsensor_readl(data,
+ ((i << 16) | SENSOR_CFG1));
+ data->config2[i] = tsensor_readl(data,
+ ((i << 16) | SENSOR_CFG2));
+ }
+}
+
+static void restore_tsensor_regs(struct tegra_tsensor_data *data)
+{
+ int i;
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ tsensor_writel(data, data->config0[i],
+ ((i << 16) | SENSOR_CFG0));
+ tsensor_writel(data, data->config1[i],
+ ((i << 16) | SENSOR_CFG1));
+ tsensor_writel(data, data->config2[i],
+ ((i << 16) | SENSOR_CFG2));
+ }
+}
+
+#ifdef CONFIG_PM
+static int tsensor_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
+ unsigned int config0;
+ int i;
+ /* set STOP bit, else OVERFLOW interrupt seen in LP1 */
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
+ config0 |= (1 << SENSOR_CFG0_STOP_SHIFT);
+ tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG0));
+ }
+ /* save current settings before suspend, when STOP bit is set */
+ save_tsensor_regs(data);
+ tsensor_clk_enable(data, false);
+
+ return 0;
+}
+
+static int tsensor_resume(struct platform_device *pdev)
+{
+ struct tegra_tsensor_data *data = platform_get_drvdata(pdev);
+ unsigned int config0;
+ int i;
+ tsensor_clk_enable(data, true);
+ /* restore current settings before suspend, no need
+ * to clear STOP bit */
+ restore_tsensor_regs(data);
+ /* clear STOP bit, after restoring regs */
+ for (i = 0; i < TSENSOR_COUNT; i++) {
+ config0 = tsensor_readl(data, ((i << 16) | SENSOR_CFG0));
+ config0 &= ~(1 << SENSOR_CFG0_STOP_SHIFT);
+ tsensor_writel(data, config0, ((i << 16) | SENSOR_CFG0));
+ }
+
+ if (data->is_edp_supported)
+ schedule_delayed_work(&data->work, 0);
+
+ return 0;
+}
+#endif
+
+static struct platform_driver tegra_tsensor_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "tegra-tsensor",
+ },
+ .probe = tegra_tsensor_probe,
+ .remove = __devexit_p(tegra_tsensor_remove),
+#ifdef CONFIG_PM
+ .suspend = tsensor_suspend,
+ .resume = tsensor_resume,
+#endif
+};
+
+static int __init tegra_tsensor_init(void)
+{
+ return platform_driver_register(&tegra_tsensor_driver);
+}
+module_init(tegra_tsensor_init);
+
+static void __exit tegra_tsensor_exit(void)
+{
+ platform_driver_unregister(&tegra_tsensor_driver);
+}
+module_exit(tegra_tsensor_exit);
+
+MODULE_AUTHOR("nvidia");
+MODULE_DESCRIPTION("Nvidia Tegra Temperature Sensor driver");
+MODULE_LICENSE("GPL");