xref: /openbmc/linux/drivers/hwmon/ad7414.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
26c633c30SSean MacLennan /*
36c633c30SSean MacLennan  * An hwmon driver for the Analog Devices AD7414
46c633c30SSean MacLennan  *
56c633c30SSean MacLennan  * Copyright 2006 Stefan Roese <sr at denx.de>, DENX Software Engineering
66c633c30SSean MacLennan  *
76c633c30SSean MacLennan  * Copyright (c) 2008 PIKA Technologies
86c633c30SSean MacLennan  *   Sean MacLennan <smaclennan@pikatech.com>
96c633c30SSean MacLennan  *
106c633c30SSean MacLennan  * Copyright (c) 2008 Spansion Inc.
116c633c30SSean MacLennan  *   Frank Edelhaeuser <frank.edelhaeuser at spansion.com>
126c633c30SSean MacLennan  *   (converted to "new style" I2C driver model, removed checkpatch.pl warnings)
136c633c30SSean MacLennan  *
146c633c30SSean MacLennan  * Based on ad7418.c
156c633c30SSean MacLennan  * Copyright 2006 Tower Technologies, Alessandro Zummo <a.zummo at towertech.it>
166c633c30SSean MacLennan  */
176c633c30SSean MacLennan 
186c633c30SSean MacLennan #include <linux/module.h>
196c633c30SSean MacLennan #include <linux/jiffies.h>
206c633c30SSean MacLennan #include <linux/i2c.h>
216c633c30SSean MacLennan #include <linux/hwmon.h>
226c633c30SSean MacLennan #include <linux/hwmon-sysfs.h>
236c633c30SSean MacLennan #include <linux/err.h>
246c633c30SSean MacLennan #include <linux/mutex.h>
256c633c30SSean MacLennan #include <linux/sysfs.h>
265a0e3ad6STejun Heo #include <linux/slab.h>
276c633c30SSean MacLennan 
286c633c30SSean MacLennan 
296c633c30SSean MacLennan /* AD7414 registers */
306c633c30SSean MacLennan #define AD7414_REG_TEMP		0x00
316c633c30SSean MacLennan #define AD7414_REG_CONF		0x01
326c633c30SSean MacLennan #define AD7414_REG_T_HIGH	0x02
336c633c30SSean MacLennan #define AD7414_REG_T_LOW	0x03
346c633c30SSean MacLennan 
356c633c30SSean MacLennan static u8 AD7414_REG_LIMIT[] = { AD7414_REG_T_HIGH, AD7414_REG_T_LOW };
366c633c30SSean MacLennan 
376c633c30SSean MacLennan struct ad7414_data {
38045c1391SAxel Lin 	struct i2c_client	*client;
396c633c30SSean MacLennan 	struct mutex		lock;	/* atomic read data updates */
40952a11caSPaul Fertser 	bool			valid;	/* true if following fields are valid */
416c633c30SSean MacLennan 	unsigned long		next_update;	/* In jiffies */
426c633c30SSean MacLennan 	s16			temp_input;	/* Register values */
436c633c30SSean MacLennan 	s8			temps[ARRAY_SIZE(AD7414_REG_LIMIT)];
446c633c30SSean MacLennan };
456c633c30SSean MacLennan 
466c633c30SSean MacLennan /* REG: (0.25C/bit, two's complement) << 6 */
ad7414_temp_from_reg(s16 reg)476c633c30SSean MacLennan static inline int ad7414_temp_from_reg(s16 reg)
486c633c30SSean MacLennan {
498deeac82SGuenter Roeck 	/*
508deeac82SGuenter Roeck 	 * use integer division instead of equivalent right shift to
516c633c30SSean MacLennan 	 * guarantee arithmetic shift and preserve the sign
526c633c30SSean MacLennan 	 */
536c633c30SSean MacLennan 	return ((int)reg / 64) * 250;
546c633c30SSean MacLennan }
556c633c30SSean MacLennan 
ad7414_read(struct i2c_client * client,u8 reg)566c633c30SSean MacLennan static inline int ad7414_read(struct i2c_client *client, u8 reg)
576c633c30SSean MacLennan {
5890f4102cSJean Delvare 	if (reg == AD7414_REG_TEMP)
5990f4102cSJean Delvare 		return i2c_smbus_read_word_swapped(client, reg);
6090f4102cSJean Delvare 	else
616c633c30SSean MacLennan 		return i2c_smbus_read_byte_data(client, reg);
626c633c30SSean MacLennan }
636c633c30SSean MacLennan 
ad7414_write(struct i2c_client * client,u8 reg,u8 value)646c633c30SSean MacLennan static inline int ad7414_write(struct i2c_client *client, u8 reg, u8 value)
656c633c30SSean MacLennan {
666c633c30SSean MacLennan 	return i2c_smbus_write_byte_data(client, reg, value);
676c633c30SSean MacLennan }
686c633c30SSean MacLennan 
ad7414_update_device(struct device * dev)69d130d971SAdrian Bunk static struct ad7414_data *ad7414_update_device(struct device *dev)
706c633c30SSean MacLennan {
71045c1391SAxel Lin 	struct ad7414_data *data = dev_get_drvdata(dev);
72045c1391SAxel Lin 	struct i2c_client *client = data->client;
736c633c30SSean MacLennan 
746c633c30SSean MacLennan 	mutex_lock(&data->lock);
756c633c30SSean MacLennan 
766c633c30SSean MacLennan 	if (time_after(jiffies, data->next_update) || !data->valid) {
776c633c30SSean MacLennan 		int value, i;
786c633c30SSean MacLennan 
796c633c30SSean MacLennan 		dev_dbg(&client->dev, "starting ad7414 update\n");
806c633c30SSean MacLennan 
816c633c30SSean MacLennan 		value = ad7414_read(client, AD7414_REG_TEMP);
826c633c30SSean MacLennan 		if (value < 0)
836c633c30SSean MacLennan 			dev_dbg(&client->dev, "AD7414_REG_TEMP err %d\n",
846c633c30SSean MacLennan 				value);
856c633c30SSean MacLennan 		else
866c633c30SSean MacLennan 			data->temp_input = value;
876c633c30SSean MacLennan 
886c633c30SSean MacLennan 		for (i = 0; i < ARRAY_SIZE(AD7414_REG_LIMIT); ++i) {
896c633c30SSean MacLennan 			value = ad7414_read(client, AD7414_REG_LIMIT[i]);
906c633c30SSean MacLennan 			if (value < 0)
916c633c30SSean MacLennan 				dev_dbg(&client->dev, "AD7414 reg %d err %d\n",
926c633c30SSean MacLennan 					AD7414_REG_LIMIT[i], value);
936c633c30SSean MacLennan 			else
946c633c30SSean MacLennan 				data->temps[i] = value;
956c633c30SSean MacLennan 		}
966c633c30SSean MacLennan 
976c633c30SSean MacLennan 		data->next_update = jiffies + HZ + HZ / 2;
98952a11caSPaul Fertser 		data->valid = true;
996c633c30SSean MacLennan 	}
1006c633c30SSean MacLennan 
1016c633c30SSean MacLennan 	mutex_unlock(&data->lock);
1026c633c30SSean MacLennan 
1036c633c30SSean MacLennan 	return data;
1046c633c30SSean MacLennan }
1056c633c30SSean MacLennan 
temp_input_show(struct device * dev,struct device_attribute * attr,char * buf)106cbf6cb2bSGuenter Roeck static ssize_t temp_input_show(struct device *dev,
1076c633c30SSean MacLennan 			       struct device_attribute *attr, char *buf)
1086c633c30SSean MacLennan {
1096c633c30SSean MacLennan 	struct ad7414_data *data = ad7414_update_device(dev);
1106c633c30SSean MacLennan 	return sprintf(buf, "%d\n", ad7414_temp_from_reg(data->temp_input));
1116c633c30SSean MacLennan }
112cbf6cb2bSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0);
1136c633c30SSean MacLennan 
max_min_show(struct device * dev,struct device_attribute * attr,char * buf)114cbf6cb2bSGuenter Roeck static ssize_t max_min_show(struct device *dev, struct device_attribute *attr,
1156c633c30SSean MacLennan 			    char *buf)
1166c633c30SSean MacLennan {
1176c633c30SSean MacLennan 	int index = to_sensor_dev_attr(attr)->index;
1186c633c30SSean MacLennan 	struct ad7414_data *data = ad7414_update_device(dev);
1196c633c30SSean MacLennan 	return sprintf(buf, "%d\n", data->temps[index] * 1000);
1206c633c30SSean MacLennan }
1216c633c30SSean MacLennan 
max_min_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)122cbf6cb2bSGuenter Roeck static ssize_t max_min_store(struct device *dev,
123cbf6cb2bSGuenter Roeck 			     struct device_attribute *attr, const char *buf,
124cbf6cb2bSGuenter Roeck 			     size_t count)
1256c633c30SSean MacLennan {
126045c1391SAxel Lin 	struct ad7414_data *data = dev_get_drvdata(dev);
127045c1391SAxel Lin 	struct i2c_client *client = data->client;
1286c633c30SSean MacLennan 	int index = to_sensor_dev_attr(attr)->index;
1296c633c30SSean MacLennan 	u8 reg = AD7414_REG_LIMIT[index];
130dcb7cb97SFrans Meulenbroeks 	long temp;
131dcb7cb97SFrans Meulenbroeks 	int ret = kstrtol(buf, 10, &temp);
132dcb7cb97SFrans Meulenbroeks 
133dcb7cb97SFrans Meulenbroeks 	if (ret < 0)
134dcb7cb97SFrans Meulenbroeks 		return ret;
1356c633c30SSean MacLennan 
1362a844c14SGuenter Roeck 	temp = clamp_val(temp, -40000, 85000);
1376c633c30SSean MacLennan 	temp = (temp + (temp < 0 ? -500 : 500)) / 1000;
1386c633c30SSean MacLennan 
1396c633c30SSean MacLennan 	mutex_lock(&data->lock);
1406c633c30SSean MacLennan 	data->temps[index] = temp;
1416c633c30SSean MacLennan 	ad7414_write(client, reg, temp);
1426c633c30SSean MacLennan 	mutex_unlock(&data->lock);
1436c633c30SSean MacLennan 	return count;
1446c633c30SSean MacLennan }
1456c633c30SSean MacLennan 
146cbf6cb2bSGuenter Roeck static SENSOR_DEVICE_ATTR_RW(temp1_max, max_min, 0);
147cbf6cb2bSGuenter Roeck static SENSOR_DEVICE_ATTR_RW(temp1_min, max_min, 1);
1486c633c30SSean MacLennan 
alarm_show(struct device * dev,struct device_attribute * attr,char * buf)149cbf6cb2bSGuenter Roeck static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
1506c633c30SSean MacLennan 			  char *buf)
1516c633c30SSean MacLennan {
1526c633c30SSean MacLennan 	int bitnr = to_sensor_dev_attr(attr)->index;
1536c633c30SSean MacLennan 	struct ad7414_data *data = ad7414_update_device(dev);
1546c633c30SSean MacLennan 	int value = (data->temp_input >> bitnr) & 1;
1556c633c30SSean MacLennan 	return sprintf(buf, "%d\n", value);
1566c633c30SSean MacLennan }
1576c633c30SSean MacLennan 
158cbf6cb2bSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, 3);
159cbf6cb2bSGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 4);
1606c633c30SSean MacLennan 
161045c1391SAxel Lin static struct attribute *ad7414_attrs[] = {
1626c633c30SSean MacLennan 	&sensor_dev_attr_temp1_input.dev_attr.attr,
1636c633c30SSean MacLennan 	&sensor_dev_attr_temp1_max.dev_attr.attr,
1646c633c30SSean MacLennan 	&sensor_dev_attr_temp1_min.dev_attr.attr,
1656c633c30SSean MacLennan 	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
1666c633c30SSean MacLennan 	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
1676c633c30SSean MacLennan 	NULL
1686c633c30SSean MacLennan };
1696c633c30SSean MacLennan 
170045c1391SAxel Lin ATTRIBUTE_GROUPS(ad7414);
1716c633c30SSean MacLennan 
ad7414_probe(struct i2c_client * client)17267487038SStephen Kitt static int ad7414_probe(struct i2c_client *client)
1736c633c30SSean MacLennan {
174045c1391SAxel Lin 	struct device *dev = &client->dev;
1756c633c30SSean MacLennan 	struct ad7414_data *data;
176045c1391SAxel Lin 	struct device *hwmon_dev;
1776c633c30SSean MacLennan 	int conf;
1786c633c30SSean MacLennan 
1796c633c30SSean MacLennan 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
1800fee6591SGuenter Roeck 				     I2C_FUNC_SMBUS_READ_WORD_DATA))
1810fee6591SGuenter Roeck 		return -EOPNOTSUPP;
1826c633c30SSean MacLennan 
183045c1391SAxel Lin 	data = devm_kzalloc(dev, sizeof(struct ad7414_data), GFP_KERNEL);
1840fee6591SGuenter Roeck 	if (!data)
1850fee6591SGuenter Roeck 		return -ENOMEM;
1866c633c30SSean MacLennan 
187045c1391SAxel Lin 	data->client = client;
1886c633c30SSean MacLennan 	mutex_init(&data->lock);
1896c633c30SSean MacLennan 
1906c633c30SSean MacLennan 	dev_info(&client->dev, "chip found\n");
1916c633c30SSean MacLennan 
1926c633c30SSean MacLennan 	/* Make sure the chip is powered up. */
1936c633c30SSean MacLennan 	conf = i2c_smbus_read_byte_data(client, AD7414_REG_CONF);
1946c633c30SSean MacLennan 	if (conf < 0)
195045c1391SAxel Lin 		dev_warn(dev, "ad7414_probe unable to read config register.\n");
1966c633c30SSean MacLennan 	else {
1976c633c30SSean MacLennan 		conf &= ~(1 << 7);
1986c633c30SSean MacLennan 		i2c_smbus_write_byte_data(client, AD7414_REG_CONF, conf);
1996c633c30SSean MacLennan 	}
2006c633c30SSean MacLennan 
201045c1391SAxel Lin 	hwmon_dev = devm_hwmon_device_register_with_groups(dev,
202045c1391SAxel Lin 							   client->name,
203045c1391SAxel Lin 							   data, ad7414_groups);
204045c1391SAxel Lin 	return PTR_ERR_OR_ZERO(hwmon_dev);
2056c633c30SSean MacLennan }
2066c633c30SSean MacLennan 
2076c633c30SSean MacLennan static const struct i2c_device_id ad7414_id[] = {
2086c633c30SSean MacLennan 	{ "ad7414", 0 },
2096c633c30SSean MacLennan 	{}
2106c633c30SSean MacLennan };
2116407deb5Saxel lin MODULE_DEVICE_TABLE(i2c, ad7414_id);
2126c633c30SSean MacLennan 
21307182986SGuenter Roeck static const struct of_device_id __maybe_unused ad7414_of_match[] = {
21472edf311SJavier Martinez Canillas 	{ .compatible = "ad,ad7414" },
21572edf311SJavier Martinez Canillas 	{ },
21672edf311SJavier Martinez Canillas };
21772edf311SJavier Martinez Canillas MODULE_DEVICE_TABLE(of, ad7414_of_match);
21872edf311SJavier Martinez Canillas 
2196c633c30SSean MacLennan static struct i2c_driver ad7414_driver = {
2206c633c30SSean MacLennan 	.driver = {
2216c633c30SSean MacLennan 		.name	= "ad7414",
22272edf311SJavier Martinez Canillas 		.of_match_table = of_match_ptr(ad7414_of_match),
2236c633c30SSean MacLennan 	},
224*1975d167SUwe Kleine-König 	.probe = ad7414_probe,
2256c633c30SSean MacLennan 	.id_table = ad7414_id,
2266c633c30SSean MacLennan };
2276c633c30SSean MacLennan 
228f0967eeaSAxel Lin module_i2c_driver(ad7414_driver);
2296c633c30SSean MacLennan 
2306c633c30SSean MacLennan MODULE_AUTHOR("Stefan Roese <sr at denx.de>, "
2316c633c30SSean MacLennan 	      "Frank Edelhaeuser <frank.edelhaeuser at spansion.com>");
2326c633c30SSean MacLennan 
2336c633c30SSean MacLennan MODULE_DESCRIPTION("AD7414 driver");
2346c633c30SSean MacLennan MODULE_LICENSE("GPL");
235