14e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/* 24e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * LM73 Sensor driver 34e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * Based on LM75 44e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * 54e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * Copyright (C) 2007, CenoSYS (www.cenosys.com). 64e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * Copyright (C) 2009, Bollore telecom (www.bolloretelecom.eu). 74e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * 84e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * Guillaume Ligneul <guillaume.ligneul@gmail.com> 94e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * Adrien Demarez <adrien.demarez@bolloretelecom.eu> 104e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * Jeremy Laine <jeremy.laine@bolloretelecom.eu> 114e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * 124e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * This software program is licensed subject to the GNU General Public License 134e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * (GPL).Version 2,June 1991, available at 144e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 154e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez */ 164e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 174e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#include <linux/module.h> 184e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#include <linux/init.h> 194e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#include <linux/i2c.h> 204e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#include <linux/hwmon.h> 214e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#include <linux/hwmon-sysfs.h> 224e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#include <linux/err.h> 234e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 244e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 254e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/* Addresses scanned */ 264e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 274e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 0x4d, 0x4e, I2C_CLIENT_END }; 284e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 294e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/* LM73 registers */ 304e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_REG_INPUT 0x00 314e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_REG_CONF 0x01 324e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_REG_MAX 0x02 334e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_REG_MIN 0x03 344e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_REG_CTRL 0x04 354e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_REG_ID 0x07 364e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 3790f4102ce59226954edbe960b2434d8b3da5f086Jean Delvare#define LM73_ID 0x9001 /* 0x0190, byte-swapped */ 384e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define DRVNAME "lm73" 394e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_TEMP_MIN (-40) 404e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez#define LM73_TEMP_MAX 150 414e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 424e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/*-----------------------------------------------------------------------*/ 434e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 444e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 454e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic ssize_t set_temp(struct device *dev, struct device_attribute *da, 464e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez const char *buf, size_t count) 474e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez{ 484e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct sensor_device_attribute *attr = to_sensor_dev_attr(da); 494e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct i2c_client *client = to_i2c_client(dev); 504e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez long temp; 514e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez short value; 524e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 53179c4fdb565dd2157e5dfe89318b74868e3b523dFrans Meulenbroeks int status = kstrtol(buf, 10, &temp); 544e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez if (status < 0) 554e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return status; 564e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 574e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez /* Write value */ 584e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez value = (short) SENSORS_LIMIT(temp/250, (LM73_TEMP_MIN*4), 594e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez (LM73_TEMP_MAX*4)) << 5; 6090f4102ce59226954edbe960b2434d8b3da5f086Jean Delvare i2c_smbus_write_word_swapped(client, attr->index, value); 614e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return count; 624e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez} 634e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 644e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic ssize_t show_temp(struct device *dev, struct device_attribute *da, 654e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez char *buf) 664e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez{ 674e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct sensor_device_attribute *attr = to_sensor_dev_attr(da); 684e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct i2c_client *client = to_i2c_client(dev); 694e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez /* use integer division instead of equivalent right shift to 704e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez guarantee arithmetic shift and preserve the sign */ 7190f4102ce59226954edbe960b2434d8b3da5f086Jean Delvare int temp = ((s16) (i2c_smbus_read_word_swapped(client, 7290f4102ce59226954edbe960b2434d8b3da5f086Jean Delvare attr->index))*250) / 32; 734e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return sprintf(buf, "%d\n", temp); 744e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez} 754e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 764e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 774e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/*-----------------------------------------------------------------------*/ 784e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 794e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/* sysfs attributes for hwmon */ 804e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 814e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, 824e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez show_temp, set_temp, LM73_REG_MAX); 834e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, 844e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez show_temp, set_temp, LM73_REG_MIN); 854e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, 864e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez show_temp, NULL, LM73_REG_INPUT); 874e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 884e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 894e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic struct attribute *lm73_attributes[] = { 904e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez &sensor_dev_attr_temp1_input.dev_attr.attr, 914e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez &sensor_dev_attr_temp1_max.dev_attr.attr, 924e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez &sensor_dev_attr_temp1_min.dev_attr.attr, 934e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 944e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez NULL 954e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez}; 964e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 974e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic const struct attribute_group lm73_group = { 984e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .attrs = lm73_attributes, 994e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez}; 1004e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1014e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/*-----------------------------------------------------------------------*/ 1024e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1034e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/* device probe and removal */ 1044e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1054e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic int 1064e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezlm73_probe(struct i2c_client *client, const struct i2c_device_id *id) 1074e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez{ 1084e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct device *hwmon_dev; 1094e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez int status; 1104e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1114e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez /* Register sysfs hooks */ 1124e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez status = sysfs_create_group(&client->dev.kobj, &lm73_group); 1134e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez if (status) 1144e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return status; 1154e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1164e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez hwmon_dev = hwmon_device_register(&client->dev); 1174e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez if (IS_ERR(hwmon_dev)) { 1184e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez status = PTR_ERR(hwmon_dev); 1194e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez goto exit_remove; 1204e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez } 1214e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez i2c_set_clientdata(client, hwmon_dev); 1224e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1234e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez dev_info(&client->dev, "%s: sensor '%s'\n", 1244e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez dev_name(hwmon_dev), client->name); 1254e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1264e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return 0; 1274e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1284e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezexit_remove: 1294e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez sysfs_remove_group(&client->dev.kobj, &lm73_group); 1304e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return status; 1314e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez} 1324e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1334e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic int lm73_remove(struct i2c_client *client) 1344e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez{ 1354e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct device *hwmon_dev = i2c_get_clientdata(client); 1364e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1374e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez hwmon_device_unregister(hwmon_dev); 1384e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez sysfs_remove_group(&client->dev.kobj, &lm73_group); 1394e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return 0; 1404e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez} 1414e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1424e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic const struct i2c_device_id lm73_ids[] = { 1431f86df49ddfd0067cce941187d57b2fd2f749a9eJean Delvare { "lm73", 0 }, 1444e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez { /* LIST END */ } 1454e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez}; 1464e233cbed249ea94d989b8be08eac0414dbdc44bAdrien DemarezMODULE_DEVICE_TABLE(i2c, lm73_ids); 1474e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1484e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez/* Return 0 if detection is successful, -ENODEV otherwise */ 149310ec79210d754afe51e2e4a983e846b60179abdJean Delvarestatic int lm73_detect(struct i2c_client *new_client, 1504e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct i2c_board_info *info) 1514e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez{ 1524e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez struct i2c_adapter *adapter = new_client->adapter; 15324d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare int id, ctrl, conf; 1544e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1554e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | 1564e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez I2C_FUNC_SMBUS_WORD_DATA)) 1574e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return -ENODEV; 1584e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 15924d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare /* 16024d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare * Do as much detection as possible with byte reads first, as word 16124d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare * reads can confuse other devices. 16224d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare */ 16324d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare ctrl = i2c_smbus_read_byte_data(new_client, LM73_REG_CTRL); 16424d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare if (ctrl < 0 || (ctrl & 0x10)) 16524d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare return -ENODEV; 16624d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare 16724d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare conf = i2c_smbus_read_byte_data(new_client, LM73_REG_CONF); 16824d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare if (conf < 0 || (conf & 0x0c)) 16924d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare return -ENODEV; 17024d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare 17124d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare id = i2c_smbus_read_byte_data(new_client, LM73_REG_ID); 17224d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare if (id < 0 || id != (LM73_ID & 0xff)) 17324d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare return -ENODEV; 17424d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare 1754e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez /* Check device ID */ 1764e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez id = i2c_smbus_read_word_data(new_client, LM73_REG_ID); 17724d6e2a89a1ff0a035f163a83a2812a3192083b6Jean Delvare if (id < 0 || id != LM73_ID) 1784e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return -ENODEV; 1794e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1804e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez strlcpy(info->type, "lm73", I2C_NAME_SIZE); 1814e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1824e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez return 0; 1834e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez} 1844e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1854e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarezstatic struct i2c_driver lm73_driver = { 1864e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .class = I2C_CLASS_HWMON, 1874e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .driver = { 1884e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .name = "lm73", 1894e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez }, 1904e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .probe = lm73_probe, 1914e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .remove = lm73_remove, 1924e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .id_table = lm73_ids, 1934e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez .detect = lm73_detect, 194c3813d6af177fab19e322f3114b1f64fbcf08d71Jean Delvare .address_list = normal_i2c, 1954e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez}; 1964e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 197f0967eea80ec2a19a4fe1ad27e3ff1b22c79a3c7Axel Linmodule_i2c_driver(lm73_driver); 1984e233cbed249ea94d989b8be08eac0414dbdc44bAdrien Demarez 1994e233cbed249ea94d989b8be08eac0414dbdc44bAdrien DemarezMODULE_AUTHOR("Guillaume Ligneul <guillaume.ligneul@gmail.com>"); 2004e233cbed249ea94d989b8be08eac0414dbdc44bAdrien DemarezMODULE_DESCRIPTION("LM73 driver"); 2014e233cbed249ea94d989b8be08eac0414dbdc44bAdrien DemarezMODULE_LICENSE("GPL"); 202