/* * drivers/misc/nct1008.c * * Driver for NCT1008, temperature monitoring device from ON Semiconductors * * Copyright (c) 2010-2020, 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 #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 #define NFACTOR_CORRECTION 0x23 #define MANUFACTURER_ID 0xFE #define MAX6649_LOC_TEMP_LO_RD 0x11 /* Tdiode Offset fuse is stored in usigned Q5.2 Fixed Point format */ #define CP_INT (5) #define CP_FRAC (2) #define MASK_CP1 (0x7F) #define SHIFT_CP1 (0) #define SHIFT_CP2 (14) #define MASK_CP2 (0x7F << SHIFT_CP2) #define FIXED_TO_MILLI_C(val, cp) \ (((((val) & MASK_##cp) >> SHIFT_##cp) * 1000) / (1 << CP_FRAC)) #define OFFSET_FRAC_BITS (4) #define OFFSET_FRAC_MULT (1 << OFFSET_FRAC_BITS) #define OFFSET_FRAC_MASK (0xf) /* Temperatures at which offsets are captured in K*/ #define TC1 (25000) #define TC2 (105000) #define TK1 (273150 + TC1) #define TK2 (273150 + TC2) /* * optimized nfactor for an ideal TMP451 sensor specified in datasheet is * 1.008, but since it changes in increments of 1/2088 steps we need 5 fraction * bits to maintain precision and hence is multipled by 10^5. */ #define TMP451_NFACTOR (100800) /* step size in which nfactor can increment is 1/2088 specified in datasheet */ #define TMP451_NFACTOR_STEP (2088) /* 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 bits */ #define EXT_OPEN_BIT BIT(2) /* External sensor is open junction */ #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 (-64L) #define NCT1008_MAX_TEMP 191L #define NCT1008_MAX_TEMP_MILLI 191750 #define MAX_STR_PRINT 50 #define NCT_CONV_TIME_ONESHOT_US 52000 #define TMP451_CONV_TIME_ONESHOT_US 34000 #define CELSIUS_TO_MILLICELSIUS(x) ((x)*1000) #define MILLICELSIUS_TO_CELSIUS(x) ((x)/1000) #define LOC TEGRA_NCT_SENSOR_LOC #define EXT TEGRA_NCT_SENSOR_EXT #define CNT TEGRA_NCT_SENSOR_MAX struct nct1008_sensor_data { struct thermal_zone_device *thz; long current_hi_limit; long current_lo_limit; int shutdown_limit; int 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; char chip_name[I2C_NAME_SIZE]; struct regulator *nct_reg; int ext_open; int oneshot_conv_period_us; int nct_disabled; int stop_workqueue; struct nct1008_sensor_data sensors[CNT]; }; static const unsigned long THERM_WARN_RANGE_HIGH_OFFSET = 3000; static unsigned long nct1008_shutdown_warning_cur_state; static long shutdown_warn_saved_temp; 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 nct1008_data *data, u8 reg, u16 value) { int ret = -ENODEV; if (!data) return ret; mutex_lock(&data->mutex); if (data->nct_disabled) { mutex_unlock(&data->mutex); goto err; } ret = i2c_smbus_write_byte_data(data->client, reg, value); mutex_unlock(&data->mutex); err: if (ret < 0) dev_err(&data->client->dev, "write reg err %d\n", ret); return ret; } static int nct1008_read_reg(struct nct1008_data *data, u8 reg) { int ret = -ENODEV; if (!data) return ret; mutex_lock(&data->mutex); if (data->nct_disabled) { mutex_unlock(&data->mutex); goto err; } ret = i2c_smbus_read_byte_data(data->client, reg); mutex_unlock(&data->mutex); err: if (ret < 0) dev_err(&data->client->dev, "read reg err %d\n", ret); return ret; } static int nct1008_get_temp_common(int sensor, struct nct1008_data *data, int *temp) { struct nct1008_platform_data *pdata = &data->plat_data; s16 temp_hi; s16 temp_lo = 0; long temp_milli = 0; u8 value; int ret; if (!((sensor == EXT) || (sensor == LOC))) return -1; /* Read External Temp */ if (sensor == EXT) { ret = nct1008_read_reg(data, EXT_TEMP_LO_RD); if (ret < 0) return -1; else value = ret; temp_lo = (value >> 6); ret = nct1008_read_reg(data, 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; } else if (sensor == LOC) { ret = nct1008_read_reg(data, LOC_TEMP_RD); if (ret < 0) return -1; else value = ret; temp_hi = value_to_temperature(pdata->extended_range, value); if (data->chip == MAX6649) { ret = nct1008_read_reg(data, MAX6649_LOC_TEMP_LO_RD); if(ret < 0) return -1; else value = ret; temp_lo = (value >> 6); } temp_milli = CELSIUS_TO_MILLICELSIUS(temp_hi) + temp_lo * 250; } 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_data *data = i2c_get_clientdata(client); struct nct1008_platform_data *pdata = &data->plat_data; s16 temp1 = 0; s16 temp = 0; u8 temp2 = 0; int value = 0; if (!dev || !buf || !attr) return -EINVAL; value = nct1008_read_reg(data, LOC_TEMP_RD); if (value < 0) goto error; temp1 = value_to_temperature(pdata->extended_range, value); if(data->chip == MAX6649) { value = nct1008_read_reg(data, MAX6649_LOC_TEMP_LO_RD); if(value < 0) goto error; temp2 = (value >> 6); return snprintf(buf, MAX_STR_PRINT, "%d.%d\n", temp1, temp2 * 25); } value = nct1008_read_reg(data, EXT_TEMP_LO_RD); if (value < 0) goto error; temp2 = (value >> 6); value = nct1008_read_reg(data, 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_data *data = i2c_get_clientdata(client); struct nct1008_platform_data *pdata = &data->plat_data; int value; s16 temp, temp2; /* Local temperature h/w shutdown limit */ value = nct1008_read_reg(data, 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(data, 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: 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) { int num; int err; int temp; struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); if (kstrtoint(buf, 0, &num)) return -EINVAL; if ((num < NCT1008_MIN_TEMP) || (num >= NCT1008_MAX_TEMP)) { dev_err(dev, "Out of bounds temperature limit\n"); return -EINVAL; } /* check for system power down */ err = nct1008_get_temp_common(EXT, data, &temp); if (err) goto error; temp = MILLICELSIUS_TO_CELSIUS(temp); if (temp >= num) dev_warn(dev, "new overheat temp=%d exceeds curr temp=%dn\n", num, temp); /* External temperature h/w shutdown limit */ temp = temperature_to_value(data->plat_data.extended_range, (s16)num); err = nct1008_write_reg(data, 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(data, LOC_THERM_LIMIT, temp); if (err < 0) goto error; data->sensors[EXT].shutdown_limit = num; return count; error: dev_err(dev, "failed to set temperature-overheat\n"); 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_data *data = i2c_get_clientdata(client); struct nct1008_platform_data *pdata = &data->plat_data; int value; s16 temp_hi, temp_lo; /* External Temperature Throttling hi-limit */ value = nct1008_read_reg(data, 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(data, 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) { int val; int err; struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); struct nct1008_platform_data *pdata = &data->plat_data; if (kstrtoint(buf, 0, &val)) return -EINVAL; if ((val < NCT1008_MIN_TEMP) || (val >= NCT1008_MAX_TEMP)) { dev_err(dev, "Out of bounds temperature limit\n"); return -EINVAL; } /* External Temperature Throttling limit */ val = temperature_to_value(pdata->extended_range, (s16)val); err = nct1008_write_reg(data, EXT_TEMP_HI_LIMIT_HI_BYTE_WR, val); if (err < 0) goto error; /* Local Temperature Throttling limit */ err = nct1008_write_reg(data, LOC_TEMP_HI_LIMIT_WR, val); 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_ext_temp(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); struct nct1008_platform_data *pdata = &data->plat_data; s16 temp_value; int val = 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(data, EXT_TEMP_LO_RD); if (data_lo < 0) goto error; val = nct1008_read_reg(data, EXT_TEMP_HI_RD); if (val < 0) goto error; temp_value = value_to_temperature(pdata->extended_range, val); 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, 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); int sz = 0; sz += snprintf(buf + sz, PAGE_SIZE - sz, "%s Registers\n", nct->chip_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 Lo Limit Hi", EXT_TEMP_LO_LIMIT_HI_BYTE_RD); if(nct->chip == MAX6649) { sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "Local Temp Value lo ", MAX6649_LOC_TEMP_LO_RD); } else { 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 Temp Hi Limit Lo", EXT_TEMP_HI_LIMIT_LO_BYTE); 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 Therm Limit ", EXT_THERM_LIMIT_WR); sz += pr_reg(nct, buf+sz, PAGE_SIZE-sz, "ManufacturerID ", MANUFACTURER_ID); return sz; } static ssize_t nct1008_set_nadjust(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); int r, nadj; sscanf(buf, "%d", &nadj); r = nct1008_write_reg(data, NFACTOR_CORRECTION, nadj); if (r) return r; return count; } static ssize_t nct1008_show_nadjust(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); int nadj, nf, sz = 0; nadj = nct1008_read_reg(data, NFACTOR_CORRECTION); nf = (TMP451_NFACTOR * TMP451_NFACTOR_STEP) / (TMP451_NFACTOR_STEP + nadj); sz += snprintf(buf + sz, PAGE_SIZE - sz, "nadj: %d, nf: %d\n", nadj, nf); return sz; } static ssize_t nct1008_set_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); int r = count, hi_b, lo_b; sscanf(buf, "%d %d", &hi_b, &lo_b); r = nct1008_write_reg(data, OFFSET_WR, hi_b); r = r ? r : nct1008_write_reg(data, OFFSET_QUARTER_WR, lo_b << 4); if (r) return r; return count; } static ssize_t nct1008_show_offset(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct nct1008_data *data = i2c_get_clientdata(client); int hi_b, lo_b, sz = 0; hi_b = nct1008_read_reg(data, OFFSET_WR); lo_b = nct1008_read_reg(data, OFFSET_QUARTER_WR); sz += snprintf(buf + sz, PAGE_SIZE - sz, "offset: %d, %d\n", hi_b, lo_b); return sz; } 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(offset, (S_IRUGO | (S_IWUSR | S_IWGRP)), nct1008_show_offset, nct1008_set_offset); static DEVICE_ATTR(nadj, (S_IRUGO | (S_IWUSR | S_IWGRP)), nct1008_show_nadjust, nct1008_set_nadjust); 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_offset.attr, &dev_attr_nadj.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) { /* A state for every 250mC */ *max_state = THERM_WARN_RANGE_HIGH_OFFSET / 250; 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->sensors[EXT].shutdown_limit * 1000; int temp; if (nct1008_get_temp_common(EXT, data, &temp)) return -1; if (temp >= (limit - THERM_WARN_RANGE_HIGH_OFFSET)) *cur_state = nct1008_shutdown_warning_cur_state; else *cur_state = 0; return 0; } static int nct1008_shutdown_warning_set_cur_state( struct thermal_cooling_device *cdev, unsigned long cur_state) { struct nct1008_data *data = cdev->devdata; long limit = data->sensors[EXT].shutdown_limit * 1000; int temp; if (nct1008_get_temp_common(EXT, data, &temp)) return -1; else if (temp < 0) goto ret; if ((temp >= (limit - THERM_WARN_RANGE_HIGH_OFFSET)) && (temp != shutdown_warn_saved_temp)) { pr_warn("%s: Warning: chip temperature (%d.%02dC) is %s SHUTDOWN limit (%c%ldC).\n", data->chip_name, temp / 1000, (temp % 1000) / 10, temp > limit ? "above" : temp == limit ? "at" : "near", temp > limit ? '>' : '<', limit / 1000); shutdown_warn_saved_temp = temp; } ret: nct1008_shutdown_warning_cur_state = cur_state; 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_mC, long hi_limit_mC) { int err; u8 value; struct i2c_client *client = data->client; bool extended_range = data->plat_data.extended_range; long lo_limit; long hi_limit; u8 reg; lo_limit = max(NCT1008_MIN_TEMP, MILLICELSIUS_TO_CELSIUS(lo_limit_mC)); hi_limit = min(NCT1008_MAX_TEMP, MILLICELSIUS_TO_CELSIUS(hi_limit_mC)); if (lo_limit >= hi_limit) return -EINVAL; if (data->sensors[sensor].current_lo_limit != lo_limit) { value = temperature_to_value(extended_range, lo_limit); reg = (sensor == LOC) ? LOC_TEMP_LO_LIMIT_WR : EXT_TEMP_LO_LIMIT_HI_BYTE_WR; err = nct1008_write_reg(data, reg, 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); reg = (sensor == LOC) ? LOC_TEMP_HI_LIMIT_WR : EXT_TEMP_HI_LIMIT_HI_BYTE_WR; err = nct1008_write_reg(data, reg, value); if (err) return err; data->sensors[sensor].current_hi_limit = hi_limit; } dev_dbg(&client->dev, "limits set to lo:%ld hi:%ld\n", lo_limit, hi_limit); return 0; } static int nct1008_ext_get_temp_as_sensor(void *data, int *temp) { return nct1008_get_temp_common(EXT, (struct nct1008_data *) data, temp); } static int nct1008_get_trend_as_sensor(int sensor, void *data, int trip, enum thermal_trend *trend) { int ret, temp, trip_temp, last_temp; struct nct1008_data *nct_data = (struct nct1008_data *)data; struct thermal_zone_device *thz = nct_data->sensors[sensor].thz; *trend = THERMAL_TREND_STABLE; if (!thz) return 0; ret = thz->ops->get_trip_temp(thz, trip, &trip_temp); if (ret) return ret; mutex_lock(&thz->lock); temp = thz->temperature; last_temp = thz->last_temperature; mutex_unlock(&thz->lock); if (temp > trip_temp) { if (temp >= last_temp) *trend = THERMAL_TREND_RAISING; else *trend = THERMAL_TREND_STABLE; } else if (temp < trip_temp) { *trend = THERMAL_TREND_DROPPING; } else { *trend = THERMAL_TREND_STABLE; } return 0; } /* Helper function to get trend for the local sensor. */ static inline int nct1008_loc_get_trend_as_sensor(void *data, int trip, enum thermal_trend *trend) { return nct1008_get_trend_as_sensor(LOC, data, trip, trend); } static inline int nct1008_ext_get_trend_as_sensor(void *data, int trip, enum thermal_trend *trend) { return nct1008_get_trend_as_sensor(EXT, data, trip, trend); } static int nct1008_loc_get_temp_as_sensor(void *data, int *temp) { return nct1008_get_temp_common(LOC, (struct nct1008_data *) data, temp); } static int nct1008_loc_set_trips(void *of_data, int low, int high) { struct nct1008_data *data = (struct nct1008_data *)of_data; nct1008_thermal_set_limits(LOC, data, low, high); return 0; } static int nct1008_ext_set_trips(void *of_data, int low, int high) { struct nct1008_data *data = (struct nct1008_data *)of_data; nct1008_thermal_set_limits(EXT, data, low, high); return 0; } static int nct1008_enable(struct i2c_client *client) { struct nct1008_data *data = i2c_get_clientdata(client); return nct1008_write_reg(data, CONFIG_WR, data->config); } static int nct1008_disable(struct i2c_client *client) { struct nct1008_data *data = i2c_get_clientdata(client); return nct1008_write_reg(data, CONFIG_WR, data->config | STANDBY_BIT); } static void nct1008_work_func(struct work_struct *work) { struct nct1008_data *data = container_of(work, struct nct1008_data, work); struct i2c_client *client = data->client; int err; int st; 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; st = nct1008_read_reg(data, STATUS_RD); dev_dbg(&client->dev, "%s: interrupt (0x%08x)\n", data->chip_name, st); if ((st & (LOC_LO_BIT | LOC_HI_BIT)) && data->sensors[LOC].thz) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) thermal_zone_device_update(data->sensors[LOC].thz, THERMAL_EVENT_UNSPECIFIED); #else thermal_zone_device_update(data->sensors[LOC].thz); #endif if ((st & (EXT_LO_BIT | EXT_HI_BIT)) && data->sensors[EXT].thz) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) thermal_zone_device_update(data->sensors[EXT].thz, THERMAL_EVENT_UNSPECIFIED); #else thermal_zone_device_update(data->sensors[EXT].thz); #endif /* Initiate one-shot conversion */ err = nct1008_write_reg(data, ONE_SHOT, 0x1); if (err < 0) return; /* Give hardware necessary time to finish conversion */ usleep_range(data->oneshot_conv_period_us, data->oneshot_conv_period_us + 1000); err = nct1008_read_reg(data, STATUS_RD); if (err < 0) return; 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) { 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 %s VDD rail, " "error %d\n", (is_enable) ? "enabling" : "disabling", data->chip_name, ret); else dev_info(&data->client->dev, "success in %s %s VDD rail\n", (is_enable) ? "enabling" : "disabling", data->chip_name); data->nct_disabled = !is_enable; mutex_unlock(&data->mutex); } /* * CP2 = Tdiode - Tchuck at 105C in Q5.2 format * CP1 = Tdiode - Tchuck at 25C in Q5.2 format * TC1, TC2 are true physical temperatures 25C & 105C. * Treported2 = TC2 + CP2 * to account for part to part variation use alpha beta and apply * linear correction to the actual temperature * Tr2 = alpha * (TC2 + CP2) + beta * Terr = Treported - Tphysical * Terr @ TC2 = TE2 = Tr2 - TC2 * Terr @ TC1 = TE1 = Tr1 - TC1 * slope = (TE2 - TE1)/(TC2 - TC1) * = alpha * ((cp2 - cp1)/(TC2 - T1)) + alpha - 1 * * per TMP451 datasheet: * Terr = (nf - 1.008)/1.008 * Tk * So, Terr_slope = (nf - 1.008)/1.008 ...(1) * * Emperically TMP451 reports higher temperature when nFactor is reduced. This * means the Terr in (2) is negative and is being subtracted when the sensor * overestimates the temperature. Slope therefore has to be negative: * slope = - alpha * ((cp2 - cp1)/(TC2 - T1)) - alpha + 1...(2) * * solving for nf from (1) & (2): * nf = -(1.008 * slope) + 1.008 ...(4) * * quantize and map nf to an signed int using the nfactor step size * nadj = (1.008 * 2088 / nFactor) - 2088 * * After applying the nFactor and Offset, Tdiode's reported temperature should * be closer to the physical temperature. The adjusted temp is calculated as: * Tadjusted = Offset - slope * Tk + Treported * Assuming adjusted temp same as physical temp, calculate offset @ T2: * Offset = Tadjusted - Treported + slope * Tk * = Tphysical - Treported + slope * Tk * = -TE2 + slope * TK2 * = -alpha * (TC2 + CP2) - beta + TC2 + slope * TK1 */ static int nct1008_offsets_program(struct nct1008_data *data) { struct i2c_client *client = data->client; struct nct1008_platform_data *p = &data->plat_data; /* use DT based offset unless fuse_offset is present */ int64_t off = p->offset, slope, nf = 0, nadj = 0, cp2, cp1; int r = 0, val, lo_b, hi_b; if (p->fuse_offset) { r = tegra_fuse_readl(FUSE_TDIODE_CALIB, &val); if (r) return r; cp2 = FIXED_TO_MILLI_C(val, CP2); cp1 = FIXED_TO_MILLI_C(val, CP1); /* * alpha and beta have 4 fraction digits and is scaled by 10^4. * nf is therefore scaled back by 10^4 in the end. */ slope = ((p->alpha * (cp2 - cp1)) / (TC2 - TC1)) + p->alpha - 10000; nf = -((TMP451_NFACTOR * slope) / 10000) + TMP451_NFACTOR; nadj = (TMP451_NFACTOR * TMP451_NFACTOR_STEP / nf) - TMP451_NFACTOR_STEP; off = -p->alpha * (TC2 + cp2) - p->beta * 1000 + TC2 * 10000 - slope * TK2; off = off * OFFSET_FRAC_MULT / 10000000; r = nct1008_write_reg(data, NFACTOR_CORRECTION, nadj); dev_info(&client->dev, "nf:%lld, nadj:%lld, off:%lld\n", nf, nadj, off); } lo_b = ((off & OFFSET_FRAC_MASK) << OFFSET_FRAC_BITS); hi_b = off >> OFFSET_FRAC_BITS; dev_info(&client->dev, "hi_b:%d, lo_b:%d\n", hi_b, lo_b); r = r ? r : nct1008_write_reg(data, OFFSET_WR, hi_b); r = r ? r : nct1008_write_reg(data, OFFSET_QUARTER_WR, lo_b); return r; } static int nct1008_ext_sensor_init(struct nct1008_data *data) { int ret, val; struct nct1008_platform_data *pdata = &data->plat_data; /* External temperature h/w shutdown limit. */ if (data->sensors[EXT].shutdown_limit != INT_MIN) { val = temperature_to_value(pdata->extended_range, data->sensors[EXT].shutdown_limit); ret = nct1008_write_reg(data, EXT_THERM_LIMIT_WR, val); if (ret) goto err; dev_info(&data->client->dev, "EXT shutdown limit %d", data->sensors[EXT].shutdown_limit); } /* Setup external hi and lo limits */ ret = nct1008_write_reg(data, EXT_TEMP_LO_LIMIT_HI_BYTE_WR, 0); if (ret) goto err; ret = nct1008_write_reg(data, EXT_TEMP_HI_LIMIT_HI_BYTE_WR, NCT1008_MAX_TEMP); err: if (ret < 0) dev_err(&data->client->dev, "EXT sensor init failed 0x%x", ret); return ret; } static int nct1008_loc_sensor_init(struct nct1008_data *data) { int ret = 0, val; struct nct1008_platform_data *pdata = &data->plat_data; /* Local temperature h/w shutdown limit */ if (data->sensors[LOC].shutdown_limit != INT_MIN) { val = temperature_to_value(pdata->extended_range, data->sensors[LOC].shutdown_limit); ret = nct1008_write_reg(data, LOC_THERM_LIMIT, val); if (ret) goto err; dev_info(&data->client->dev, "LOC shutdown limit %d", data->sensors[LOC].shutdown_limit); } /* Setup local hi and lo limits. */ ret = nct1008_write_reg(data, LOC_TEMP_HI_LIMIT_WR, NCT1008_MAX_TEMP); if (ret) goto err; ret = nct1008_write_reg(data, LOC_TEMP_LO_LIMIT_WR, 0); err: if (ret < 0) dev_err(&data->client->dev, "LOC sensor init failed 0x%x", ret); return ret; } static int nct1008_sensors_oneshot(struct nct1008_data *data) { int ret = -ENODEV; int retry; static const int max_poweron_settling_retries = 3; for (retry = 0; retry < max_poweron_settling_retries; retry++) { /* Initiate one-shot conversion */ ret = nct1008_write_reg(data, ONE_SHOT, 0x1); if (ret) goto err; /* Give hardware necessary time to finish conversion */ usleep_range(data->oneshot_conv_period_us, data->oneshot_conv_period_us + 1000); ret = nct1008_read_reg(data, STATUS_RD); if (ret < 0) goto err; data->ext_open = ret & EXT_OPEN_BIT; /* Continue initialization */ if (data->ext_open == 0) break; dev_info(&data->client->dev, "Status = 0x%02x. Retrying.\n", ret); } if (data->ext_open) dev_err(&data->client->dev, "!!!EXT sensor open circuit!!!\n"); err: return ret; } static int nct1008_sensors_init(struct nct1008_data *data) { int ret = -ENODEV; int temp; struct nct1008_platform_data *pdata = &data->plat_data; if (!pdata) goto err; /* Configure sensor to trigger alerts clear THERM2_BIT and ALERT_BIT*/ data->config = 0; /* Initially place in Standby */ ret = nct1008_write_reg(data, CONFIG_WR, STANDBY_BIT); if (ret) goto err; /* Add a delay to make sure it enters into standby mode */ usleep_range(data->oneshot_conv_period_us, data->oneshot_conv_period_us + 1000); ret = nct1008_loc_sensor_init(data); if (ret < 0) goto err; ret = nct1008_ext_sensor_init(data); if (ret < 0) goto err; if (pdata->extended_range) data->config |= EXTENDED_RANGE_BIT; ret = nct1008_write_reg(data, CONFIG_WR, data->config | STANDBY_BIT); if (ret) goto err; /* Temperature conversion rate */ if (pdata->conv_rate >= 0) { ret = nct1008_write_reg(data, CONV_RATE_WR, pdata->conv_rate); if (ret) goto err; } /* Wait for power to settle and EXT sensor to be detected */ ret = nct1008_sensors_oneshot(data); if (ret < 0) goto err; /* read initial local temperature */ ret = nct1008_get_temp_common(LOC, data, &temp); if (ret < 0) goto err; dev_info(&data->client->dev, "initial LOC temp: %d ", temp); /* read initial ext temperature */ if (data->ext_open == 0) { ret = nct1008_get_temp_common(EXT, data, &temp); if (ret < 0) goto err; dev_info(&data->client->dev, "initial EXT temp: %d ", temp); } err: if (ret < 0) dev_err(&data->client->dev, "sensor init failed 0x%x", ret); return ret; } static int nct1008_limits_store(struct nct1008_data *data) { int val; struct nct1008_platform_data *pdata = &data->plat_data; /* Reset current hi/lo limit values with register values */ val = nct1008_read_reg(data, EXT_TEMP_LO_LIMIT_HI_BYTE_RD); if (val < 0) goto err; data->sensors[EXT].current_lo_limit = value_to_temperature(pdata->extended_range, val); val = nct1008_read_reg(data, EXT_TEMP_HI_LIMIT_HI_BYTE_RD); if (val < 0) goto err; data->sensors[EXT].current_hi_limit = value_to_temperature(pdata->extended_range, val); val = nct1008_read_reg(data, LOC_TEMP_LO_LIMIT_RD); if (val < 0) goto err; data->sensors[LOC].current_lo_limit = value_to_temperature(pdata->extended_range, val); val = nct1008_read_reg(data, LOC_TEMP_HI_LIMIT_RD); if (val < 0) goto err; data->sensors[LOC].current_hi_limit = value_to_temperature(pdata->extended_range, val); err: if (val < 0) dev_err(&data->client->dev, "limit reg read failed 0x%x", val); return val; } static int nct1008_configure_sensor(struct nct1008_data *data) { int ret; ret = nct1008_sensors_init(data); if (ret < 0) goto err; ret = nct1008_limits_store(data); if (ret < 0) goto err; if (data->chip != MAX6649) nct1008_offsets_program(data); err: return ret; } static int nct1008_configure_irq(struct nct1008_data *data) { if (data->client->irq < 0) return -EINVAL; data->workqueue = create_singlethread_workqueue(data->chip_name); INIT_WORK(&data->work, nct1008_work_func); return request_irq(data->client->irq, nct1008_irq, IRQF_TRIGGER_NONE, data->chip_name, data); } static int nct1008_dt_parse(struct i2c_client *client, struct nct1008_data *data) { struct device_node *np = client->dev.of_node; struct device_node *child; struct nct1008_platform_data *pdata = &data->plat_data; struct gpio_desc *nct1008_gpio = NULL; unsigned int proc, index = 0; pdata->conv_rate = INT_MIN; data->sensors[LOC].shutdown_limit = INT_MIN; data->sensors[EXT].shutdown_limit = INT_MIN; if (!np) { dev_err(&client->dev, "Cannot find the DT node\n"); return -ENODEV; } dev_info(&client->dev, "starting parse dt\n"); if (client->irq == 0) { client->irq = -1; dev_info(&client->dev, "Missing interrupt prop\n"); } if (of_property_read_u32(np, "conv-rate", &pdata->conv_rate)) dev_info(&client->dev, "Missing conv-rate prop\n"); nct1008_gpio = gpiod_get(&client->dev, "temp-alert", GPIOD_IN); if (IS_ERR(nct1008_gpio)) dev_info(&client->dev, "Missing temp-alert-gpio prop\n"); dev_dbg(&client->dev, "gpio:%d irq:%d\n", desc_to_gpio(nct1008_gpio), gpiod_to_irq(nct1008_gpio)); /* optional properties */ /* Keep property with typo for backward compatibility */ if (!of_property_read_u32(np, "extended-rage", &proc) || !of_property_read_u32(np, "extended-range", &proc)) pdata->extended_range = (proc) ? true : false; pdata->alpha = 10000; pdata->beta = 0; pdata->offset = 0; pdata->fuse_offset = false; if (!of_property_read_u32(np, "alpha", &proc)) pdata->alpha = proc; if (!of_property_read_u32(np, "beta", &proc)) pdata->beta = proc; if (!of_property_read_u32(np, "offset", &proc)) /* offset resolution is 0.25C resolution, TMP451 uses 0.0625C*/ pdata->offset = proc * 4; else if (!of_property_read_u32(np, "offset-hi-res", &proc)) /* high resolution offset 0.0625C*/ pdata->offset = proc; else if (of_property_read_bool(np, "support-fuse-offset")) /* offset present in fuses */ pdata->fuse_offset = true; else dev_info(&client->dev, "programming offset of 0C\n"); /* * This a legacy property that is incorrect because it assumes * the first subnode will be the LOC sensor. This has been * deprecated in favor of the new *-shutdown-limit properties * but is being kept to maintain backward compatibility. */ for_each_child_of_node(np, child) { if (!of_property_read_u32(child, "shutdown-limit", &proc)) data->sensors[index].shutdown_limit = proc; index++; } if (index) dev_info(&client->dev, "!!!Found deprecated property!!!\n"); if (!of_property_read_u32(np, "loc-shutdown-limit", &proc)) data->sensors[LOC].shutdown_limit = proc; if (!of_property_read_u32(np, "ext-shutdown-limit", &proc)) data->sensors[EXT].shutdown_limit = proc; dev_info(&client->dev, "success parsing dt\n"); client->dev.platform_data = pdata; return 0; } static struct thermal_zone_of_device_ops loc_sops = { .get_temp = nct1008_loc_get_temp_as_sensor, .get_trend = nct1008_loc_get_trend_as_sensor, .set_trips = nct1008_loc_set_trips, }; static struct thermal_zone_of_device_ops ext_sops = { .get_temp = nct1008_ext_get_temp_as_sensor, .get_trend = nct1008_ext_get_trend_as_sensor, .set_trips = nct1008_ext_set_trips, }; static void nct1008_thermal_init(struct nct1008_data *data) { struct thermal_zone_device *tzd; struct thermal_cooling_device *cdev; struct device *dev = &data->client->dev; /* Config for the Local sensor. */ tzd = thermal_zone_of_sensor_register(dev, LOC, data, &loc_sops); if (!IS_ERR_OR_NULL(tzd)) data->sensors[LOC].thz = tzd; /* register External sensor if connection is good */ tzd = thermal_zone_of_sensor_register(dev, EXT, data, &ext_sops); if (!IS_ERR_OR_NULL(tzd)) data->sensors[EXT].thz = tzd; cdev = thermal_of_cooling_device_register(dev->of_node, "shutdown_warning", data, &nct1008_shutdown_warning_ops); if (IS_ERR_OR_NULL(cdev)) dev_err(dev, "cdev registration failed %ld\n", PTR_ERR(cdev)); } /* * Manufacturer(OnSemi) recommended sequence for * Extended Range mode is as follows * nct1008_configure_sensor: * 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 * nct1008_enable: * 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) { int err; struct nct1008_data *data; if (!client->dev.of_node) { dev_err(&client->dev, "missing device tree node\n"); return -EINVAL; } dev_dbg(&client->dev, "find device tree node, parsing dt\n"); data = devm_kzalloc(&client->dev, sizeof(struct nct1008_data), GFP_KERNEL); if (!data) return -ENOMEM; err = nct1008_dt_parse(client, data); if (err) return err; data->client = client; data->chip = id->driver_data; strlcpy(data->chip_name, id->name, I2C_NAME_SIZE); i2c_set_clientdata(client, data); mutex_init(&data->mutex); data->nct_reg = regulator_get(&client->dev, "vdd"); if (IS_ERR(data->nct_reg)) { dev_err(&client->dev, "vdd regulator get failed: 0x%x\n", err); mutex_destroy(&data->mutex); return -ENODEV; } /* oneshot conversion time */ if (data->chip == TMP451) data->oneshot_conv_period_us = TMP451_CONV_TIME_ONESHOT_US; else data->oneshot_conv_period_us = NCT_CONV_TIME_ONESHOT_US; nct1008_power_control(data, true); /* sensor is in standby */ err = nct1008_configure_sensor(data); if (err < 0) goto error; err = nct1008_configure_irq(data); if (err < 0) dev_info(&client->dev, "irq config failed: 0x%x ", err); /* sensor is running */ err = nct1008_enable(client); if (err < 0) 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; } dev_info(&client->dev, "%s: initialized\n", __func__); nct1008_thermal_init(data); return 0; error: dev_err(&client->dev, "\n exit %s, err=%d ", __func__, err); mutex_destroy(&data->mutex); nct1008_power_control(data, false); 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); 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); cancel_work_sync(&data->work); if (client->irq) disable_irq(client->irq); if (data->sensors[LOC].thz) { if (client->dev.of_node) thermal_zone_of_sensor_unregister (&(client->dev), data->sensors[LOC].thz); else thermal_zone_device_unregister(data->sensors[LOC].thz); data->sensors[LOC].thz = NULL; } if (data->sensors[EXT].thz) { if (client->dev.of_node) thermal_zone_of_sensor_unregister (&(client->dev), data->sensors[EXT].thz); else thermal_zone_device_unregister(data->sensors[EXT].thz); data->sensors[EXT].thz = NULL; } mutex_lock(&data->mutex); data->nct_disabled = 1; mutex_unlock(&data->mutex); } static int nct1008_suspend(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_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); int err; struct nct1008_data *data = i2c_get_clientdata(client); nct1008_power_control(data, true); err = nct1008_configure_sensor(data); if (err < 0) return err; err = nct1008_enable(client); if (err < 0) return err; 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, }; static const struct i2c_device_id nct1008_id[] = { { "nct1008", NCT1008 }, { "nct72", NCT72 }, { "tmp451", TMP451 }, { "max6649", MAX6649 }, {} }; MODULE_DEVICE_TABLE(i2c, nct1008_id); static const struct of_device_id nct1008_of_match[] = { {.compatible = "onsemi,nct72", }, {.compatible = "ti,tmp451", }, {.compatible = "maxim,max6649", }, { } }; static struct i2c_driver nct1008_driver = { .driver = { .name = "nct1008_nct72", .pm = &nct1008_pm_ops, .of_match_table = nct1008_of_match, .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .probe = nct1008_probe, .remove = nct1008_remove, .id_table = nct1008_id, .shutdown = nct1008_shutdown, }; module_i2c_driver(nct1008_driver); MODULE_AUTHOR("Srikar Srimath Tirumala "); MODULE_DESCRIPTION("Temperature sensor driver for NCT1008/NCT72/TMP451"); MODULE_LICENSE("GPL");