lm83.c 12.7 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3
/*
 * lm83.c - Part of lm_sensors, Linux kernel modules for hardware
 *          monitoring
4
 * Copyright (C) 2003-2009  Jean Delvare <khali@linux-fr.org>
Linus Torvalds's avatar
Linus Torvalds committed
5 6 7 8 9 10 11 12 13 14
 *
 * Heavily inspired from the lm78, lm75 and adm1021 drivers. The LM83 is
 * a sensor chip made by National Semiconductor. It reports up to four
 * temperatures (its own plus up to three external ones) with a 1 deg
 * resolution and a 3-4 deg accuracy. Complete datasheet can be obtained
 * from National's website at:
 *   http://www.national.com/pf/LM/LM83.html
 * Since the datasheet omits to give the chip stepping code, I give it
 * here: 0x03 (at register 0xff).
 *
Jordan Crouse's avatar
Jordan Crouse committed
15 16 17 18
 * Also supports the LM82 temp sensor, which is basically a stripped down
 * model of the LM83.  Datasheet is here:
 * http://www.national.com/pf/LM/LM82.html
 *
Linus Torvalds's avatar
Linus Torvalds committed
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
39
#include <linux/hwmon-sysfs.h>
40 41
#include <linux/hwmon.h>
#include <linux/err.h>
42
#include <linux/mutex.h>
43
#include <linux/sysfs.h>
Linus Torvalds's avatar
Linus Torvalds committed
44 45 46 47 48 49 50

/*
 * Addresses to scan
 * Address is selected using 2 three-level pins, resulting in 9 possible
 * addresses.
 */

51 52
static const unsigned short normal_i2c[] = {
	0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END };
Linus Torvalds's avatar
Linus Torvalds committed
53

54
enum chips { lm83, lm82 };
Linus Torvalds's avatar
Linus Torvalds committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

/*
 * The LM83 registers
 * Manufacturer ID is 0x01 for National Semiconductor.
 */

#define LM83_REG_R_MAN_ID		0xFE
#define LM83_REG_R_CHIP_ID		0xFF
#define LM83_REG_R_CONFIG		0x03
#define LM83_REG_W_CONFIG		0x09
#define LM83_REG_R_STATUS1		0x02
#define LM83_REG_R_STATUS2		0x35
#define LM83_REG_R_LOCAL_TEMP		0x00
#define LM83_REG_R_LOCAL_HIGH		0x05
#define LM83_REG_W_LOCAL_HIGH		0x0B
#define LM83_REG_R_REMOTE1_TEMP		0x30
#define LM83_REG_R_REMOTE1_HIGH		0x38
#define LM83_REG_W_REMOTE1_HIGH		0x50
#define LM83_REG_R_REMOTE2_TEMP		0x01
#define LM83_REG_R_REMOTE2_HIGH		0x07
#define LM83_REG_W_REMOTE2_HIGH		0x0D
#define LM83_REG_R_REMOTE3_TEMP		0x31
#define LM83_REG_R_REMOTE3_HIGH		0x3A
#define LM83_REG_W_REMOTE3_HIGH		0x52
#define LM83_REG_R_TCRIT		0x42
#define LM83_REG_W_TCRIT		0x5A

/*
 * Conversions and various macros
84
 * The LM83 uses signed 8-bit values with LSB = 1 degree Celsius.
Linus Torvalds's avatar
Linus Torvalds committed
85 86 87 88 89 90 91 92 93 94 95 96
 */

#define TEMP_FROM_REG(val)	((val) * 1000)
#define TEMP_TO_REG(val)	((val) <= -128000 ? -128 : \
				 (val) >= 127000 ? 127 : \
				 (val) < 0 ? ((val) - 500) / 1000 : \
				 ((val) + 500) / 1000)

static const u8 LM83_REG_R_TEMP[] = {
	LM83_REG_R_LOCAL_TEMP,
	LM83_REG_R_REMOTE1_TEMP,
	LM83_REG_R_REMOTE2_TEMP,
97
	LM83_REG_R_REMOTE3_TEMP,
Linus Torvalds's avatar
Linus Torvalds committed
98 99 100
	LM83_REG_R_LOCAL_HIGH,
	LM83_REG_R_REMOTE1_HIGH,
	LM83_REG_R_REMOTE2_HIGH,
101 102
	LM83_REG_R_REMOTE3_HIGH,
	LM83_REG_R_TCRIT,
Linus Torvalds's avatar
Linus Torvalds committed
103 104 105 106 107 108
};

static const u8 LM83_REG_W_HIGH[] = {
	LM83_REG_W_LOCAL_HIGH,
	LM83_REG_W_REMOTE1_HIGH,
	LM83_REG_W_REMOTE2_HIGH,
109 110
	LM83_REG_W_REMOTE3_HIGH,
	LM83_REG_W_TCRIT,
Linus Torvalds's avatar
Linus Torvalds committed
111 112 113 114 115 116
};

/*
 * Functions declaration
 */

117
static int lm83_detect(struct i2c_client *new_client,
118 119 120 121
		       struct i2c_board_info *info);
static int lm83_probe(struct i2c_client *client,
		      const struct i2c_device_id *id);
static int lm83_remove(struct i2c_client *client);
Linus Torvalds's avatar
Linus Torvalds committed
122 123 124 125 126 127
static struct lm83_data *lm83_update_device(struct device *dev);

/*
 * Driver data (common to all clients)
 */
 
128 129 130 131 132 133 134
static const struct i2c_device_id lm83_id[] = {
	{ "lm83", lm83 },
	{ "lm82", lm82 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, lm83_id);

Linus Torvalds's avatar
Linus Torvalds committed
135
static struct i2c_driver lm83_driver = {
136
	.class		= I2C_CLASS_HWMON,
137 138 139
	.driver = {
		.name	= "lm83",
	},
140 141 142 143
	.probe		= lm83_probe,
	.remove		= lm83_remove,
	.id_table	= lm83_id,
	.detect		= lm83_detect,
144
	.address_list	= normal_i2c,
Linus Torvalds's avatar
Linus Torvalds committed
145 146 147 148 149 150 151
};

/*
 * Client data (each client gets its own)
 */

struct lm83_data {
152
	struct device *hwmon_dev;
153
	struct mutex update_lock;
Linus Torvalds's avatar
Linus Torvalds committed
154 155 156 157
	char valid; /* zero until following fields are valid */
	unsigned long last_updated; /* in jiffies */

	/* registers values */
158 159 160
	s8 temp[9];	/* 0..3: input 1-4,
			   4..7: high limit 1-4,
			   8   : critical limit */
Linus Torvalds's avatar
Linus Torvalds committed
161 162 163 164 165 166 167
	u16 alarms; /* bitvector, combined */
};

/*
 * Sysfs stuff
 */

168 169 170 171 172 173
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
			 char *buf)
{
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	struct lm83_data *data = lm83_update_device(dev);
	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index]));
Linus Torvalds's avatar
Linus Torvalds committed
174
}
175 176 177 178 179 180 181 182 183 184

static ssize_t set_temp(struct device *dev, struct device_attribute *devattr,
			const char *buf, size_t count)
{
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	struct i2c_client *client = to_i2c_client(dev);
	struct lm83_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	int nr = attr->index;

185
	mutex_lock(&data->update_lock);
186 187 188
	data->temp[nr] = TEMP_TO_REG(val);
	i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr - 4],
				  data->temp[nr]);
189
	mutex_unlock(&data->update_lock);
190
	return count;
Linus Torvalds's avatar
Linus Torvalds committed
191 192
}

193 194
static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy,
			   char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
195 196 197 198 199
{
	struct lm83_data *data = lm83_update_device(dev);
	return sprintf(buf, "%d\n", data->alarms);
}

200 201 202 203 204 205 206 207 208 209
static ssize_t show_alarm(struct device *dev, struct device_attribute
			  *devattr, char *buf)
{
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	struct lm83_data *data = lm83_update_device(dev);
	int bitnr = attr->index;

	return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
}

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1);
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2);
static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3);
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp,
	set_temp, 4);
static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp,
	set_temp, 5);
static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp,
	set_temp, 6);
static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_temp,
	set_temp, 7);
static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL, 8);
static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp, NULL, 8);
static SENSOR_DEVICE_ATTR(temp3_crit, S_IWUSR | S_IRUGO, show_temp,
	set_temp, 8);
static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp, NULL, 8);
227 228 229 230

/* Individual alarm files */
static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 0);
static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, 1);
231
static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2);
232 233 234 235
static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 4);
static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6);
static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 8);
static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, show_alarm, NULL, 9);
236
static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, 10);
237
static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 12);
238
static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 13);
239 240
static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 15);
/* Raw alarm file for compatibility */
Linus Torvalds's avatar
Linus Torvalds committed
241 242
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);

243 244 245 246 247 248 249 250 251 252
static struct attribute *lm83_attributes[] = {
	&sensor_dev_attr_temp1_input.dev_attr.attr,
	&sensor_dev_attr_temp3_input.dev_attr.attr,
	&sensor_dev_attr_temp1_max.dev_attr.attr,
	&sensor_dev_attr_temp3_max.dev_attr.attr,
	&sensor_dev_attr_temp1_crit.dev_attr.attr,
	&sensor_dev_attr_temp3_crit.dev_attr.attr,

	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
	&sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
253
	&sensor_dev_attr_temp3_fault.dev_attr.attr,
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	&sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
	&dev_attr_alarms.attr,
	NULL
};

static const struct attribute_group lm83_group = {
	.attrs = lm83_attributes,
};

static struct attribute *lm83_attributes_opt[] = {
	&sensor_dev_attr_temp2_input.dev_attr.attr,
	&sensor_dev_attr_temp4_input.dev_attr.attr,
	&sensor_dev_attr_temp2_max.dev_attr.attr,
	&sensor_dev_attr_temp4_max.dev_attr.attr,
	&sensor_dev_attr_temp2_crit.dev_attr.attr,
	&sensor_dev_attr_temp4_crit.dev_attr.attr,

	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
	&sensor_dev_attr_temp4_crit_alarm.dev_attr.attr,
274
	&sensor_dev_attr_temp4_fault.dev_attr.attr,
275
	&sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
276
	&sensor_dev_attr_temp2_fault.dev_attr.attr,
277 278 279 280 281 282 283 284
	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
	NULL
};

static const struct attribute_group lm83_group_opt = {
	.attrs = lm83_attributes_opt,
};

Linus Torvalds's avatar
Linus Torvalds committed
285 286 287 288
/*
 * Real code
 */

289
/* Return 0 if detection is successful, -ENODEV otherwise */
290
static int lm83_detect(struct i2c_client *new_client,
291
		       struct i2c_board_info *info)
Linus Torvalds's avatar
Linus Torvalds committed
292
{
293
	struct i2c_adapter *adapter = new_client->adapter;
294 295
	const char *name;
	u8 man_id, chip_id;
Linus Torvalds's avatar
Linus Torvalds committed
296 297

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
298
		return -ENODEV;
Linus Torvalds's avatar
Linus Torvalds committed
299

300 301 302 303 304 305 306
	/* Detection */
	if ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) & 0xA8) ||
	    (i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) & 0x48) ||
	    (i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) & 0x41)) {
		dev_dbg(&adapter->dev, "LM83 detection failed at 0x%02x\n",
			new_client->addr);
		return -ENODEV;
Linus Torvalds's avatar
Linus Torvalds committed
307 308
	}

309 310 311 312
	/* Identification */
	man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID);
	if (man_id != 0x01)	/* National Semiconductor */
		return -ENODEV;
Linus Torvalds's avatar
Linus Torvalds committed
313

314 315 316
	chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID);
	switch (chip_id) {
	case 0x03:
Linus Torvalds's avatar
Linus Torvalds committed
317
		name = "lm83";
318 319
		break;
	case 0x01:
Jordan Crouse's avatar
Jordan Crouse committed
320
		name = "lm82";
321 322 323 324 325 326 327
		break;
	default:
		/* identification failed */
		dev_info(&adapter->dev,
			 "Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n",
			 man_id, chip_id);
		return -ENODEV;
Linus Torvalds's avatar
Linus Torvalds committed
328 329
	}

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
	strlcpy(info->type, name, I2C_NAME_SIZE);

	return 0;
}

static int lm83_probe(struct i2c_client *new_client,
		      const struct i2c_device_id *id)
{
	struct lm83_data *data;
	int err;

	data = kzalloc(sizeof(struct lm83_data), GFP_KERNEL);
	if (!data) {
		err = -ENOMEM;
		goto exit;
	}

	i2c_set_clientdata(new_client, data);
Linus Torvalds's avatar
Linus Torvalds committed
348
	data->valid = 0;
349
	mutex_init(&data->update_lock);
Linus Torvalds's avatar
Linus Torvalds committed
350 351

	/*
352
	 * Register sysfs hooks
Jordan Crouse's avatar
Jordan Crouse committed
353 354 355 356 357
	 * The LM82 can only monitor one external diode which is
	 * at the same register as the LM83 temp3 entry - so we
	 * declare 1 and 3 common, and then 2 and 4 only for the LM83.
	 */

358
	if ((err = sysfs_create_group(&new_client->dev.kobj, &lm83_group)))
359
		goto exit_free;
Linus Torvalds's avatar
Linus Torvalds committed
360

361
	if (id->driver_data == lm83) {
362 363 364 365 366
		if ((err = sysfs_create_group(&new_client->dev.kobj,
					      &lm83_group_opt)))
			goto exit_remove_files;
	}

367 368 369
	data->hwmon_dev = hwmon_device_register(&new_client->dev);
	if (IS_ERR(data->hwmon_dev)) {
		err = PTR_ERR(data->hwmon_dev);
370
		goto exit_remove_files;
Jordan Crouse's avatar
Jordan Crouse committed
371 372
	}

Linus Torvalds's avatar
Linus Torvalds committed
373 374
	return 0;

375 376 377
exit_remove_files:
	sysfs_remove_group(&new_client->dev.kobj, &lm83_group);
	sysfs_remove_group(&new_client->dev.kobj, &lm83_group_opt);
Linus Torvalds's avatar
Linus Torvalds committed
378 379 380 381 382 383
exit_free:
	kfree(data);
exit:
	return err;
}

384
static int lm83_remove(struct i2c_client *client)
Linus Torvalds's avatar
Linus Torvalds committed
385
{
386
	struct lm83_data *data = i2c_get_clientdata(client);
Linus Torvalds's avatar
Linus Torvalds committed
387

388
	hwmon_device_unregister(data->hwmon_dev);
389 390
	sysfs_remove_group(&client->dev.kobj, &lm83_group);
	sysfs_remove_group(&client->dev.kobj, &lm83_group_opt);
391 392

	kfree(data);
Linus Torvalds's avatar
Linus Torvalds committed
393 394 395 396 397 398 399 400
	return 0;
}

static struct lm83_data *lm83_update_device(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct lm83_data *data = i2c_get_clientdata(client);

401
	mutex_lock(&data->update_lock);
Linus Torvalds's avatar
Linus Torvalds committed
402 403 404 405 406

	if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) {
		int nr;

		dev_dbg(&client->dev, "Updating lm83 data.\n");
407 408
		for (nr = 0; nr < 9; nr++) {
			data->temp[nr] =
Linus Torvalds's avatar
Linus Torvalds committed
409 410 411 412 413 414 415 416 417 418 419 420
			    i2c_smbus_read_byte_data(client,
			    LM83_REG_R_TEMP[nr]);
		}
		data->alarms =
		    i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1)
		    + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2)
		    << 8);

		data->last_updated = jiffies;
		data->valid = 1;
	}

421
	mutex_unlock(&data->update_lock);
Linus Torvalds's avatar
Linus Torvalds committed
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441

	return data;
}

static int __init sensors_lm83_init(void)
{
	return i2c_add_driver(&lm83_driver);
}

static void __exit sensors_lm83_exit(void)
{
	i2c_del_driver(&lm83_driver);
}

MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("LM83 driver");
MODULE_LICENSE("GPL");

module_init(sensors_lm83_init);
module_exit(sensors_lm83_exit);