1*4e233cbeSAdrien Demarez /* 2*4e233cbeSAdrien Demarez * LM73 Sensor driver 3*4e233cbeSAdrien Demarez * Based on LM75 4*4e233cbeSAdrien Demarez * 5*4e233cbeSAdrien Demarez * Copyright (C) 2007, CenoSYS (www.cenosys.com). 6*4e233cbeSAdrien Demarez * Copyright (C) 2009, Bollore telecom (www.bolloretelecom.eu). 7*4e233cbeSAdrien Demarez * 8*4e233cbeSAdrien Demarez * Guillaume Ligneul <guillaume.ligneul@gmail.com> 9*4e233cbeSAdrien Demarez * Adrien Demarez <adrien.demarez@bolloretelecom.eu> 10*4e233cbeSAdrien Demarez * Jeremy Laine <jeremy.laine@bolloretelecom.eu> 11*4e233cbeSAdrien Demarez * 12*4e233cbeSAdrien Demarez * This software program is licensed subject to the GNU General Public License 13*4e233cbeSAdrien Demarez * (GPL).Version 2,June 1991, available at 14*4e233cbeSAdrien Demarez * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 15*4e233cbeSAdrien Demarez */ 16*4e233cbeSAdrien Demarez 17*4e233cbeSAdrien Demarez #include <linux/module.h> 18*4e233cbeSAdrien Demarez #include <linux/init.h> 19*4e233cbeSAdrien Demarez #include <linux/slab.h> 20*4e233cbeSAdrien Demarez #include <linux/i2c.h> 21*4e233cbeSAdrien Demarez #include <linux/hwmon.h> 22*4e233cbeSAdrien Demarez #include <linux/hwmon-sysfs.h> 23*4e233cbeSAdrien Demarez #include <linux/err.h> 24*4e233cbeSAdrien Demarez 25*4e233cbeSAdrien Demarez 26*4e233cbeSAdrien Demarez /* Addresses scanned */ 27*4e233cbeSAdrien Demarez static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 28*4e233cbeSAdrien Demarez 0x4d, 0x4e, I2C_CLIENT_END }; 29*4e233cbeSAdrien Demarez 30*4e233cbeSAdrien Demarez /* Insmod parameters */ 31*4e233cbeSAdrien Demarez I2C_CLIENT_INSMOD_1(lm73); 32*4e233cbeSAdrien Demarez 33*4e233cbeSAdrien Demarez /* LM73 registers */ 34*4e233cbeSAdrien Demarez #define LM73_REG_INPUT 0x00 35*4e233cbeSAdrien Demarez #define LM73_REG_CONF 0x01 36*4e233cbeSAdrien Demarez #define LM73_REG_MAX 0x02 37*4e233cbeSAdrien Demarez #define LM73_REG_MIN 0x03 38*4e233cbeSAdrien Demarez #define LM73_REG_CTRL 0x04 39*4e233cbeSAdrien Demarez #define LM73_REG_ID 0x07 40*4e233cbeSAdrien Demarez 41*4e233cbeSAdrien Demarez #define LM73_ID 0x9001 /* or 0x190 after a swab16() */ 42*4e233cbeSAdrien Demarez #define DRVNAME "lm73" 43*4e233cbeSAdrien Demarez #define LM73_TEMP_MIN (-40) 44*4e233cbeSAdrien Demarez #define LM73_TEMP_MAX 150 45*4e233cbeSAdrien Demarez 46*4e233cbeSAdrien Demarez /*-----------------------------------------------------------------------*/ 47*4e233cbeSAdrien Demarez 48*4e233cbeSAdrien Demarez 49*4e233cbeSAdrien Demarez static ssize_t set_temp(struct device *dev, struct device_attribute *da, 50*4e233cbeSAdrien Demarez const char *buf, size_t count) 51*4e233cbeSAdrien Demarez { 52*4e233cbeSAdrien Demarez struct sensor_device_attribute *attr = to_sensor_dev_attr(da); 53*4e233cbeSAdrien Demarez struct i2c_client *client = to_i2c_client(dev); 54*4e233cbeSAdrien Demarez long temp; 55*4e233cbeSAdrien Demarez short value; 56*4e233cbeSAdrien Demarez 57*4e233cbeSAdrien Demarez int status = strict_strtol(buf, 10, &temp); 58*4e233cbeSAdrien Demarez if (status < 0) 59*4e233cbeSAdrien Demarez return status; 60*4e233cbeSAdrien Demarez 61*4e233cbeSAdrien Demarez /* Write value */ 62*4e233cbeSAdrien Demarez value = (short) SENSORS_LIMIT(temp/250, (LM73_TEMP_MIN*4), 63*4e233cbeSAdrien Demarez (LM73_TEMP_MAX*4)) << 5; 64*4e233cbeSAdrien Demarez i2c_smbus_write_word_data(client, attr->index, swab16(value)); 65*4e233cbeSAdrien Demarez return count; 66*4e233cbeSAdrien Demarez } 67*4e233cbeSAdrien Demarez 68*4e233cbeSAdrien Demarez static ssize_t show_temp(struct device *dev, struct device_attribute *da, 69*4e233cbeSAdrien Demarez char *buf) 70*4e233cbeSAdrien Demarez { 71*4e233cbeSAdrien Demarez struct sensor_device_attribute *attr = to_sensor_dev_attr(da); 72*4e233cbeSAdrien Demarez struct i2c_client *client = to_i2c_client(dev); 73*4e233cbeSAdrien Demarez /* use integer division instead of equivalent right shift to 74*4e233cbeSAdrien Demarez guarantee arithmetic shift and preserve the sign */ 75*4e233cbeSAdrien Demarez int temp = ((s16) (swab16(i2c_smbus_read_word_data(client, 76*4e233cbeSAdrien Demarez attr->index)))*250) / 32; 77*4e233cbeSAdrien Demarez return sprintf(buf, "%d\n", temp); 78*4e233cbeSAdrien Demarez } 79*4e233cbeSAdrien Demarez 80*4e233cbeSAdrien Demarez 81*4e233cbeSAdrien Demarez /*-----------------------------------------------------------------------*/ 82*4e233cbeSAdrien Demarez 83*4e233cbeSAdrien Demarez /* sysfs attributes for hwmon */ 84*4e233cbeSAdrien Demarez 85*4e233cbeSAdrien Demarez static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, 86*4e233cbeSAdrien Demarez show_temp, set_temp, LM73_REG_MAX); 87*4e233cbeSAdrien Demarez static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, 88*4e233cbeSAdrien Demarez show_temp, set_temp, LM73_REG_MIN); 89*4e233cbeSAdrien Demarez static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, 90*4e233cbeSAdrien Demarez show_temp, NULL, LM73_REG_INPUT); 91*4e233cbeSAdrien Demarez 92*4e233cbeSAdrien Demarez 93*4e233cbeSAdrien Demarez static struct attribute *lm73_attributes[] = { 94*4e233cbeSAdrien Demarez &sensor_dev_attr_temp1_input.dev_attr.attr, 95*4e233cbeSAdrien Demarez &sensor_dev_attr_temp1_max.dev_attr.attr, 96*4e233cbeSAdrien Demarez &sensor_dev_attr_temp1_min.dev_attr.attr, 97*4e233cbeSAdrien Demarez 98*4e233cbeSAdrien Demarez NULL 99*4e233cbeSAdrien Demarez }; 100*4e233cbeSAdrien Demarez 101*4e233cbeSAdrien Demarez static const struct attribute_group lm73_group = { 102*4e233cbeSAdrien Demarez .attrs = lm73_attributes, 103*4e233cbeSAdrien Demarez }; 104*4e233cbeSAdrien Demarez 105*4e233cbeSAdrien Demarez /*-----------------------------------------------------------------------*/ 106*4e233cbeSAdrien Demarez 107*4e233cbeSAdrien Demarez /* device probe and removal */ 108*4e233cbeSAdrien Demarez 109*4e233cbeSAdrien Demarez static int 110*4e233cbeSAdrien Demarez lm73_probe(struct i2c_client *client, const struct i2c_device_id *id) 111*4e233cbeSAdrien Demarez { 112*4e233cbeSAdrien Demarez struct device *hwmon_dev; 113*4e233cbeSAdrien Demarez int status; 114*4e233cbeSAdrien Demarez 115*4e233cbeSAdrien Demarez /* Register sysfs hooks */ 116*4e233cbeSAdrien Demarez status = sysfs_create_group(&client->dev.kobj, &lm73_group); 117*4e233cbeSAdrien Demarez if (status) 118*4e233cbeSAdrien Demarez return status; 119*4e233cbeSAdrien Demarez 120*4e233cbeSAdrien Demarez hwmon_dev = hwmon_device_register(&client->dev); 121*4e233cbeSAdrien Demarez if (IS_ERR(hwmon_dev)) { 122*4e233cbeSAdrien Demarez status = PTR_ERR(hwmon_dev); 123*4e233cbeSAdrien Demarez goto exit_remove; 124*4e233cbeSAdrien Demarez } 125*4e233cbeSAdrien Demarez i2c_set_clientdata(client, hwmon_dev); 126*4e233cbeSAdrien Demarez 127*4e233cbeSAdrien Demarez dev_info(&client->dev, "%s: sensor '%s'\n", 128*4e233cbeSAdrien Demarez dev_name(hwmon_dev), client->name); 129*4e233cbeSAdrien Demarez 130*4e233cbeSAdrien Demarez return 0; 131*4e233cbeSAdrien Demarez 132*4e233cbeSAdrien Demarez exit_remove: 133*4e233cbeSAdrien Demarez sysfs_remove_group(&client->dev.kobj, &lm73_group); 134*4e233cbeSAdrien Demarez return status; 135*4e233cbeSAdrien Demarez } 136*4e233cbeSAdrien Demarez 137*4e233cbeSAdrien Demarez static int lm73_remove(struct i2c_client *client) 138*4e233cbeSAdrien Demarez { 139*4e233cbeSAdrien Demarez struct device *hwmon_dev = i2c_get_clientdata(client); 140*4e233cbeSAdrien Demarez 141*4e233cbeSAdrien Demarez hwmon_device_unregister(hwmon_dev); 142*4e233cbeSAdrien Demarez sysfs_remove_group(&client->dev.kobj, &lm73_group); 143*4e233cbeSAdrien Demarez i2c_set_clientdata(client, NULL); 144*4e233cbeSAdrien Demarez return 0; 145*4e233cbeSAdrien Demarez } 146*4e233cbeSAdrien Demarez 147*4e233cbeSAdrien Demarez static const struct i2c_device_id lm73_ids[] = { 148*4e233cbeSAdrien Demarez { "lm73", lm73 }, 149*4e233cbeSAdrien Demarez { /* LIST END */ } 150*4e233cbeSAdrien Demarez }; 151*4e233cbeSAdrien Demarez MODULE_DEVICE_TABLE(i2c, lm73_ids); 152*4e233cbeSAdrien Demarez 153*4e233cbeSAdrien Demarez /* Return 0 if detection is successful, -ENODEV otherwise */ 154*4e233cbeSAdrien Demarez static int lm73_detect(struct i2c_client *new_client, int kind, 155*4e233cbeSAdrien Demarez struct i2c_board_info *info) 156*4e233cbeSAdrien Demarez { 157*4e233cbeSAdrien Demarez struct i2c_adapter *adapter = new_client->adapter; 158*4e233cbeSAdrien Demarez u16 id; 159*4e233cbeSAdrien Demarez u8 ctrl; 160*4e233cbeSAdrien Demarez 161*4e233cbeSAdrien Demarez if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | 162*4e233cbeSAdrien Demarez I2C_FUNC_SMBUS_WORD_DATA)) 163*4e233cbeSAdrien Demarez return -ENODEV; 164*4e233cbeSAdrien Demarez 165*4e233cbeSAdrien Demarez /* Check device ID */ 166*4e233cbeSAdrien Demarez id = i2c_smbus_read_word_data(new_client, LM73_REG_ID); 167*4e233cbeSAdrien Demarez ctrl = i2c_smbus_read_byte_data(new_client, LM73_REG_CTRL); 168*4e233cbeSAdrien Demarez if ((id != LM73_ID) || (ctrl & 0x10)) 169*4e233cbeSAdrien Demarez return -ENODEV; 170*4e233cbeSAdrien Demarez 171*4e233cbeSAdrien Demarez strlcpy(info->type, "lm73", I2C_NAME_SIZE); 172*4e233cbeSAdrien Demarez 173*4e233cbeSAdrien Demarez return 0; 174*4e233cbeSAdrien Demarez } 175*4e233cbeSAdrien Demarez 176*4e233cbeSAdrien Demarez static struct i2c_driver lm73_driver = { 177*4e233cbeSAdrien Demarez .class = I2C_CLASS_HWMON, 178*4e233cbeSAdrien Demarez .driver = { 179*4e233cbeSAdrien Demarez .name = "lm73", 180*4e233cbeSAdrien Demarez }, 181*4e233cbeSAdrien Demarez .probe = lm73_probe, 182*4e233cbeSAdrien Demarez .remove = lm73_remove, 183*4e233cbeSAdrien Demarez .id_table = lm73_ids, 184*4e233cbeSAdrien Demarez .detect = lm73_detect, 185*4e233cbeSAdrien Demarez .address_data = &addr_data, 186*4e233cbeSAdrien Demarez }; 187*4e233cbeSAdrien Demarez 188*4e233cbeSAdrien Demarez /* module glue */ 189*4e233cbeSAdrien Demarez 190*4e233cbeSAdrien Demarez static int __init sensors_lm73_init(void) 191*4e233cbeSAdrien Demarez { 192*4e233cbeSAdrien Demarez return i2c_add_driver(&lm73_driver); 193*4e233cbeSAdrien Demarez } 194*4e233cbeSAdrien Demarez 195*4e233cbeSAdrien Demarez static void __exit sensors_lm73_exit(void) 196*4e233cbeSAdrien Demarez { 197*4e233cbeSAdrien Demarez i2c_del_driver(&lm73_driver); 198*4e233cbeSAdrien Demarez } 199*4e233cbeSAdrien Demarez 200*4e233cbeSAdrien Demarez MODULE_AUTHOR("Guillaume Ligneul <guillaume.ligneul@gmail.com>"); 201*4e233cbeSAdrien Demarez MODULE_DESCRIPTION("LM73 driver"); 202*4e233cbeSAdrien Demarez MODULE_LICENSE("GPL"); 203*4e233cbeSAdrien Demarez 204*4e233cbeSAdrien Demarez module_init(sensors_lm73_init); 205*4e233cbeSAdrien Demarez module_exit(sensors_lm73_exit); 206