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