/* * drivers/misc/nct1008.c * * Driver for NCT1008, temperature monitoring device from ON Semiconductors * * Copyright (c) 2010-2014, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include /* Register Addresses used in this module. */ #define LOC_TEMP_RD 0x00 #define EXT_TEMP_HI_RD 0x01 #define STATUS_RD 0x02 #define CONFIG_RD 0x03 #define CONV_RATE_RD 0x04 #define LOC_TEMP_HI_LIMIT_RD 0x05 #define LOC_TEMP_LO_LIMIT_RD 0x06 #define EXT_TEMP_HI_LIMIT_HI_BYTE_RD 0x07 #define EXT_TEMP_LO_LIMIT_HI_BYTE_RD 0x08 #define CONFIG_WR 0x09 #define CONV_RATE_WR 0x0A #define LOC_TEMP_HI_LIMIT_WR 0x0B #define LOC_TEMP_LO_LIMIT_WR 0x0C #define EXT_TEMP_HI_LIMIT_HI_BYTE_WR 0x0D #define EXT_TEMP_LO_LIMIT_HI_BYTE_WR 0x0E #define ONE_SHOT 0x0F #define EXT_TEMP_LO_RD 0x10 #define OFFSET_WR 0x11 #define OFFSET_QUARTER_WR 0x12 #define EXT_TEMP_HI_LIMIT_LO_BYTE 0x13 #define EXT_TEMP_LO_LIMIT_LO_BYTE 0x14 /* NOT USED 0x15 */ /* NOT USED 0x16 */ /* NOT USED 0x17 */ /* NOT USED 0x18 */ #define EXT_THERM_LIMIT_WR 0x19 /* NOT USED 0x1A */ /* NOT USED 0x1B */ /* NOT USED 0x1C */ /* NOT USED 0x1D */ /* NOT USED 0x1E */ /* NOT USED 0x1F */ #define LOC_THERM_LIMIT 0x20 #define THERM_HYSTERESIS 0x21 #define COSECUTIVE_ALERT 0x22 /* Set of register types that are sensor dependant. */ enum nct1008_sensor_reg_types { TEMP_HI_LIMIT, TEMP_LO_LIMIT, TEMP_HI_LIMIT_RD, TEMP_LO_LIMIT_RD, TEMP_HI_LIMIT_WR, TEMP_LO_LIMIT_WR, TEMP_RD_LO, TEMP_RD_HI, TEMP_RD, TEMP_WR, REGS_COUNT /* This has to be the last element! */ }; /* Mapping from register type on a given sensor to hardware specific address. */ static int nct1008_sensor_regs[SENSORS_COUNT][REGS_COUNT] = { [LOC] = { [TEMP_HI_LIMIT_RD] = LOC_TEMP_HI_LIMIT_RD, [TEMP_HI_LIMIT_WR] = LOC_TEMP_HI_LIMIT_WR, [TEMP_LO_LIMIT_RD] = LOC_TEMP_LO_LIMIT_RD, [TEMP_LO_LIMIT_WR] = LOC_TEMP_LO_LIMIT_WR, }, [EXT] = { [TEMP_HI_LIMIT_RD] = EXT_TEMP_HI_LIMIT_HI_BYTE_RD, [TEMP_HI_LIMIT_WR] = EXT_TEMP_HI_LIMIT_HI_BYTE_WR, [TEMP_LO_LIMIT_RD] = EXT_TEMP_LO_LIMIT_HI_BYTE_RD, [TEMP_LO_LIMIT_WR] = EXT_TEMP_LO_LIMIT_HI_BYTE_WR, [TEMP_RD_LO] = EXT_TEMP_LO_RD, [TEMP_RD_HI] = EXT_TEMP_HI_RD, }, }; /* Accessor to the sensor specific registers. */ #define NCT_REG(x, y) nct1008_sensor_regs[x][y] /* Configuration register bits. */ #define EXTENDED_RANGE_BIT BIT(2) #define THERM2_BIT BIT(5) #define STANDBY_BIT BIT(6) #define ALERT_BIT BIT(7) /* Status register trip point bits. */ #define EXT_LO_BIT BIT(3) /* External Sensor has tripped 'temp <= LOW' */ #define EXT_HI_BIT BIT(4) /* External Sensor has tripped 'temp > HIGH' */ #define LOC_LO_BIT BIT(5) /* Local Sensor has tripped 'temp <= LOW' */ #define LOC_HI_BIT BIT(6) /* Local Sensor has tripped 'temp > HIGH' */ /* Constants. */ #define EXTENDED_RANGE_OFFSET 64U #define STANDARD_RANGE_MAX 127U #define EXTENDED_RANGE_MAX (150U + EXTENDED_RANGE_OFFSET) #define NCT1008_MIN_TEMP (-64) #define NCT1008_MAX_TEMP 191 #define NCT1008_MAX_TEMP_MILLI 191750 #define MAX_STR_PRINT 50 #define MAX_CONV_TIME_ONESHOT_MS 52 #define CELSIUS_TO_MILLICELSIUS(x) ((x)*1000) #define MILLICELSIUS_TO_CELSIUS(x) ((x)/1000) #define THERM_WARN_RANGE_HIGH_OFFSET 2000 #define THERM_WARN_RANGE_LOW_OFFSET 7000 struct nct1008_adjust_offset_table { int temp; int offset; }; struct nct1008_sensor_data { struct nct1008_adjust_offset_table offset_table[16]; struct thermal_zone_device *thz; long current_hi_limit; long current_lo_limit; long temp; }; struct nct1008_data { struct workqueue_struct *workqueue; struct work_struct work; struct i2c_client *client; struct nct1008_platform_data plat_data; struct mutex mutex; u8 config; enum nct1008_chip chip; struct regulator *nct_reg; int conv_period_ms; int nct_disabled; int stop_workqueue; struct nct1008_sensor_data sensors[SENSORS_COUNT]; }; static int conv_period_ms_table[] = {16000, 8000, 4000, 2000, 1000, 500, 250, 125, 63, 32, 16}; static void nct1008_setup_shutdown_warning(struct nct1008_data *data); static inline s16 value_to_temperature(bool extended, u8 value) { return extended ? (s16)(value - EXTENDED_RANGE_OFFSET) : (s16)value; } static inline u8 temperature_to_value(bool extended, s16 temp) { return extended ? (u8)(temp + EXTENDED_RANGE_OFFSET) : (u8)temp; } static int nct1008_write_reg(struct i2c_client *client, u8 reg, u16 value) { int ret = 0; struct nct1008_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); if (data && data->nct_disabled) { mutex_unlock(&data->mutex); return -ENODEV; } ret = i2c_smbus_write_byte_data(client, reg, value); mutex_unlock(&data->mutex); if (ret < 0) dev_err(&client->dev, "%s: err %d\n", __func__, ret); return ret; } static int nct1008_read_reg(struct i2c_client *client, u8 reg) { int ret = 0; struct nct1008_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); if (data && data->nct_disabled) { mutex_unlock(&data->mutex); return -ENODEV; } ret = i2c_smbus_read_byte_data(client, reg); mutex_unlock(&data->mutex); if (ret < 0) dev_err(&client->dev, "%s: err %d\n", __func__, ret); return ret; } static int nct1008_get_temp_common(int sensor, struct nct1008_data *data, long *temp) { struct i2c_client *client = data->client; struct nct1008_platform_data *pdata = client->dev.platform_data; struct nct1008_sensor_data *sensorp; s16 temp_hi; s16 temp_lo; long temp_milli = 0; int i, off = 0; u8 value; int ret; if (!((sensor == EXT) || (sensor == LOC))) return -1; /* Read External Temp */ if (sensor == EXT) { ret = nct1008_read_reg(client, NCT_REG(sensor, TEMP_RD_LO)); if (ret < 0) return -1; else value = ret; temp_lo = (value >> 6); ret = nct1008_read_reg(client, EXT_TEMP_HI_RD); if (ret < 0) return -1; else value = ret; temp_hi = value_to_temperature(pdata->extended_range, value); temp_milli = CELSIUS_TO_MILLICELSIUS(temp_hi) + temp_lo * 250; sensorp = &data->sensors[sensor]; for (i = 0; i < ARRAY_SIZE(sensorp->offset_table); i++) { if (temp_milli < (sensorp->offset_table[i].temp * 1000)) { off = sensorp->offset_table[i].offset * 1000; break; } } temp_milli += off; } else if (sensor == LOC) { value = nct1008_read_reg(client, LOC_TEMP_RD); if (value < 0) return -1; temp_hi = value_to_temperature(pdata->extended_range, value); temp_milli = CELSIUS_TO_MILLICELSIUS(temp_hi); } if (temp_milli > NCT1008_MAX_TEMP_MILLI) return -1; *temp = temp_milli; data->sensors[sensor].temp = temp_milli; return 0; } static ssize_t nct1008_show_temp(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_platform_data *pdata = client->dev.platform_data; s16 temp1 = 0; s16 temp = 0; u8 temp2 = 0; int value = 0; if (!dev || !buf || !attr) return -EINVAL; value = nct1008_read_reg(client, LOC_TEMP_RD); if (value < 0) goto error; temp1 = value_to_temperature(pdata->extended_range, value); value = nct1008_read_reg(client, EXT_TEMP_LO_RD); if (value < 0) goto error; temp2 = (value >> 6); value = nct1008_read_reg(client, EXT_TEMP_HI_RD); if (value < 0) goto error; temp = value_to_temperature(pdata->extended_range, value); return snprintf(buf, MAX_STR_PRINT, "%d %d.%d\n", temp1, temp, temp2 * 25); error: return snprintf(buf, MAX_STR_PRINT, "Error read local/ext temperature\n"); } static ssize_t nct1008_show_temp_overheat(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_platform_data *pdata = client->dev.platform_data; int value; s16 temp, temp2; /* Local temperature h/w shutdown limit */ value = nct1008_read_reg(client, LOC_THERM_LIMIT); if (value < 0) goto error; temp = value_to_temperature(pdata->extended_range, value); /* External temperature h/w shutdown limit */ value = nct1008_read_reg(client, EXT_THERM_LIMIT_WR); if (value < 0) goto error; temp2 = value_to_temperature(pdata->extended_range, value); return snprintf(buf, MAX_STR_PRINT, "%d %d\n", temp, temp2); error: dev_err(dev, "%s: failed to read temperature-overheat " "\n", __func__); return snprintf(buf, MAX_STR_PRINT, " Rd overheat Error\n"); } static ssize_t nct1008_set_temp_overheat(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { long int num; int err; u8 temp; long curr_temp; struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); char bufTemp[MAX_STR_PRINT]; char bufOverheat[MAX_STR_PRINT]; unsigned int ret; if (strict_strtol(buf, 0, &num)) { dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__, __LINE__, __func__); return -EINVAL; } if (((int)num < NCT1008_MIN_TEMP) || ((int)num >= NCT1008_MAX_TEMP)) { dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__, __LINE__, __func__); return -EINVAL; } /* check for system power down */ err = nct1008_get_temp_common(EXT, data, &curr_temp); if (err) goto error; curr_temp = MILLICELSIUS_TO_CELSIUS(curr_temp); if (curr_temp >= (int)num) { ret = nct1008_show_temp(dev, attr, bufTemp); ret = nct1008_show_temp_overheat(dev, attr, bufOverheat); dev_err(dev, "\nCurrent temp: %s ", bufTemp); dev_err(dev, "\nOld overheat limit: %s ", bufOverheat); dev_err(dev, "\nReset from overheat: curr temp=%ld, new overheat temp=%d\n\n", curr_temp, (int)num); } /* External temperature h/w shutdown limit */ temp = temperature_to_value(data->plat_data.extended_range, (s16)num); err = nct1008_write_reg(client, EXT_THERM_LIMIT_WR, temp); if (err < 0) goto error; /* Local temperature h/w shutdown limit */ temp = temperature_to_value(data->plat_data.extended_range, (s16)num); err = nct1008_write_reg(client, LOC_THERM_LIMIT, temp); if (err < 0) goto error; data->plat_data.sensors[EXT].shutdown_limit = num; nct1008_setup_shutdown_warning(data); return count; error: dev_err(dev, " %s: failed to set temperature-overheat\n", __func__); return err; } static ssize_t nct1008_show_temp_alert(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_platform_data *pdata = client->dev.platform_data; int value; s16 temp_hi, temp_lo; /* External Temperature Throttling hi-limit */ value = nct1008_read_reg(client, EXT_TEMP_HI_LIMIT_HI_BYTE_RD); if (value < 0) goto error; temp_hi = value_to_temperature(pdata->extended_range, value); /* External Temperature Throttling lo-limit */ value = nct1008_read_reg(client, EXT_TEMP_LO_LIMIT_HI_BYTE_RD); if (value < 0) goto error; temp_lo = value_to_temperature(pdata->extended_range, value); return snprintf(buf, MAX_STR_PRINT, "lo:%d hi:%d\n", temp_lo, temp_hi); error: dev_err(dev, "%s: failed to read temperature-alert\n", __func__); return snprintf(buf, MAX_STR_PRINT, " Rd alert Error\n"); } static ssize_t nct1008_set_temp_alert(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { long int num; int value; int err; struct i2c_client *client = to_i2c_client(dev); struct nct1008_platform_data *pdata = client->dev.platform_data; if (strict_strtol(buf, 0, &num)) { dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__, __LINE__, __func__); return -EINVAL; } if (((int)num < NCT1008_MIN_TEMP) || ((int)num >= NCT1008_MAX_TEMP)) { dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__, __LINE__, __func__); return -EINVAL; } /* External Temperature Throttling limit */ value = temperature_to_value(pdata->extended_range, (s16)num); err = nct1008_write_reg(client, EXT_TEMP_HI_LIMIT_HI_BYTE_WR, value); if (err < 0) goto error; /* Local Temperature Throttling limit */ err = nct1008_write_reg(client, LOC_TEMP_HI_LIMIT_WR, value); if (err < 0) goto error; return count; error: dev_err(dev, "%s: failed to set temperature-alert " "\n", __func__); return err; } static ssize_t nct1008_show_sensor_temp(int sensor, struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_platform_data *pdata = client->dev.platform_data; s16 temp_value; int data = 0; int data_lo; if (!dev || !buf || !attr) return -EINVAL; /* When reading the full external temperature value, read the * LSB first. This causes the MSB to be locked (that is, the * ADC does not write to it) until it is read */ data_lo = nct1008_read_reg(client, NCT_REG(sensor, TEMP_RD_LO)); if (data_lo < 0) { dev_err(&client->dev, "%s: failed to read " "ext_temperature, i2c error=%d\n", __func__, data_lo); goto error; } data = nct1008_read_reg(client, NCT_REG(sensor, TEMP_RD_HI)); if (data < 0) { dev_err(&client->dev, "%s: failed to read " "ext_temperature, i2c error=%d\n", __func__, data); goto error; } temp_value = value_to_temperature(pdata->extended_range, data); return snprintf(buf, MAX_STR_PRINT, "%d.%d\n", temp_value, (25 * (data_lo >> 6))); error: return snprintf(buf, MAX_STR_PRINT, "Error read ext temperature\n"); } static ssize_t pr_reg(struct nct1008_data *nct, char *buf, int max_s, const char *reg_name, int offset) { int ret, sz = 0; ret = nct1008_read_reg(nct->client, offset); if (ret >= 0) sz += snprintf(buf + sz, PAGE_SIZE - sz, "%20s 0x%02x 0x%02x 0x%02x\n", reg_name, nct->client->addr, offset, ret); else sz += snprintf(buf + sz, PAGE_SIZE - sz, "%s: line=%d, i2c ** read error=%d **\n", __func__, __LINE__, ret); return sz; } static ssize_t nct1008_show_regs(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *nct = i2c_get_clientdata(client); char *name = nct->chip == NCT72 ? "nct72" : "nct1008"; int sz = 0; sz += snprintf(buf + sz, PAGE_SIZE - sz, "%s Registers\n", name); sz += snprintf(buf + sz, PAGE_SIZE - sz, "---------------------------------------\n"); sz += snprintf(buf + sz, PAGE_SIZE - sz, "%20s %4s %4s %s\n", "Register Name ", "Addr", "Reg", "Value"); sz += snprintf(buf + sz, PAGE_SIZE - sz, "%20s %4s %4s %s\n", "--------------------", "----", "----", "-----"); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Status ", STATUS_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Configuration ", CONFIG_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Conversion Rate ", CONV_RATE_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Hysteresis ", THERM_HYSTERESIS); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Consecutive Alert ", COSECUTIVE_ALERT); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Local Temp Value ", LOC_TEMP_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Local Temp Hi Limit ", LOC_TEMP_HI_LIMIT_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Local Temp Lo Limit ", LOC_TEMP_LO_LIMIT_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Local Therm Limit ", LOC_THERM_LIMIT); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Value Hi ", EXT_TEMP_HI_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Value Lo ", EXT_TEMP_LO_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Hi Limit Hi", EXT_TEMP_HI_LIMIT_HI_BYTE_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Hi Limit Lo", EXT_TEMP_HI_LIMIT_LO_BYTE); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Lo Limit Hi", EXT_TEMP_LO_LIMIT_HI_BYTE_RD); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Lo Limit Lo", EXT_TEMP_LO_LIMIT_LO_BYTE); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Offset Hi ", OFFSET_WR); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Temp Offset Lo ", OFFSET_QUARTER_WR); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Ext Therm Limit ", EXT_THERM_LIMIT_WR); return sz; } static ssize_t nct1008_set_offsets(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *nct = i2c_get_clientdata(client); int index, temp, off; int rv = count; strim((char *)buf); sscanf(buf, "[%u] %u %d", &index, &temp, &off); if (index >= ARRAY_SIZE(nct->sensors[EXT].offset_table)) { pr_info("%s: invalid index [%d]\n", __func__, index); rv = -EINVAL; } else { nct->sensors[EXT].offset_table[index].temp = temp; nct->sensors[EXT].offset_table[index].offset = off; } return rv; } static ssize_t nct1008_show_offsets(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *nct = i2c_get_clientdata(client); char *name = nct->chip == NCT72 ? "nct72" : "nct1008"; int i, sz = 0; sz += snprintf(buf + sz, PAGE_SIZE - sz, "%s offsets table\n", name); sz += snprintf(buf + sz, PAGE_SIZE - sz, "%2s %4s %s\n", " #", "temp", "offset"); sz += snprintf(buf + sz, PAGE_SIZE - sz, "%2s %4s %s\n", "--", "----", "------"); for (i = 0; i < ARRAY_SIZE(nct->sensors[EXT].offset_table); i++) sz += snprintf(buf + sz, PAGE_SIZE - sz, "%2d %4d %3d\n", i, nct->sensors[EXT].offset_table[i].temp, nct->sensors[EXT].offset_table[i].offset); return sz; } /* This function is used by the system to show the temperature. */ static ssize_t nct1008_show_ext_temp(struct device *dev, struct device_attribute *attr, char *buf) { return nct1008_show_sensor_temp(EXT, dev, attr, buf); } static DEVICE_ATTR(temperature, S_IRUGO, nct1008_show_temp, NULL); static DEVICE_ATTR(temperature_overheat, (S_IRUGO | (S_IWUSR | S_IWGRP)), nct1008_show_temp_overheat, nct1008_set_temp_overheat); static DEVICE_ATTR(temperature_alert, (S_IRUGO | (S_IWUSR | S_IWGRP)), nct1008_show_temp_alert, nct1008_set_temp_alert); static DEVICE_ATTR(ext_temperature, S_IRUGO, nct1008_show_ext_temp, NULL); static DEVICE_ATTR(registers, S_IRUGO, nct1008_show_regs, NULL); static DEVICE_ATTR(offsets, (S_IRUGO | (S_IWUSR | S_IWGRP)), nct1008_show_offsets, nct1008_set_offsets); static struct attribute *nct1008_attributes[] = { &dev_attr_temperature.attr, &dev_attr_temperature_overheat.attr, &dev_attr_temperature_alert.attr, &dev_attr_ext_temperature.attr, &dev_attr_registers.attr, &dev_attr_offsets.attr, NULL }; static const struct attribute_group nct1008_attr_group = { .attrs = nct1008_attributes, }; static int nct1008_shutdown_warning_get_max_state( struct thermal_cooling_device *cdev, unsigned long *max_state) { *max_state = 1; return 0; } static int nct1008_shutdown_warning_get_cur_state( struct thermal_cooling_device *cdev, unsigned long *cur_state) { struct nct1008_data *data = cdev->devdata; long limit = data->plat_data.sensors[EXT].shutdown_limit * 1000; long temp; if (nct1008_get_temp_common(EXT, data, &temp)) return -1; if (temp >= (limit - THERM_WARN_RANGE_HIGH_OFFSET)) *cur_state = 1; else *cur_state = 0; return 0; } static int nct1008_shutdown_warning_set_cur_state( struct thermal_cooling_device *cdev, unsigned long cur_state) { static long temp_sav; struct nct1008_data *data = cdev->devdata; long limit = data->plat_data.sensors[EXT].shutdown_limit * 1000; long temp; if (nct1008_get_temp_common(EXT, data, &temp)) return -1; if ((temp >= (limit - THERM_WARN_RANGE_HIGH_OFFSET)) && (temp != temp_sav)) { pr_warn("NCT%s: Warning: Temperature (%ld.%02ldC) is %s SHUTDOWN (%c)\n", (data->chip == NCT72) ? "72" : "1008", temp / 1000, (temp % 1000) / 10, temp > limit ? "above" : temp == limit ? "at" : "near", temp > temp_sav ? '^' : 'v'); temp_sav = temp; } return 0; } static struct thermal_cooling_device_ops nct1008_shutdown_warning_ops = { .get_max_state = nct1008_shutdown_warning_get_max_state, .get_cur_state = nct1008_shutdown_warning_get_cur_state, .set_cur_state = nct1008_shutdown_warning_set_cur_state, }; static int nct1008_thermal_set_limits(int sensor, struct nct1008_data *data, long lo_limit_milli, long hi_limit_milli) { int err; u8 value; bool extended_range = data->plat_data.extended_range; long lo_limit = MILLICELSIUS_TO_CELSIUS(lo_limit_milli); long hi_limit = MILLICELSIUS_TO_CELSIUS(hi_limit_milli); if (lo_limit >= hi_limit) return -EINVAL; if (data->sensors[sensor].current_lo_limit != lo_limit) { value = temperature_to_value(extended_range, lo_limit); pr_debug("%s: set lo_limit %ld\n", __func__, lo_limit); err = nct1008_write_reg(data->client, NCT_REG(sensor, TEMP_LO_LIMIT_WR), value); if (err) return err; data->sensors[sensor].current_lo_limit = lo_limit; } if (data->sensors[sensor].current_hi_limit != hi_limit) { value = temperature_to_value(extended_range, hi_limit); pr_debug("%s: set hi_limit %ld\n", __func__, hi_limit); err = nct1008_write_reg(data->client, NCT_REG(sensor, TEMP_HI_LIMIT_WR), value); if (err) return err; data->sensors[sensor].current_hi_limit = hi_limit; } if (sensor == LOC) pr_debug("NCT1008: LOC-sensor limits set to %ld - %ld\n", lo_limit, hi_limit); return 0; } #ifdef CONFIG_THERMAL static void nct1008_update(int sensor, struct nct1008_data *data) { struct thermal_zone_device *thz; long low_temp, high_temp; struct thermal_trip_info *trip_state; long temp, trip_temp, hysteresis_temp; int count; low_temp = 0, high_temp = NCT1008_MAX_TEMP * 1000; thz = data->sensors[sensor].thz; if (!thz) return; thermal_zone_device_update(thz); temp = thz->temperature; for (count = 0; count < thz->trips; count++) { trip_state = &data->plat_data.sensors[sensor].trips[count]; trip_temp = trip_state->trip_temp; hysteresis_temp = trip_temp - trip_state->hysteresis; if ((trip_state->trip_type == THERMAL_TRIP_PASSIVE) && !trip_state->tripped) hysteresis_temp = trip_temp; if ((trip_temp > temp) && (trip_temp < high_temp)) high_temp = trip_temp; if ((hysteresis_temp < temp) && (hysteresis_temp > low_temp)) low_temp = hysteresis_temp; } nct1008_thermal_set_limits(sensor, data, low_temp, high_temp); } static int nct1008_ext_get_temp(struct thermal_zone_device *thz, long *temp) { struct nct1008_data *data = thz->devdata; return nct1008_get_temp_common(EXT, data, temp); } static int nct1008_ext_bind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { struct nct1008_data *data = thz->devdata; int i; bool bind = false; struct nct1008_sensor_platform_data *sensor; sensor = &data->plat_data.sensors[EXT]; for (i = 0; i < sensor->num_trips; i++) { if (!strcmp(sensor->trips[i].cdev_type, cdev->type) && !thermal_zone_bind_cooling_device(thz, i, cdev, sensor->trips[i].upper, sensor->trips[i].lower)) bind = true; } if (bind) nct1008_update(EXT, data); return 0; } static int nct1008_unbind(int sensor, struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { struct nct1008_data *data = thz->devdata; int i; struct nct1008_sensor_platform_data *sensor_data; sensor_data = &data->plat_data.sensors[sensor]; for (i = 0; i < sensor_data->num_trips; i++) { if (!strcmp(sensor_data->trips[i].cdev_type, cdev->type)) thermal_zone_unbind_cooling_device(thz, i, cdev); } return 0; } /* Helper function that is called in order to unbind external sensor from the cooling device. */ static inline int nct1008_ext_unbind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { return nct1008_unbind(EXT, thz, cdev); } /* Helper function that is called in order to unbind local sensor from the cooling device. */ static inline int nct1008_loc_unbind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { return nct1008_unbind(LOC, thz, cdev); } /* This function reads the temperature value set for the given trip point. */ static int nct1008_get_trip_temp(int sensor, struct thermal_zone_device *thz, int trip, long *temp) { struct nct1008_data *data = thz->devdata; struct thermal_trip_info *trip_state = &data->plat_data.sensors[sensor].trips[trip]; *temp = trip_state->trip_temp; if (trip_state->trip_type != THERMAL_TRIP_PASSIVE) return 0; if (thz->temperature >= *temp) { trip_state->tripped = true; } else if (trip_state->tripped) { *temp -= trip_state->hysteresis; if (thz->temperature < *temp) trip_state->tripped = false; } return 0; } /* This function reads the temperature value set for the given trip point for the local sensor. */ static inline int nct1008_loc_get_trip_temp(struct thermal_zone_device *thz, int trip, long *temp) { return nct1008_get_trip_temp(LOC, thz, trip, temp); } /* This function reads the temperature value set for the given trip point for the remote sensor. */ static inline int nct1008_ext_get_trip_temp(struct thermal_zone_device *thz, int trip, long *temp) { return nct1008_get_trip_temp(EXT, thz, trip, temp); } /* This function allows setting trip point temperature for the sensor specified. */ static int nct1008_set_trip_temp(int sensor, struct thermal_zone_device *thz, int trip, long temp) { struct nct1008_data *data = thz->devdata; data->plat_data.sensors[sensor].trips[trip].trip_temp = temp; nct1008_update(sensor, data); return 0; } /* This function allows setting trip point temperature for the local sensor. */ static inline int nct1008_loc_set_trip_temp(struct thermal_zone_device *thz, int trip, long temp) { return nct1008_set_trip_temp(LOC, thz, trip, temp); } /* This function allows setting trip point temperature for the external * sensor. */ static inline int nct1008_ext_set_trip_temp(struct thermal_zone_device *thz, int trip, long temp) { return nct1008_set_trip_temp(EXT, thz, trip, temp); } /* This function return the trip point type for the sensor specified. */ static int nct1008_get_trip_type(int sensor, struct thermal_zone_device *thz, int trip, enum thermal_trip_type *type) { struct nct1008_data *data = thz->devdata; *type = data->plat_data.sensors[sensor].trips[trip].trip_type; return 0; } /* This function return the trip point type for the local sensor. */ static inline int nct1008_loc_get_trip_type(struct thermal_zone_device *thz, int trip, enum thermal_trip_type *type) { return nct1008_get_trip_type(LOC, thz, trip, type); } /* This function return the trip point type for the external sensor. */ static inline int nct1008_ext_get_trip_type(struct thermal_zone_device *thz, int trip, enum thermal_trip_type *type) { return nct1008_get_trip_type(EXT, thz, trip, type); } /* This function returns value of trend for the temperature change, depending on the trip point type. */ static int nct1008_get_trend(int sensor, struct thermal_zone_device *thz, int trip, enum thermal_trend *trend) { struct nct1008_data *data = thz->devdata; struct thermal_trip_info *trip_state; trip_state = &data->plat_data.sensors[sensor].trips[trip]; switch (trip_state->trip_type) { case THERMAL_TRIP_ACTIVE: if (data->sensors[sensor].temp >= trip_state->trip_temp) *trend = THERMAL_TREND_RAISING; else *trend = THERMAL_TREND_DROPPING; break; case THERMAL_TRIP_PASSIVE: if (data->sensors[sensor].temp > trip_state->trip_temp) *trend = THERMAL_TREND_RAISING; else *trend = THERMAL_TREND_DROPPING; break; default: return -EINVAL; } return 0; } /* Helper function to get trend for the local sensor. */ static inline int nct1008_loc_get_trend(struct thermal_zone_device *thz, int trip, enum thermal_trend *trend) { return nct1008_get_trend(LOC, thz, trip, trend); } /* Helper function to get trend for the external sensor. */ static inline int nct1008_ext_get_trend(struct thermal_zone_device *thz, int trip, enum thermal_trend *trend) { return nct1008_get_trend(EXT, thz, trip, trend); } /* Helper function to get temperature of the local sensor. */ static int nct1008_loc_get_temp(struct thermal_zone_device *thz, long *temp) { struct nct1008_data *data = thz->devdata; return nct1008_get_temp_common(LOC, data, temp); } /* Helper function to bind local sensor with the cooling device specified. */ static int nct1008_loc_bind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { struct nct1008_data *data = thz->devdata; int i; bool bind = false; struct nct1008_sensor_platform_data *sensor_data; pr_debug("NCT1008: LOC-sensor bind %s, %s attempt\n", thz->type, cdev->type); sensor_data = &data->plat_data.sensors[LOC]; for (i = 0; i < sensor_data->num_trips; i++) { if (!strcmp(sensor_data->trips[i].cdev_type, cdev->type) && !thermal_zone_bind_cooling_device(thz, i, cdev, sensor_data->trips[i].upper, sensor_data->trips[i].lower)){ bind = true; break; } } if (bind) { nct1008_update(LOC, data); pr_debug("NCT1008: LOC-sensor bind %s, %s done\n", thz->type, cdev->type); } return 0; } static struct thermal_zone_device_ops nct_loc_ops = { .get_temp = nct1008_loc_get_temp, .bind = nct1008_loc_bind, .unbind = nct1008_loc_unbind, .get_trip_type = nct1008_loc_get_trip_type, .get_trip_temp = nct1008_loc_get_trip_temp, .set_trip_temp = nct1008_loc_set_trip_temp, .get_trend = nct1008_loc_get_trend, }; static struct thermal_zone_device_ops nct_ext_ops = { .get_temp = nct1008_ext_get_temp, .bind = nct1008_ext_bind, .unbind = nct1008_ext_unbind, .get_trip_type = nct1008_ext_get_trip_type, .get_trip_temp = nct1008_ext_get_trip_temp, .set_trip_temp = nct1008_ext_set_trip_temp, .get_trend = nct1008_ext_get_trend, }; #else static void nct1008_update(nct1008_sensors sensor, struct nct1008_data *data) { } #endif /* CONFIG_THERMAL */ static int nct1008_enable(struct i2c_client *client) { struct nct1008_data *data = i2c_get_clientdata(client); int err; err = nct1008_write_reg(client, CONFIG_WR, data->config); if (err < 0) dev_err(&client->dev, "%s, line=%d, i2c write error=%d\n", __func__, __LINE__, err); return err; } static int nct1008_disable(struct i2c_client *client) { struct nct1008_data *data = i2c_get_clientdata(client); int err; err = nct1008_write_reg(client, CONFIG_WR, data->config | STANDBY_BIT); if (err < 0) dev_err(&client->dev, "%s, line=%d, i2c write error=%d\n", __func__, __LINE__, err); return err; } static void nct1008_work_func(struct work_struct *work) { struct nct1008_data *data = container_of(work, struct nct1008_data, work); int err; struct timespec ts; int intr_status; mutex_lock(&data->mutex); if (data->stop_workqueue) { mutex_unlock(&data->mutex); return; } mutex_unlock(&data->mutex); err = nct1008_disable(data->client); if (err == -ENODEV) return; intr_status = nct1008_read_reg(data->client, STATUS_RD); pr_debug("NCT1008: interruption (0x%08x)\n", intr_status); if (intr_status & (LOC_LO_BIT | LOC_HI_BIT)) { pr_debug("NCT1008: LOC-sensor is not within limits\n"); nct1008_update(LOC, data); } if (intr_status & (EXT_LO_BIT | EXT_HI_BIT)) nct1008_update(EXT, data); /* Initiate one-shot conversion */ nct1008_write_reg(data->client, ONE_SHOT, 0x1); /* Give hardware necessary time to finish conversion */ ts = ns_to_timespec(MAX_CONV_TIME_ONESHOT_MS * 1000 * 1000); hrtimer_nanosleep(&ts, NULL, HRTIMER_MODE_REL, CLOCK_MONOTONIC); nct1008_read_reg(data->client, STATUS_RD); nct1008_enable(data->client); enable_irq(data->client->irq); } static irqreturn_t nct1008_irq(int irq, void *dev_id) { struct nct1008_data *data = dev_id; disable_irq_nosync(irq); queue_work(data->workqueue, &data->work); return IRQ_HANDLED; } static void nct1008_power_control(struct nct1008_data *data, bool is_enable) { int ret; mutex_lock(&data->mutex); if (!data->nct_reg) { data->nct_reg = regulator_get(&data->client->dev, "vdd"); if (IS_ERR_OR_NULL(data->nct_reg)) { if (PTR_ERR(data->nct_reg) == -ENODEV) dev_info(&data->client->dev, "no regulator found for vdd." " Assuming vdd is always powered"); else dev_warn(&data->client->dev, "Error [%ld] in " "getting the regulator handle for" " vdd\n", PTR_ERR(data->nct_reg)); data->nct_reg = NULL; mutex_unlock(&data->mutex); return; } } if (is_enable) ret = regulator_enable(data->nct_reg); else ret = regulator_disable(data->nct_reg); if (ret < 0) dev_err(&data->client->dev, "Error in %s rail vdd_nct%s, " "error %d\n", (is_enable) ? "enabling" : "disabling", (data->chip == NCT72) ? "72" : "1008", ret); else dev_info(&data->client->dev, "success in %s rail vdd_nct%s\n", (is_enable) ? "enabling" : "disabling", (data->chip == NCT72) ? "72" : "1008"); data->nct_disabled = !is_enable; mutex_unlock(&data->mutex); } static void nct1008_setup_shutdown_warning(struct nct1008_data *data) { static struct thermal_cooling_device *cdev; long limit = data->plat_data.sensors[EXT].shutdown_limit * 1000; long warn_temp = limit - THERM_WARN_RANGE_HIGH_OFFSET; int i; struct nct1008_sensor_platform_data *sensor_data; if (cdev) thermal_cooling_device_unregister(cdev); cdev = thermal_cooling_device_register("shutdown_warning", data, &nct1008_shutdown_warning_ops); if (IS_ERR_OR_NULL(cdev)) { cdev = NULL; return; } sensor_data = &data->plat_data.sensors[EXT]; for (i = 0; i < sensor_data->num_trips; i++) { if (!strcmp(sensor_data->trips[i].cdev_type, "shutdown_warning")) { sensor_data->trips[i].trip_temp = warn_temp; nct1008_update(EXT, data); break; } } pr_debug("NCT%s: Enabled overheat logging at %ld.%02ldC\n", (data->chip == NCT72) ? "72" : "1008", warn_temp / 1000, (warn_temp % 1000) / 10); } static int nct1008_configure_sensor(struct nct1008_data *data) { struct i2c_client *client = data->client; struct nct1008_platform_data *pdata = client->dev.platform_data; u8 value; s16 temp; u8 temp2; int ret; bool ext_err = false; if (!pdata || !pdata->supported_hwrev) return -ENODEV; ret = nct1008_read_reg(data->client, STATUS_RD); if (ret & BIT(2)) { pr_info("NCT%s: ERR: remote sensor circuit is open (0x%02x)\n", (data->chip == NCT72) ? "72" : "1008", ret); ext_err = true; /* flag the error */ } /* Initially place in Standby */ ret = nct1008_write_reg(client, CONFIG_WR, STANDBY_BIT); if (ret) goto error; /* Local temperature h/w shutdown limit */ value = temperature_to_value(pdata->extended_range, pdata->sensors[LOC].shutdown_limit); ret = nct1008_write_reg(client, LOC_THERM_LIMIT, value); if (ret) goto error; /* set extended range mode if needed */ if (pdata->extended_range) data->config |= EXTENDED_RANGE_BIT; data->config &= ~(THERM2_BIT | ALERT_BIT); ret = nct1008_write_reg(client, CONFIG_WR, data->config | STANDBY_BIT); if (ret) goto error; /* Temperature conversion rate */ ret = nct1008_write_reg(client, CONV_RATE_WR, pdata->conv_rate); if (ret) goto error; data->conv_period_ms = conv_period_ms_table[pdata->conv_rate]; /* Setup local hi and lo limits. */ ret = nct1008_write_reg(client, LOC_TEMP_HI_LIMIT_WR, NCT1008_MAX_TEMP); if (ret) goto error; ret = nct1008_write_reg(client, LOC_TEMP_LO_LIMIT_WR, 0); if (ret) goto error; /* read initial temperature */ ret = nct1008_read_reg(client, LOC_TEMP_RD); if (ret < 0) goto error; else value = ret; temp = value_to_temperature(pdata->extended_range, value); dev_dbg(&client->dev, "\n initial local temp = %d ", temp); if (ext_err) return ext_err; /* skip configuration of EXT sensor */ /* External temperature h/w shutdown limit. */ value = temperature_to_value(pdata->extended_range, pdata->sensors[EXT].shutdown_limit); ret = nct1008_write_reg(client, EXT_THERM_LIMIT_WR, value); if (ret) goto error; /* Setup external hi and lo limits */ ret = nct1008_write_reg(client, EXT_TEMP_LO_LIMIT_HI_BYTE_WR, 0); if (ret) goto error; ret = nct1008_write_reg(client, EXT_TEMP_HI_LIMIT_HI_BYTE_WR, NCT1008_MAX_TEMP); if (ret) goto error; ret = nct1008_read_reg(client, EXT_TEMP_LO_RD); if (ret < 0) goto error; else value = ret; temp2 = (value >> 6); ret = nct1008_read_reg(client, EXT_TEMP_HI_RD); if (ret < 0) goto error; else value = ret; temp = value_to_temperature(pdata->extended_range, value); if (temp2 > 0) dev_dbg(&client->dev, "\n initial ext temp = %d.%d deg", temp, temp2 * 25); else dev_dbg(&client->dev, "\n initial ext temp = %d.0 deg", temp); /* Remote channel offset */ ret = nct1008_write_reg(client, OFFSET_WR, pdata->offset / 4); if (ret < 0) goto error; /* Remote channel offset fraction (quarters) */ ret = nct1008_write_reg(client, OFFSET_QUARTER_WR, (pdata->offset % 4) << 6); if (ret < 0) goto error; /* Reset current hi/lo limit values with register values */ ret = nct1008_read_reg(data->client, EXT_TEMP_LO_LIMIT_HI_BYTE_RD); if (ret < 0) goto error; else value = ret; data->sensors[EXT].current_lo_limit = value_to_temperature(pdata->extended_range, value); ret = nct1008_read_reg(data->client, EXT_TEMP_HI_LIMIT_HI_BYTE_RD); if (ret < 0) goto error; else value = ret; data->sensors[EXT].current_hi_limit = value_to_temperature(pdata->extended_range, value); ret = nct1008_read_reg(data->client, LOC_TEMP_LO_LIMIT_RD); if (ret < 0) goto error; else value = ret; data->sensors[LOC].current_lo_limit = value_to_temperature(pdata->extended_range, value); value = nct1008_read_reg(data->client, LOC_TEMP_HI_LIMIT_RD); if (ret < 0) goto error; else value = ret; data->sensors[LOC].current_hi_limit = value_to_temperature(pdata->extended_range, value); nct1008_setup_shutdown_warning(data); return 0; error: dev_err(&client->dev, "\n exit %s, err=%d ", __func__, ret); return ret; } static int nct1008_configure_irq(struct nct1008_data *data) { data->workqueue = create_singlethread_workqueue((data->chip == NCT72) \ ? "nct72" : "nct1008"); INIT_WORK(&data->work, nct1008_work_func); if (data->client->irq < 0) return 0; else return request_irq(data->client->irq, nct1008_irq, IRQF_TRIGGER_LOW, (data->chip == NCT72) ? "nct72" : "nct1008", data); } /* * Manufacturer(OnSemi) recommended sequence for * Extended Range mode is as follows * 1. Place in Standby * 2. Scale the THERM and ALERT limits * appropriately(for Extended Range mode). * 3. Enable Extended Range mode. * ALERT mask/THERM2 mode may be done here * as these are not critical * 4. Set Conversion Rate as required * 5. Take device out of Standby */ /* * function nct1008_probe takes care of initial configuration */ static int nct1008_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct nct1008_data *data; int err; int i; u64 mask = 0; char nct_loc_name[THERMAL_NAME_LENGTH]; char nct_ext_name[THERMAL_NAME_LENGTH]; bool ext_err; struct nct1008_sensor_platform_data *sensor_data; data = kzalloc(sizeof(struct nct1008_data), GFP_KERNEL); if (!data) return -ENOMEM; data->client = client; data->chip = id->driver_data; memcpy(&data->plat_data, client->dev.platform_data, sizeof(struct nct1008_platform_data)); i2c_set_clientdata(client, data); mutex_init(&data->mutex); nct1008_power_control(data, true); if (!data->nct_reg) { /* power up failure */ err = -EIO; goto cleanup; } /* extended range recommended steps 1 through 4 taken care * in nct1008_configure_sensor function */ err = nct1008_configure_sensor(data); /* sensor is in standby */ ext_err = err; if (err < 0) { dev_err(&client->dev, "\n error file: %s : %s(), line=%d ", __FILE__, __func__, __LINE__); goto error; } err = nct1008_configure_irq(data); if (err < 0) { dev_err(&client->dev, "\n error file: %s : %s(), line=%d ", __FILE__, __func__, __LINE__); goto error; } dev_info(&client->dev, "%s: initialized\n", __func__); /* extended range recommended step 5 is in nct1008_enable function */ err = nct1008_enable(client); /* sensor is running */ if (err < 0) { dev_err(&client->dev, "Error: %s, line=%d, error=%d\n", __func__, __LINE__, err); goto error; } /* register sysfs hooks */ err = sysfs_create_group(&client->dev.kobj, &nct1008_attr_group); if (err < 0) { dev_err(&client->dev, "\n sysfs create err=%d ", err); goto error; } #ifdef CONFIG_THERMAL if (data->plat_data.loc_name) { strcpy(nct_loc_name, "Tboard_"); strcpy(nct_ext_name, "Tdiode_"); strncat(nct_loc_name, data->plat_data.loc_name, (THERMAL_NAME_LENGTH - strlen("Tboard_")) - 1); strncat(nct_ext_name, data->plat_data.loc_name, (THERMAL_NAME_LENGTH - strlen("Tdiode_")) - 1); } else { strcpy(nct_loc_name, "Tboard"); strcpy(nct_ext_name, "Tdiode"); } sensor_data = &data->plat_data.sensors[LOC]; /* Config for the Local sensor. */ mask = 0; for (i = 0; i < sensor_data->num_trips; i++) mask |= (((u64)(data->plat_data.sensors[LOC].trips[i].mask > 0)) << i); data->sensors[LOC].thz = thermal_zone_device_register( nct_loc_name, sensor_data->num_trips, mask, data, &nct_loc_ops, sensor_data->tzp, 2000, 0); if (IS_ERR_OR_NULL(data->sensors[LOC].thz)) goto error; /* check ext sensor connection before registering zone */ if (ext_err) return 0; /* skip registering unconneted sensor as zone */ /* Config for the External sensor. */ mask = 0; for (i = 0; i < data->plat_data.sensors[EXT].num_trips; i++) mask |= (((u64)(data->plat_data.sensors[EXT].trips[i].mask > 0)) << i); data->sensors[EXT].thz = thermal_zone_device_register(nct_ext_name, data->plat_data.sensors[EXT].num_trips, mask, data, &nct_ext_ops, data->plat_data.sensors[EXT].tzp, data->plat_data.sensors[EXT].passive_delay, data->plat_data.sensors[EXT].polling_delay); if (IS_ERR_OR_NULL(data->sensors[EXT].thz)) { thermal_zone_device_unregister(data->sensors[LOC].thz); data->sensors[LOC].thz = NULL; goto error; } nct1008_update(EXT, data); nct1008_update(LOC, data); #endif return 0; error: dev_err(&client->dev, "\n exit %s, err=%d ", __func__, err); nct1008_power_control(data, false); cleanup: mutex_destroy(&data->mutex); if (data->nct_reg) regulator_put(data->nct_reg); kfree(data); return err; } static int nct1008_remove(struct i2c_client *client) { struct nct1008_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); data->stop_workqueue = 1; mutex_unlock(&data->mutex); cancel_work_sync(&data->work); free_irq(data->client->irq, data); sysfs_remove_group(&client->dev.kobj, &nct1008_attr_group); nct1008_power_control(data, false); if (data->nct_reg) regulator_put(data->nct_reg); mutex_destroy(&data->mutex); kfree(data); return 0; } static void nct1008_shutdown(struct i2c_client *client) { struct nct1008_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); data->stop_workqueue = 1; mutex_unlock(&data->mutex); if (data->sensors[LOC].thz) { thermal_zone_device_unregister(data->sensors[LOC].thz); data->sensors[LOC].thz = NULL; } if (data->sensors[EXT].thz) { thermal_zone_device_unregister(data->sensors[EXT].thz); data->sensors[EXT].thz = NULL; } cancel_work_sync(&data->work); if (client->irq) disable_irq(client->irq); mutex_lock(&data->mutex); data->nct_disabled = 1; mutex_unlock(&data->mutex); } #ifdef CONFIG_PM_SLEEP static int nct1008_suspend_powerdown(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); int err; struct nct1008_data *data = i2c_get_clientdata(client); mutex_lock(&data->mutex); data->stop_workqueue = 1; mutex_unlock(&data->mutex); cancel_work_sync(&data->work); disable_irq(client->irq); err = nct1008_disable(client); nct1008_power_control(data, false); return err; } static int nct1008_suspend_wakeup(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); int err; struct nct1008_data *data = i2c_get_clientdata(client); long temp; int sensor_nr; struct nct1008_sensor_platform_data *sensor_data; for (sensor_nr = 0; sensor_nr < SENSORS_COUNT; sensor_nr++) { sensor_data = &data->plat_data.sensors[sensor_nr]; err = nct1008_get_temp_common(sensor_nr, data, &temp); if (err) goto error; if (temp > sensor_data->suspend_limit_lo) err = nct1008_thermal_set_limits(sensor_nr, data, sensor_data->suspend_limit_lo, NCT1008_MAX_TEMP * 1000); else err = nct1008_thermal_set_limits(sensor_nr, data, NCT1008_MIN_TEMP * 1000, sensor_data->suspend_limit_hi); if (err) goto error; } /* Enable NCT wake. */ err = enable_irq_wake(client->irq); if (err) dev_err(&client->dev, "Error: %s, error=%d. failed to enable NCT wakeup\n", __func__, err); return err; error: dev_err(&client->dev, "\n error in file=: %s %s() line=%d: " "error=%d. Can't set correct LP1 alarm limits or set wakeup irq, " "shutting down device", __FILE__, __func__, __LINE__, err); return nct1008_suspend_powerdown(dev); } static int nct1008_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); if (data->plat_data.suspend_with_wakeup && data->plat_data.suspend_with_wakeup()) return nct1008_suspend_wakeup(dev); else return nct1008_suspend_powerdown(dev); } static int nct1008_resume_wakeup(struct device *dev) { int err = 0; struct i2c_client *client = to_i2c_client(dev); err = disable_irq_wake(client->irq); if (err) { dev_err(&client->dev, "Error: %s, error=%d. failed to disable NCT " "wakeup\n", __func__, err); return err; } /* NCT wasn't powered down, so IRQ is still enabled. */ /* Disable it before calling update */ disable_irq(client->irq); return err; } static int nct1008_resume_powerdown(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); int err = 0; struct nct1008_data *data = i2c_get_clientdata(client); nct1008_power_control(data, true); nct1008_configure_sensor(data); err = nct1008_enable(client); if (err < 0) { dev_err(&client->dev, "Error: %s, error=%d\n", __func__, err); return err; } return err; } static int nct1008_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); int err; struct nct1008_data *data = i2c_get_clientdata(client); if (data->plat_data.suspend_with_wakeup && data->plat_data.suspend_with_wakeup()) err = nct1008_resume_wakeup(dev); else err = nct1008_resume_powerdown(dev); if (err) return err; nct1008_update(LOC, data); nct1008_update(EXT, data); mutex_lock(&data->mutex); data->stop_workqueue = 0; mutex_unlock(&data->mutex); enable_irq(client->irq); return 0; } static const struct dev_pm_ops nct1008_pm_ops = { .suspend = nct1008_suspend, .resume = nct1008_resume, }; #endif static const struct i2c_device_id nct1008_id[] = { { "nct1008", NCT1008 }, { "nct72", NCT72}, {} }; MODULE_DEVICE_TABLE(i2c, nct1008_id); static struct i2c_driver nct1008_driver = { .driver = { .name = "nct1008_nct72", #ifdef CONFIG_PM_SLEEP .pm = &nct1008_pm_ops, #endif }, .probe = nct1008_probe, .remove = nct1008_remove, .id_table = nct1008_id, .shutdown = nct1008_shutdown, }; static int __init nct1008_init(void) { return i2c_add_driver(&nct1008_driver); } static void __exit nct1008_exit(void) { i2c_del_driver(&nct1008_driver); } MODULE_DESCRIPTION("Temperature sensor driver for OnSemi NCT1008/NCT72"); MODULE_LICENSE("GPL"); module_init(nct1008_init); module_exit(nct1008_exit);