18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * An hwmon driver for the Microchip TC74 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2015 Maciej Szmigiero <mail@maciej.szmigiero.name> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Based on ad7414.c: 88c2ecf20Sopenharmony_ci * Copyright 2006 Stefan Roese, DENX Software Engineering 98c2ecf20Sopenharmony_ci * Copyright 2008 Sean MacLennan, PIKA Technologies 108c2ecf20Sopenharmony_ci * Copyright 2008 Frank Edelhaeuser, Spansion Inc. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/bitops.h> 148c2ecf20Sopenharmony_ci#include <linux/err.h> 158c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 168c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h> 178c2ecf20Sopenharmony_ci#include <linux/i2c.h> 188c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/mutex.h> 218c2ecf20Sopenharmony_ci#include <linux/slab.h> 228c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* TC74 registers */ 258c2ecf20Sopenharmony_ci#define TC74_REG_TEMP 0x00 268c2ecf20Sopenharmony_ci#define TC74_REG_CONFIG 0x01 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistruct tc74_data { 298c2ecf20Sopenharmony_ci struct i2c_client *client; 308c2ecf20Sopenharmony_ci struct mutex lock; /* atomic read data updates */ 318c2ecf20Sopenharmony_ci bool valid; /* validity of fields below */ 328c2ecf20Sopenharmony_ci unsigned long next_update; /* In jiffies */ 338c2ecf20Sopenharmony_ci s8 temp_input; /* Temp value in dC */ 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic int tc74_update_device(struct device *dev) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci struct tc74_data *data = dev_get_drvdata(dev); 398c2ecf20Sopenharmony_ci struct i2c_client *client = data->client; 408c2ecf20Sopenharmony_ci int ret; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci ret = mutex_lock_interruptible(&data->lock); 438c2ecf20Sopenharmony_ci if (ret) 448c2ecf20Sopenharmony_ci return ret; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (time_after(jiffies, data->next_update) || !data->valid) { 478c2ecf20Sopenharmony_ci s32 value; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci value = i2c_smbus_read_byte_data(client, TC74_REG_CONFIG); 508c2ecf20Sopenharmony_ci if (value < 0) { 518c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "TC74_REG_CONFIG read err %d\n", 528c2ecf20Sopenharmony_ci (int)value); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci ret = value; 558c2ecf20Sopenharmony_ci goto ret_unlock; 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci if (!(value & BIT(6))) { 598c2ecf20Sopenharmony_ci /* not ready yet */ 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci ret = -EAGAIN; 628c2ecf20Sopenharmony_ci goto ret_unlock; 638c2ecf20Sopenharmony_ci } 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci value = i2c_smbus_read_byte_data(client, TC74_REG_TEMP); 668c2ecf20Sopenharmony_ci if (value < 0) { 678c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "TC74_REG_TEMP read err %d\n", 688c2ecf20Sopenharmony_ci (int)value); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci ret = value; 718c2ecf20Sopenharmony_ci goto ret_unlock; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci data->temp_input = value; 758c2ecf20Sopenharmony_ci data->next_update = jiffies + HZ / 4; 768c2ecf20Sopenharmony_ci data->valid = true; 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ciret_unlock: 808c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci return ret; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic ssize_t temp_input_show(struct device *dev, 868c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct tc74_data *data = dev_get_drvdata(dev); 898c2ecf20Sopenharmony_ci int ret; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci ret = tc74_update_device(dev); 928c2ecf20Sopenharmony_ci if (ret) 938c2ecf20Sopenharmony_ci return ret; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", data->temp_input * 1000); 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic struct attribute *tc74_attrs[] = { 1008c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_input.dev_attr.attr, 1018c2ecf20Sopenharmony_ci NULL 1028c2ecf20Sopenharmony_ci}; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(tc74); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic int tc74_probe(struct i2c_client *client) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct device *dev = &client->dev; 1098c2ecf20Sopenharmony_ci struct tc74_data *data; 1108c2ecf20Sopenharmony_ci struct device *hwmon_dev; 1118c2ecf20Sopenharmony_ci s32 conf; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 1148c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(struct tc74_data), GFP_KERNEL); 1178c2ecf20Sopenharmony_ci if (!data) 1188c2ecf20Sopenharmony_ci return -ENOMEM; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci data->client = client; 1218c2ecf20Sopenharmony_ci mutex_init(&data->lock); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* Make sure the chip is powered up. */ 1248c2ecf20Sopenharmony_ci conf = i2c_smbus_read_byte_data(client, TC74_REG_CONFIG); 1258c2ecf20Sopenharmony_ci if (conf < 0) { 1268c2ecf20Sopenharmony_ci dev_err(dev, "unable to read config register\n"); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci return conf; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci if (conf & 0x3f) { 1328c2ecf20Sopenharmony_ci dev_err(dev, "invalid config register value\n"); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return -ENODEV; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci if (conf & BIT(7)) { 1388c2ecf20Sopenharmony_ci s32 ret; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci conf &= ~BIT(7); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci ret = i2c_smbus_write_byte_data(client, TC74_REG_CONFIG, conf); 1438c2ecf20Sopenharmony_ci if (ret) 1448c2ecf20Sopenharmony_ci dev_warn(dev, "unable to disable STANDBY\n"); 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci hwmon_dev = devm_hwmon_device_register_with_groups(dev, 1488c2ecf20Sopenharmony_ci client->name, 1498c2ecf20Sopenharmony_ci data, tc74_groups); 1508c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(hwmon_dev); 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic const struct i2c_device_id tc74_id[] = { 1548c2ecf20Sopenharmony_ci { "tc74", 0 }, 1558c2ecf20Sopenharmony_ci {} 1568c2ecf20Sopenharmony_ci}; 1578c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, tc74_id); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic struct i2c_driver tc74_driver = { 1608c2ecf20Sopenharmony_ci .driver = { 1618c2ecf20Sopenharmony_ci .name = "tc74", 1628c2ecf20Sopenharmony_ci }, 1638c2ecf20Sopenharmony_ci .probe_new = tc74_probe, 1648c2ecf20Sopenharmony_ci .id_table = tc74_id, 1658c2ecf20Sopenharmony_ci}; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cimodule_i2c_driver(tc74_driver); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ciMODULE_AUTHOR("Maciej Szmigiero <mail@maciej.szmigiero.name>"); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TC74 driver"); 1728c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 173