diff options
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 33 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 4 | ||||
-rw-r--r-- | drivers/hwmon/adt7461.c | 809 | ||||
-rw-r--r-- | drivers/hwmon/ina219.c | 414 | ||||
-rw-r--r-- | drivers/hwmon/ina230.c | 561 | ||||
-rw-r--r-- | drivers/hwmon/tegra-tsensor.c | 1991 |
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, ®, &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, ®, &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(®1); + 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(®); + 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(®); + 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"); |