18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * An hwmon driver for the Analog Devices AD7414 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2006 Stefan Roese <sr at denx.de>, DENX Software Engineering 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright (c) 2008 PIKA Technologies 88c2ecf20Sopenharmony_ci * Sean MacLennan <smaclennan@pikatech.com> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Copyright (c) 2008 Spansion Inc. 118c2ecf20Sopenharmony_ci * Frank Edelhaeuser <frank.edelhaeuser at spansion.com> 128c2ecf20Sopenharmony_ci * (converted to "new style" I2C driver model, removed checkpatch.pl warnings) 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * Based on ad7418.c 158c2ecf20Sopenharmony_ci * Copyright 2006 Tower Technologies, Alessandro Zummo <a.zummo at towertech.it> 168c2ecf20Sopenharmony_ci */ 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <linux/module.h> 198c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 208c2ecf20Sopenharmony_ci#include <linux/i2c.h> 218c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 228c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h> 238c2ecf20Sopenharmony_ci#include <linux/err.h> 248c2ecf20Sopenharmony_ci#include <linux/mutex.h> 258c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 268c2ecf20Sopenharmony_ci#include <linux/slab.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* AD7414 registers */ 308c2ecf20Sopenharmony_ci#define AD7414_REG_TEMP 0x00 318c2ecf20Sopenharmony_ci#define AD7414_REG_CONF 0x01 328c2ecf20Sopenharmony_ci#define AD7414_REG_T_HIGH 0x02 338c2ecf20Sopenharmony_ci#define AD7414_REG_T_LOW 0x03 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic u8 AD7414_REG_LIMIT[] = { AD7414_REG_T_HIGH, AD7414_REG_T_LOW }; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistruct ad7414_data { 388c2ecf20Sopenharmony_ci struct i2c_client *client; 398c2ecf20Sopenharmony_ci struct mutex lock; /* atomic read data updates */ 408c2ecf20Sopenharmony_ci char valid; /* !=0 if following fields are valid */ 418c2ecf20Sopenharmony_ci unsigned long next_update; /* In jiffies */ 428c2ecf20Sopenharmony_ci s16 temp_input; /* Register values */ 438c2ecf20Sopenharmony_ci s8 temps[ARRAY_SIZE(AD7414_REG_LIMIT)]; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* REG: (0.25C/bit, two's complement) << 6 */ 478c2ecf20Sopenharmony_cistatic inline int ad7414_temp_from_reg(s16 reg) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci /* 508c2ecf20Sopenharmony_ci * use integer division instead of equivalent right shift to 518c2ecf20Sopenharmony_ci * guarantee arithmetic shift and preserve the sign 528c2ecf20Sopenharmony_ci */ 538c2ecf20Sopenharmony_ci return ((int)reg / 64) * 250; 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic inline int ad7414_read(struct i2c_client *client, u8 reg) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci if (reg == AD7414_REG_TEMP) 598c2ecf20Sopenharmony_ci return i2c_smbus_read_word_swapped(client, reg); 608c2ecf20Sopenharmony_ci else 618c2ecf20Sopenharmony_ci return i2c_smbus_read_byte_data(client, reg); 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic inline int ad7414_write(struct i2c_client *client, u8 reg, u8 value) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci return i2c_smbus_write_byte_data(client, reg, value); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic struct ad7414_data *ad7414_update_device(struct device *dev) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct ad7414_data *data = dev_get_drvdata(dev); 728c2ecf20Sopenharmony_ci struct i2c_client *client = data->client; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci mutex_lock(&data->lock); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (time_after(jiffies, data->next_update) || !data->valid) { 778c2ecf20Sopenharmony_ci int value, i; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "starting ad7414 update\n"); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci value = ad7414_read(client, AD7414_REG_TEMP); 828c2ecf20Sopenharmony_ci if (value < 0) 838c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "AD7414_REG_TEMP err %d\n", 848c2ecf20Sopenharmony_ci value); 858c2ecf20Sopenharmony_ci else 868c2ecf20Sopenharmony_ci data->temp_input = value; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(AD7414_REG_LIMIT); ++i) { 898c2ecf20Sopenharmony_ci value = ad7414_read(client, AD7414_REG_LIMIT[i]); 908c2ecf20Sopenharmony_ci if (value < 0) 918c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "AD7414 reg %d err %d\n", 928c2ecf20Sopenharmony_ci AD7414_REG_LIMIT[i], value); 938c2ecf20Sopenharmony_ci else 948c2ecf20Sopenharmony_ci data->temps[i] = value; 958c2ecf20Sopenharmony_ci } 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci data->next_update = jiffies + HZ + HZ / 2; 988c2ecf20Sopenharmony_ci data->valid = 1; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci return data; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic ssize_t temp_input_show(struct device *dev, 1078c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci struct ad7414_data *data = ad7414_update_device(dev); 1108c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", ad7414_temp_from_reg(data->temp_input)); 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic ssize_t max_min_show(struct device *dev, struct device_attribute *attr, 1158c2ecf20Sopenharmony_ci char *buf) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci int index = to_sensor_dev_attr(attr)->index; 1188c2ecf20Sopenharmony_ci struct ad7414_data *data = ad7414_update_device(dev); 1198c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", data->temps[index] * 1000); 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic ssize_t max_min_store(struct device *dev, 1238c2ecf20Sopenharmony_ci struct device_attribute *attr, const char *buf, 1248c2ecf20Sopenharmony_ci size_t count) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci struct ad7414_data *data = dev_get_drvdata(dev); 1278c2ecf20Sopenharmony_ci struct i2c_client *client = data->client; 1288c2ecf20Sopenharmony_ci int index = to_sensor_dev_attr(attr)->index; 1298c2ecf20Sopenharmony_ci u8 reg = AD7414_REG_LIMIT[index]; 1308c2ecf20Sopenharmony_ci long temp; 1318c2ecf20Sopenharmony_ci int ret = kstrtol(buf, 10, &temp); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (ret < 0) 1348c2ecf20Sopenharmony_ci return ret; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci temp = clamp_val(temp, -40000, 85000); 1378c2ecf20Sopenharmony_ci temp = (temp + (temp < 0 ? -500 : 500)) / 1000; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci mutex_lock(&data->lock); 1408c2ecf20Sopenharmony_ci data->temps[index] = temp; 1418c2ecf20Sopenharmony_ci ad7414_write(client, reg, temp); 1428c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 1438c2ecf20Sopenharmony_ci return count; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(temp1_max, max_min, 0); 1478c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(temp1_min, max_min, 1); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_cistatic ssize_t alarm_show(struct device *dev, struct device_attribute *attr, 1508c2ecf20Sopenharmony_ci char *buf) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci int bitnr = to_sensor_dev_attr(attr)->index; 1538c2ecf20Sopenharmony_ci struct ad7414_data *data = ad7414_update_device(dev); 1548c2ecf20Sopenharmony_ci int value = (data->temp_input >> bitnr) & 1; 1558c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", value); 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, 3); 1598c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 4); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic struct attribute *ad7414_attrs[] = { 1628c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_input.dev_attr.attr, 1638c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_max.dev_attr.attr, 1648c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_min.dev_attr.attr, 1658c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, 1668c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, 1678c2ecf20Sopenharmony_ci NULL 1688c2ecf20Sopenharmony_ci}; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(ad7414); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int ad7414_probe(struct i2c_client *client) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci struct device *dev = &client->dev; 1758c2ecf20Sopenharmony_ci struct ad7414_data *data; 1768c2ecf20Sopenharmony_ci struct device *hwmon_dev; 1778c2ecf20Sopenharmony_ci int conf; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | 1808c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_WORD_DATA)) 1818c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(struct ad7414_data), GFP_KERNEL); 1848c2ecf20Sopenharmony_ci if (!data) 1858c2ecf20Sopenharmony_ci return -ENOMEM; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci data->client = client; 1888c2ecf20Sopenharmony_ci mutex_init(&data->lock); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci dev_info(&client->dev, "chip found\n"); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* Make sure the chip is powered up. */ 1938c2ecf20Sopenharmony_ci conf = i2c_smbus_read_byte_data(client, AD7414_REG_CONF); 1948c2ecf20Sopenharmony_ci if (conf < 0) 1958c2ecf20Sopenharmony_ci dev_warn(dev, "ad7414_probe unable to read config register.\n"); 1968c2ecf20Sopenharmony_ci else { 1978c2ecf20Sopenharmony_ci conf &= ~(1 << 7); 1988c2ecf20Sopenharmony_ci i2c_smbus_write_byte_data(client, AD7414_REG_CONF, conf); 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci hwmon_dev = devm_hwmon_device_register_with_groups(dev, 2028c2ecf20Sopenharmony_ci client->name, 2038c2ecf20Sopenharmony_ci data, ad7414_groups); 2048c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(hwmon_dev); 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic const struct i2c_device_id ad7414_id[] = { 2088c2ecf20Sopenharmony_ci { "ad7414", 0 }, 2098c2ecf20Sopenharmony_ci {} 2108c2ecf20Sopenharmony_ci}; 2118c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ad7414_id); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic const struct of_device_id __maybe_unused ad7414_of_match[] = { 2148c2ecf20Sopenharmony_ci { .compatible = "ad,ad7414" }, 2158c2ecf20Sopenharmony_ci { }, 2168c2ecf20Sopenharmony_ci}; 2178c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ad7414_of_match); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic struct i2c_driver ad7414_driver = { 2208c2ecf20Sopenharmony_ci .driver = { 2218c2ecf20Sopenharmony_ci .name = "ad7414", 2228c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(ad7414_of_match), 2238c2ecf20Sopenharmony_ci }, 2248c2ecf20Sopenharmony_ci .probe_new = ad7414_probe, 2258c2ecf20Sopenharmony_ci .id_table = ad7414_id, 2268c2ecf20Sopenharmony_ci}; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cimodule_i2c_driver(ad7414_driver); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ciMODULE_AUTHOR("Stefan Roese <sr at denx.de>, " 2318c2ecf20Sopenharmony_ci "Frank Edelhaeuser <frank.edelhaeuser at spansion.com>"); 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("AD7414 driver"); 2348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 235