/* * Temperature Monitor Driver * * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * drivers/hwmon/tmon-tmp411.c * */ /* Note: Copied temperature conversion code from TMP411 driver */ #include #include #include #include #include #include #include #include #define STATUS_REG 0x02 #define CONFIG_REG_READ 0x03 #define CONFIG_REG_WRITE 0x09 #define CONVERSION_REG_READ 0x04 #define CONVERSION_REG_WRITE 0x0a #define TMON_OFFSET 0x40 #define TMON_STANDBY_MASK 0x40 #define MSB_LTEMP_REG 0x00 #define LSB_LTEMP_REG 0x15 #define MSB_RTEMP_REG 0x01 #define LSB_RTEMP_REG 0x10 #define CONFIG_RANGE 0x04 #define DEFAULT_TMON_POLLING_TIME 2000 /* Time in ms */ #define DEFAULT_TMON_DELTA_TEMP 4000 /* Temp. change to execute platform callback */ #define TMON_ERR INT_MAX #define TMON_NOCHANGE (INT_MAX - 1) static const u8 TMP411_TEMP_LOW_LIMIT_MSB_READ[2] = { 0x06, 0x08 }; static const u8 TMP411_TEMP_LOW_LIMIT_MSB_WRITE[2] = { 0x0C, 0x0E }; static const u8 TMP411_TEMP_LOW_LIMIT_LSB[2] = { 0x17, 0x14 }; static const u8 TMP411_TEMP_HIGH_LIMIT_MSB_READ[2] = { 0x05, 0x07 }; static const u8 TMP411_TEMP_HIGH_LIMIT_MSB_WRITE[2] = { 0x0B, 0x0D }; static const u8 TMP411_TEMP_HIGH_LIMIT_LSB[2] = { 0x16, 0x13 }; /* These are called the THERM limit / hysteresis / mask in the datasheet */ static const u8 TMP411_TEMP_CRIT_LIMIT[2] = { 0x20, 0x19 }; static u16 temp_low_limit[2]; static u16 temp_high_limit[2]; static u8 temp_crit_limit[2]; static u8 conv_rate; struct tmon_info { int mode; struct i2c_client *client; struct delayed_work tmon_work; struct tmon_plat_data *pdata; }; #define device_attr(type) \ static struct device_attribute dev_attr_##type = { \ .attr = {.name = __stringify(type), \ .mode = S_IWUSR | S_IRUGO }, \ .show = show_##type, \ } static int tmon_read(struct i2c_client *client, u8 reg, u8 *value) { int tmp; tmp = i2c_smbus_read_byte_data(client, reg); if (tmp < 0) return -EINVAL; *value = tmp; return 0; } static int tmon_to_temp(u16 reg, u8 config) { int temp = reg; if (config & CONFIG_RANGE) temp -= 64 * 256; return (temp * 625 + 80) / 160; } static int tmon_read_remote_temp(struct i2c_client *client, int *ptemp) { u8 config; u8 temp; int err; int temperature = 0; struct tmon_info *data = i2c_get_clientdata(client); err = tmon_read(client, CONFIG_REG_READ, &config); if (err) return err; err = tmon_read(client, MSB_RTEMP_REG, &temp); if (err) return err; temperature = temp << 8; err = tmon_read(client, LSB_RTEMP_REG, &temp); if (err) return err; temperature |= temp; if (data->pdata) { *ptemp = tmon_to_temp(temperature, config) + data->pdata->remote_offset; } else { return -EINVAL; } return 0; } static int tmon_read_local_temp(struct i2c_client *client, int *ptemp) { u8 config; u8 temp; int err; int temperature = 0; err = tmon_read(client, CONFIG_REG_READ, &config); if (err) return err; err = tmon_read(client, MSB_LTEMP_REG, &temp); if (err) return err; temperature = temp << 8; err = tmon_read(client, LSB_LTEMP_REG, &temp); if (err) return err; temperature |= temp; *ptemp = tmon_to_temp(temperature, config); return 0; } static int tmon_check_local_temp(struct i2c_client *client, u32 delta_temp) { static int last_temp; int err; int curr_temp = 0; err = tmon_read_local_temp(client, &curr_temp); if (err) return TMON_ERR; if (abs(curr_temp - last_temp) >= delta_temp) { last_temp = curr_temp; return curr_temp; } return TMON_NOCHANGE; } static int tmon_check_remote_temp(struct i2c_client *client, u32 delta_temp) { static int last_temp; int err; int curr_temp = 0; err = tmon_read_remote_temp(client, &curr_temp); if (err) return TMON_ERR; if (abs(curr_temp - last_temp) >= delta_temp) { last_temp = curr_temp; return curr_temp; } return TMON_NOCHANGE; } static ssize_t show_remote_temp(struct device *dev, struct device_attribute *devattr, char *buf) { int temperature = 0; tmon_read_remote_temp(to_i2c_client(dev), &temperature); return sprintf(buf, "%d\n", temperature); } static ssize_t show_local_temp(struct device *dev, struct device_attribute *devattr, char *buf) { int temperature = 0; tmon_read_local_temp(to_i2c_client(dev), &temperature); return sprintf(buf, "%d\n", temperature); } device_attr(remote_temp); device_attr(local_temp); static void tmon_update(struct work_struct *work) { int ret; struct tmon_info *tmon_data = container_of(to_delayed_work(work), struct tmon_info, tmon_work); struct tmon_plat_data *pdata = tmon_data->pdata; if (pdata->delta_time <= 0) pdata->delta_time = DEFAULT_TMON_POLLING_TIME; if (pdata->delta_temp <= 0) pdata->delta_temp = DEFAULT_TMON_DELTA_TEMP; ret = tmon_check_local_temp(tmon_data->client, pdata->delta_temp); if (ret != TMON_ERR && ret != TMON_NOCHANGE && pdata->ltemp_dependent_reg_update) pdata->ltemp_dependent_reg_update(ret); ret = tmon_check_remote_temp(tmon_data->client, pdata->delta_temp); if (ret != TMON_ERR && ret != TMON_NOCHANGE && pdata->utmip_temp_dep_update) pdata->utmip_temp_dep_update(ret, pdata->utmip_temp_bound); schedule_delayed_work(&tmon_data->tmon_work, msecs_to_jiffies(pdata->delta_time)); } static int __devinit tmon_tmp411_probe(struct i2c_client *client, const struct i2c_device_id *id) { int err; struct tmon_plat_data *tmon_pdata = client->dev.platform_data; struct tmon_info *data; if (tmon_pdata == NULL) { dev_err(&client->dev, "no platform data\n"); return -EINVAL; } if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { dev_err(&client->dev, "insufficient functionality!\n"); return -ENODEV; } data = kzalloc(sizeof(struct tmon_info), GFP_KERNEL); if (!data) return -ENOMEM; data->client = client; i2c_set_clientdata(client, data); data->pdata = tmon_pdata; err = device_create_file(&client->dev, &dev_attr_local_temp); if (err) { kfree(data); return err; } err = device_create_file(&client->dev, &dev_attr_remote_temp); if (err) { kfree(data); device_remove_file(&client->dev, &dev_attr_local_temp); return err; } INIT_DELAYED_WORK(&data->tmon_work, tmon_update); schedule_delayed_work(&data->tmon_work, msecs_to_jiffies(data->pdata->delta_time)); dev_info(&client->dev, "Temperature Monitor enabled\n"); return 0; } static int __devexit tmon_tmp411_remove(struct i2c_client *client) { struct tmon_info *data = i2c_get_clientdata(client); cancel_delayed_work(&data->tmon_work); device_remove_file(&client->dev, &dev_attr_remote_temp); device_remove_file(&client->dev, &dev_attr_local_temp); kfree(data); return 0; } #ifdef CONFIG_PM_SLEEP static int tmon_tmp411_suspend(struct device *dev) { int i; struct i2c_client *client = to_i2c_client(dev); struct tmon_info *data = i2c_get_clientdata(client); /* save temperature limits for restore during resume */ for (i = 0; i < 2; i++) { /* * High byte must be read first immediately followed * by the low byte */ temp_low_limit[i] = i2c_smbus_read_byte_data(client, TMP411_TEMP_LOW_LIMIT_MSB_READ[i]) << 8; temp_low_limit[i] |= i2c_smbus_read_byte_data(client, TMP411_TEMP_LOW_LIMIT_LSB[i]); temp_high_limit[i] = i2c_smbus_read_byte_data(client, TMP411_TEMP_HIGH_LIMIT_MSB_READ[i]) << 8; temp_high_limit[i] |= i2c_smbus_read_byte_data(client, TMP411_TEMP_HIGH_LIMIT_LSB[i]); temp_crit_limit[i] = i2c_smbus_read_byte_data(client, TMP411_TEMP_CRIT_LIMIT[i]); } conv_rate = i2c_smbus_read_byte_data(client, CONVERSION_REG_READ); cancel_delayed_work_sync(&data->tmon_work); return 0; } static int tmon_tmp411_resume(struct device *dev) { int i; struct i2c_client *client = to_i2c_client(dev); struct tmon_info *data = i2c_get_clientdata(client); /* Restore temperature limits */ for (i = 0; i < 2; i++) { i2c_smbus_write_byte_data(client, TMP411_TEMP_HIGH_LIMIT_MSB_WRITE[i], temp_high_limit[i] >> 8); i2c_smbus_write_byte_data(client, TMP411_TEMP_HIGH_LIMIT_LSB[i], temp_high_limit[i] & 0xFF); i2c_smbus_write_byte_data(client, TMP411_TEMP_LOW_LIMIT_MSB_WRITE[i], temp_low_limit[i] >> 8); i2c_smbus_write_byte_data(client, TMP411_TEMP_LOW_LIMIT_LSB[i], temp_low_limit[i] & 0xFF); i2c_smbus_write_byte_data(client, TMP411_TEMP_CRIT_LIMIT[i], temp_crit_limit[i]); } i2c_smbus_write_byte_data(client, CONVERSION_REG_WRITE, conv_rate); schedule_delayed_work(&data->tmon_work, msecs_to_jiffies(data->pdata->delta_time)); return 0; } #endif static const struct dev_pm_ops tegra_tmp411_dev_pm_ops = { #ifdef CONFIG_PM_SLEEP .suspend = tmon_tmp411_suspend, .resume = tmon_tmp411_resume, #endif }; /* tmon-tmp411 driver struct */ static const struct i2c_device_id tmon_tmp411_id[] = { {"tmon-tmp411-sensor", 0}, {} }; MODULE_DEVICE_TABLE(i2c, tmon_tmp411_id); static struct i2c_driver tmon_tmp411_driver = { .driver = { .name = "tmon-tmp411-sensor", #ifdef CONFIG_PM_SLEEP .pm = &tegra_tmp411_dev_pm_ops, #endif }, .probe = tmon_tmp411_probe, .remove = __devexit_p(tmon_tmp411_remove), .id_table = tmon_tmp411_id, }; static int __init tmon_tmp411_module_init(void) { return i2c_add_driver(&tmon_tmp411_driver); } static void __exit tmon_tmp411_module_exit(void) { i2c_del_driver(&tmon_tmp411_driver); } module_init(tmon_tmp411_module_init); module_exit(tmon_tmp411_module_exit); MODULE_AUTHOR("Manoj Chourasia "); MODULE_DESCRIPTION("Temperature Monitor module"); MODULE_LICENSE("GPL");