xref: /openbmc/linux/drivers/hwmon/lm73.c (revision 8c14d126ae2efbcd094b24e5413b8cbe1d2c01e4)
14e233cbeSAdrien Demarez /*
24e233cbeSAdrien Demarez  * LM73 Sensor driver
34e233cbeSAdrien Demarez  * Based on LM75
44e233cbeSAdrien Demarez  *
54e233cbeSAdrien Demarez  * Copyright (C) 2007, CenoSYS (www.cenosys.com).
64e233cbeSAdrien Demarez  * Copyright (C) 2009, Bollore telecom (www.bolloretelecom.eu).
74e233cbeSAdrien Demarez  *
84e233cbeSAdrien Demarez  * Guillaume Ligneul <guillaume.ligneul@gmail.com>
94e233cbeSAdrien Demarez  * Adrien Demarez <adrien.demarez@bolloretelecom.eu>
104e233cbeSAdrien Demarez  * Jeremy Laine <jeremy.laine@bolloretelecom.eu>
114e233cbeSAdrien Demarez  *
124e233cbeSAdrien Demarez  * This software program is licensed subject to the GNU General Public License
134e233cbeSAdrien Demarez  * (GPL).Version 2,June 1991, available at
144e233cbeSAdrien Demarez  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
154e233cbeSAdrien Demarez  */
164e233cbeSAdrien Demarez 
174e233cbeSAdrien Demarez #include <linux/module.h>
184e233cbeSAdrien Demarez #include <linux/init.h>
194e233cbeSAdrien Demarez #include <linux/i2c.h>
204e233cbeSAdrien Demarez #include <linux/hwmon.h>
214e233cbeSAdrien Demarez #include <linux/hwmon-sysfs.h>
224e233cbeSAdrien Demarez #include <linux/err.h>
234e233cbeSAdrien Demarez 
244e233cbeSAdrien Demarez 
254e233cbeSAdrien Demarez /* Addresses scanned */
264e233cbeSAdrien Demarez static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c,
274e233cbeSAdrien Demarez 					0x4d, 0x4e, I2C_CLIENT_END };
284e233cbeSAdrien Demarez 
294e233cbeSAdrien Demarez /* LM73 registers */
304e233cbeSAdrien Demarez #define LM73_REG_INPUT		0x00
314e233cbeSAdrien Demarez #define LM73_REG_CONF		0x01
324e233cbeSAdrien Demarez #define LM73_REG_MAX		0x02
334e233cbeSAdrien Demarez #define LM73_REG_MIN		0x03
344e233cbeSAdrien Demarez #define LM73_REG_CTRL		0x04
354e233cbeSAdrien Demarez #define LM73_REG_ID		0x07
364e233cbeSAdrien Demarez 
3790f4102cSJean Delvare #define LM73_ID			0x9001	/* 0x0190, byte-swapped */
384e233cbeSAdrien Demarez #define DRVNAME			"lm73"
3991bba688SGuenter Roeck #define LM73_TEMP_MIN		(-256000 / 250)
4091bba688SGuenter Roeck #define LM73_TEMP_MAX		(255750 / 250)
414e233cbeSAdrien Demarez 
42*8c14d126SChris Verges #define LM73_CTRL_RES_SHIFT	5
43*8c14d126SChris Verges #define LM73_CTRL_RES_MASK	(BIT(5) | BIT(6))
44*8c14d126SChris Verges #define LM73_CTRL_TO_MASK	BIT(7)
454e233cbeSAdrien Demarez 
46*8c14d126SChris Verges static const unsigned short lm73_convrates[] = {
47*8c14d126SChris Verges 	14,	/* 11-bits (0.25000 C/LSB): RES1 Bit = 0, RES0 Bit = 0 */
48*8c14d126SChris Verges 	28,	/* 12-bits (0.12500 C/LSB): RES1 Bit = 0, RES0 Bit = 1 */
49*8c14d126SChris Verges 	56,	/* 13-bits (0.06250 C/LSB): RES1 Bit = 1, RES0 Bit = 0 */
50*8c14d126SChris Verges 	112,	/* 14-bits (0.03125 C/LSB): RES1 Bit = 1, RES0 Bit = 1 */
51*8c14d126SChris Verges };
52*8c14d126SChris Verges 
53*8c14d126SChris Verges struct lm73_data {
54*8c14d126SChris Verges 	struct device *hwmon_dev;
55*8c14d126SChris Verges 	struct mutex lock;
56*8c14d126SChris Verges 	u8 ctrl;			/* control register value */
57*8c14d126SChris Verges };
58*8c14d126SChris Verges 
59*8c14d126SChris Verges /*-----------------------------------------------------------------------*/
604e233cbeSAdrien Demarez 
614e233cbeSAdrien Demarez static ssize_t set_temp(struct device *dev, struct device_attribute *da,
624e233cbeSAdrien Demarez 			const char *buf, size_t count)
634e233cbeSAdrien Demarez {
644e233cbeSAdrien Demarez 	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
654e233cbeSAdrien Demarez 	struct i2c_client *client = to_i2c_client(dev);
664e233cbeSAdrien Demarez 	long temp;
674e233cbeSAdrien Demarez 	short value;
680602934fSChris Verges 	s32 err;
694e233cbeSAdrien Demarez 
70179c4fdbSFrans Meulenbroeks 	int status = kstrtol(buf, 10, &temp);
714e233cbeSAdrien Demarez 	if (status < 0)
724e233cbeSAdrien Demarez 		return status;
734e233cbeSAdrien Demarez 
744e233cbeSAdrien Demarez 	/* Write value */
7591bba688SGuenter Roeck 	value = clamp_val(temp / 250, LM73_TEMP_MIN, LM73_TEMP_MAX) << 5;
760602934fSChris Verges 	err = i2c_smbus_write_word_swapped(client, attr->index, value);
770602934fSChris Verges 	return (err < 0) ? err : count;
784e233cbeSAdrien Demarez }
794e233cbeSAdrien Demarez 
804e233cbeSAdrien Demarez static ssize_t show_temp(struct device *dev, struct device_attribute *da,
814e233cbeSAdrien Demarez 			 char *buf)
824e233cbeSAdrien Demarez {
834e233cbeSAdrien Demarez 	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
844e233cbeSAdrien Demarez 	struct i2c_client *client = to_i2c_client(dev);
850602934fSChris Verges 	int temp;
860602934fSChris Verges 
870602934fSChris Verges 	s32 err = i2c_smbus_read_word_swapped(client, attr->index);
880602934fSChris Verges 	if (err < 0)
890602934fSChris Verges 		return err;
900602934fSChris Verges 
914e233cbeSAdrien Demarez 	/* use integer division instead of equivalent right shift to
924e233cbeSAdrien Demarez 	   guarantee arithmetic shift and preserve the sign */
930602934fSChris Verges 	temp = (((s16) err) * 250) / 32;
940602934fSChris Verges 	return scnprintf(buf, PAGE_SIZE, "%d\n", temp);
954e233cbeSAdrien Demarez }
964e233cbeSAdrien Demarez 
97*8c14d126SChris Verges static ssize_t set_convrate(struct device *dev, struct device_attribute *da,
98*8c14d126SChris Verges 			    const char *buf, size_t count)
99*8c14d126SChris Verges {
100*8c14d126SChris Verges 	struct i2c_client *client = to_i2c_client(dev);
101*8c14d126SChris Verges 	struct lm73_data *data = i2c_get_clientdata(client);
102*8c14d126SChris Verges 	unsigned long convrate;
103*8c14d126SChris Verges 	s32 err;
104*8c14d126SChris Verges 	int res = 0;
105*8c14d126SChris Verges 
106*8c14d126SChris Verges 	err = kstrtoul(buf, 10, &convrate);
107*8c14d126SChris Verges 	if (err < 0)
108*8c14d126SChris Verges 		return err;
109*8c14d126SChris Verges 
110*8c14d126SChris Verges 	/*
111*8c14d126SChris Verges 	 * Convert the desired conversion rate into register bits.
112*8c14d126SChris Verges 	 * res is already initialized, and everything past the second-to-last
113*8c14d126SChris Verges 	 * value in the array is treated as belonging to the last value
114*8c14d126SChris Verges 	 * in the array.
115*8c14d126SChris Verges 	 */
116*8c14d126SChris Verges 	while (res < (ARRAY_SIZE(lm73_convrates) - 1) &&
117*8c14d126SChris Verges 			convrate > lm73_convrates[res])
118*8c14d126SChris Verges 		res++;
119*8c14d126SChris Verges 
120*8c14d126SChris Verges 	mutex_lock(&data->lock);
121*8c14d126SChris Verges 	data->ctrl &= LM73_CTRL_TO_MASK;
122*8c14d126SChris Verges 	data->ctrl |= res << LM73_CTRL_RES_SHIFT;
123*8c14d126SChris Verges 	err = i2c_smbus_write_byte_data(client, LM73_REG_CTRL, data->ctrl);
124*8c14d126SChris Verges 	mutex_unlock(&data->lock);
125*8c14d126SChris Verges 
126*8c14d126SChris Verges 	if (err < 0)
127*8c14d126SChris Verges 		return err;
128*8c14d126SChris Verges 
129*8c14d126SChris Verges 	return count;
130*8c14d126SChris Verges }
131*8c14d126SChris Verges 
132*8c14d126SChris Verges static ssize_t show_convrate(struct device *dev, struct device_attribute *da,
133*8c14d126SChris Verges 			     char *buf)
134*8c14d126SChris Verges {
135*8c14d126SChris Verges 	struct i2c_client *client = to_i2c_client(dev);
136*8c14d126SChris Verges 	struct lm73_data *data = i2c_get_clientdata(client);
137*8c14d126SChris Verges 	int res;
138*8c14d126SChris Verges 
139*8c14d126SChris Verges 	res = (data->ctrl & LM73_CTRL_RES_MASK) >> LM73_CTRL_RES_SHIFT;
140*8c14d126SChris Verges 	return scnprintf(buf, PAGE_SIZE, "%hu\n", lm73_convrates[res]);
141*8c14d126SChris Verges }
1424e233cbeSAdrien Demarez 
1434e233cbeSAdrien Demarez /*-----------------------------------------------------------------------*/
1444e233cbeSAdrien Demarez 
1454e233cbeSAdrien Demarez /* sysfs attributes for hwmon */
1464e233cbeSAdrien Demarez 
1474e233cbeSAdrien Demarez static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
1484e233cbeSAdrien Demarez 			show_temp, set_temp, LM73_REG_MAX);
1494e233cbeSAdrien Demarez static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO,
1504e233cbeSAdrien Demarez 			show_temp, set_temp, LM73_REG_MIN);
1514e233cbeSAdrien Demarez static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
1524e233cbeSAdrien Demarez 			show_temp, NULL, LM73_REG_INPUT);
153*8c14d126SChris Verges static SENSOR_DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO,
154*8c14d126SChris Verges 			show_convrate, set_convrate, 0);
1554e233cbeSAdrien Demarez 
1564e233cbeSAdrien Demarez static struct attribute *lm73_attributes[] = {
1574e233cbeSAdrien Demarez 	&sensor_dev_attr_temp1_input.dev_attr.attr,
1584e233cbeSAdrien Demarez 	&sensor_dev_attr_temp1_max.dev_attr.attr,
1594e233cbeSAdrien Demarez 	&sensor_dev_attr_temp1_min.dev_attr.attr,
160*8c14d126SChris Verges 	&sensor_dev_attr_update_interval.dev_attr.attr,
1614e233cbeSAdrien Demarez 	NULL
1624e233cbeSAdrien Demarez };
1634e233cbeSAdrien Demarez 
1644e233cbeSAdrien Demarez static const struct attribute_group lm73_group = {
1654e233cbeSAdrien Demarez 	.attrs = lm73_attributes,
1664e233cbeSAdrien Demarez };
1674e233cbeSAdrien Demarez 
1684e233cbeSAdrien Demarez /*-----------------------------------------------------------------------*/
1694e233cbeSAdrien Demarez 
1704e233cbeSAdrien Demarez /* device probe and removal */
1714e233cbeSAdrien Demarez 
1724e233cbeSAdrien Demarez static int
1734e233cbeSAdrien Demarez lm73_probe(struct i2c_client *client, const struct i2c_device_id *id)
1744e233cbeSAdrien Demarez {
1754e233cbeSAdrien Demarez 	int status;
176*8c14d126SChris Verges 	struct lm73_data *data;
177*8c14d126SChris Verges 	int ctrl;
178*8c14d126SChris Verges 
179*8c14d126SChris Verges 	data = devm_kzalloc(&client->dev, sizeof(struct lm73_data),
180*8c14d126SChris Verges 			    GFP_KERNEL);
181*8c14d126SChris Verges 	if (!data)
182*8c14d126SChris Verges 		return -ENOMEM;
183*8c14d126SChris Verges 
184*8c14d126SChris Verges 	i2c_set_clientdata(client, data);
185*8c14d126SChris Verges 	mutex_init(&data->lock);
186*8c14d126SChris Verges 
187*8c14d126SChris Verges 	ctrl = i2c_smbus_read_byte_data(client, LM73_REG_CTRL);
188*8c14d126SChris Verges 	if (ctrl < 0)
189*8c14d126SChris Verges 		return ctrl;
190*8c14d126SChris Verges 	data->ctrl = ctrl;
1914e233cbeSAdrien Demarez 
1924e233cbeSAdrien Demarez 	/* Register sysfs hooks */
1934e233cbeSAdrien Demarez 	status = sysfs_create_group(&client->dev.kobj, &lm73_group);
1944e233cbeSAdrien Demarez 	if (status)
1954e233cbeSAdrien Demarez 		return status;
1964e233cbeSAdrien Demarez 
197*8c14d126SChris Verges 	data->hwmon_dev = hwmon_device_register(&client->dev);
198*8c14d126SChris Verges 	if (IS_ERR(data->hwmon_dev)) {
199*8c14d126SChris Verges 		status = PTR_ERR(data->hwmon_dev);
2004e233cbeSAdrien Demarez 		goto exit_remove;
2014e233cbeSAdrien Demarez 	}
2024e233cbeSAdrien Demarez 
2034e233cbeSAdrien Demarez 	dev_info(&client->dev, "%s: sensor '%s'\n",
204*8c14d126SChris Verges 		 dev_name(data->hwmon_dev), client->name);
2054e233cbeSAdrien Demarez 
2064e233cbeSAdrien Demarez 	return 0;
2074e233cbeSAdrien Demarez 
2084e233cbeSAdrien Demarez exit_remove:
2094e233cbeSAdrien Demarez 	sysfs_remove_group(&client->dev.kobj, &lm73_group);
2104e233cbeSAdrien Demarez 	return status;
2114e233cbeSAdrien Demarez }
2124e233cbeSAdrien Demarez 
2134e233cbeSAdrien Demarez static int lm73_remove(struct i2c_client *client)
2144e233cbeSAdrien Demarez {
215*8c14d126SChris Verges 	struct lm73_data *data = i2c_get_clientdata(client);
2164e233cbeSAdrien Demarez 
217*8c14d126SChris Verges 	hwmon_device_unregister(data->hwmon_dev);
2184e233cbeSAdrien Demarez 	sysfs_remove_group(&client->dev.kobj, &lm73_group);
2194e233cbeSAdrien Demarez 	return 0;
2204e233cbeSAdrien Demarez }
2214e233cbeSAdrien Demarez 
2224e233cbeSAdrien Demarez static const struct i2c_device_id lm73_ids[] = {
2231f86df49SJean Delvare 	{ "lm73", 0 },
2244e233cbeSAdrien Demarez 	{ /* LIST END */ }
2254e233cbeSAdrien Demarez };
2264e233cbeSAdrien Demarez MODULE_DEVICE_TABLE(i2c, lm73_ids);
2274e233cbeSAdrien Demarez 
2284e233cbeSAdrien Demarez /* Return 0 if detection is successful, -ENODEV otherwise */
229310ec792SJean Delvare static int lm73_detect(struct i2c_client *new_client,
2304e233cbeSAdrien Demarez 			struct i2c_board_info *info)
2314e233cbeSAdrien Demarez {
2324e233cbeSAdrien Demarez 	struct i2c_adapter *adapter = new_client->adapter;
23324d6e2a8SJean Delvare 	int id, ctrl, conf;
2344e233cbeSAdrien Demarez 
2354e233cbeSAdrien Demarez 	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
2364e233cbeSAdrien Demarez 					I2C_FUNC_SMBUS_WORD_DATA))
2374e233cbeSAdrien Demarez 		return -ENODEV;
2384e233cbeSAdrien Demarez 
23924d6e2a8SJean Delvare 	/*
24024d6e2a8SJean Delvare 	 * Do as much detection as possible with byte reads first, as word
24124d6e2a8SJean Delvare 	 * reads can confuse other devices.
24224d6e2a8SJean Delvare 	 */
24324d6e2a8SJean Delvare 	ctrl = i2c_smbus_read_byte_data(new_client, LM73_REG_CTRL);
24424d6e2a8SJean Delvare 	if (ctrl < 0 || (ctrl & 0x10))
24524d6e2a8SJean Delvare 		return -ENODEV;
24624d6e2a8SJean Delvare 
24724d6e2a8SJean Delvare 	conf = i2c_smbus_read_byte_data(new_client, LM73_REG_CONF);
24824d6e2a8SJean Delvare 	if (conf < 0 || (conf & 0x0c))
24924d6e2a8SJean Delvare 		return -ENODEV;
25024d6e2a8SJean Delvare 
25124d6e2a8SJean Delvare 	id = i2c_smbus_read_byte_data(new_client, LM73_REG_ID);
25224d6e2a8SJean Delvare 	if (id < 0 || id != (LM73_ID & 0xff))
25324d6e2a8SJean Delvare 		return -ENODEV;
25424d6e2a8SJean Delvare 
2554e233cbeSAdrien Demarez 	/* Check device ID */
2564e233cbeSAdrien Demarez 	id = i2c_smbus_read_word_data(new_client, LM73_REG_ID);
25724d6e2a8SJean Delvare 	if (id < 0 || id != LM73_ID)
2584e233cbeSAdrien Demarez 		return -ENODEV;
2594e233cbeSAdrien Demarez 
2604e233cbeSAdrien Demarez 	strlcpy(info->type, "lm73", I2C_NAME_SIZE);
2614e233cbeSAdrien Demarez 
2624e233cbeSAdrien Demarez 	return 0;
2634e233cbeSAdrien Demarez }
2644e233cbeSAdrien Demarez 
2654e233cbeSAdrien Demarez static struct i2c_driver lm73_driver = {
2664e233cbeSAdrien Demarez 	.class		= I2C_CLASS_HWMON,
2674e233cbeSAdrien Demarez 	.driver = {
2684e233cbeSAdrien Demarez 		.name	= "lm73",
2694e233cbeSAdrien Demarez 	},
2704e233cbeSAdrien Demarez 	.probe		= lm73_probe,
2714e233cbeSAdrien Demarez 	.remove		= lm73_remove,
2724e233cbeSAdrien Demarez 	.id_table	= lm73_ids,
2734e233cbeSAdrien Demarez 	.detect		= lm73_detect,
274c3813d6aSJean Delvare 	.address_list	= normal_i2c,
2754e233cbeSAdrien Demarez };
2764e233cbeSAdrien Demarez 
277f0967eeaSAxel Lin module_i2c_driver(lm73_driver);
2784e233cbeSAdrien Demarez 
2794e233cbeSAdrien Demarez MODULE_AUTHOR("Guillaume Ligneul <guillaume.ligneul@gmail.com>");
2804e233cbeSAdrien Demarez MODULE_DESCRIPTION("LM73 driver");
2814e233cbeSAdrien Demarez MODULE_LICENSE("GPL");
282