18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * w83l785ts.c - Part of lm_sensors, Linux kernel modules for hardware 48c2ecf20Sopenharmony_ci * monitoring 58c2ecf20Sopenharmony_ci * Copyright (C) 2003-2009 Jean Delvare <jdelvare@suse.de> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Inspired from the lm83 driver. The W83L785TS-S is a sensor chip made 88c2ecf20Sopenharmony_ci * by Winbond. It reports a single external temperature with a 1 deg 98c2ecf20Sopenharmony_ci * resolution and a 3 deg accuracy. Datasheet can be obtained from 108c2ecf20Sopenharmony_ci * Winbond's website at: 118c2ecf20Sopenharmony_ci * http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/W83L785TS-S.pdf 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Ported to Linux 2.6 by Wolfgang Ziegler <nuppla@gmx.at> and Jean Delvare 148c2ecf20Sopenharmony_ci * <jdelvare@suse.de>. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * Thanks to James Bolt <james@evilpenguin.com> for benchmarking the read 178c2ecf20Sopenharmony_ci * error handling mechanism. 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/delay.h> 228c2ecf20Sopenharmony_ci#include <linux/init.h> 238c2ecf20Sopenharmony_ci#include <linux/slab.h> 248c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 258c2ecf20Sopenharmony_ci#include <linux/i2c.h> 268c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 278c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h> 288c2ecf20Sopenharmony_ci#include <linux/err.h> 298c2ecf20Sopenharmony_ci#include <linux/mutex.h> 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci/* How many retries on register read error */ 328c2ecf20Sopenharmony_ci#define MAX_RETRIES 5 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* 358c2ecf20Sopenharmony_ci * Address to scan 368c2ecf20Sopenharmony_ci * Address is fully defined internally and cannot be changed. 378c2ecf20Sopenharmony_ci */ 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic const unsigned short normal_i2c[] = { 0x2e, I2C_CLIENT_END }; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci/* 428c2ecf20Sopenharmony_ci * The W83L785TS-S registers 438c2ecf20Sopenharmony_ci * Manufacturer ID is 0x5CA3 for Winbond. 448c2ecf20Sopenharmony_ci */ 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define W83L785TS_REG_MAN_ID1 0x4D 478c2ecf20Sopenharmony_ci#define W83L785TS_REG_MAN_ID2 0x4C 488c2ecf20Sopenharmony_ci#define W83L785TS_REG_CHIP_ID 0x4E 498c2ecf20Sopenharmony_ci#define W83L785TS_REG_CONFIG 0x40 508c2ecf20Sopenharmony_ci#define W83L785TS_REG_TYPE 0x52 518c2ecf20Sopenharmony_ci#define W83L785TS_REG_TEMP 0x27 528c2ecf20Sopenharmony_ci#define W83L785TS_REG_TEMP_OVER 0x53 /* not sure about this one */ 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* 558c2ecf20Sopenharmony_ci * Conversions 568c2ecf20Sopenharmony_ci * The W83L785TS-S uses signed 8-bit values. 578c2ecf20Sopenharmony_ci */ 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci#define TEMP_FROM_REG(val) ((val) * 1000) 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci/* 628c2ecf20Sopenharmony_ci * Functions declaration 638c2ecf20Sopenharmony_ci */ 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int w83l785ts_probe(struct i2c_client *client); 668c2ecf20Sopenharmony_cistatic int w83l785ts_detect(struct i2c_client *client, 678c2ecf20Sopenharmony_ci struct i2c_board_info *info); 688c2ecf20Sopenharmony_cistatic int w83l785ts_remove(struct i2c_client *client); 698c2ecf20Sopenharmony_cistatic u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval); 708c2ecf20Sopenharmony_cistatic struct w83l785ts_data *w83l785ts_update_device(struct device *dev); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci/* 738c2ecf20Sopenharmony_ci * Driver data (common to all clients) 748c2ecf20Sopenharmony_ci */ 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic const struct i2c_device_id w83l785ts_id[] = { 778c2ecf20Sopenharmony_ci { "w83l785ts", 0 }, 788c2ecf20Sopenharmony_ci { } 798c2ecf20Sopenharmony_ci}; 808c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, w83l785ts_id); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic struct i2c_driver w83l785ts_driver = { 838c2ecf20Sopenharmony_ci .class = I2C_CLASS_HWMON, 848c2ecf20Sopenharmony_ci .driver = { 858c2ecf20Sopenharmony_ci .name = "w83l785ts", 868c2ecf20Sopenharmony_ci }, 878c2ecf20Sopenharmony_ci .probe_new = w83l785ts_probe, 888c2ecf20Sopenharmony_ci .remove = w83l785ts_remove, 898c2ecf20Sopenharmony_ci .id_table = w83l785ts_id, 908c2ecf20Sopenharmony_ci .detect = w83l785ts_detect, 918c2ecf20Sopenharmony_ci .address_list = normal_i2c, 928c2ecf20Sopenharmony_ci}; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci/* 958c2ecf20Sopenharmony_ci * Client data (each client gets its own) 968c2ecf20Sopenharmony_ci */ 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistruct w83l785ts_data { 998c2ecf20Sopenharmony_ci struct device *hwmon_dev; 1008c2ecf20Sopenharmony_ci struct mutex update_lock; 1018c2ecf20Sopenharmony_ci char valid; /* zero until following fields are valid */ 1028c2ecf20Sopenharmony_ci unsigned long last_updated; /* in jiffies */ 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* registers values */ 1058c2ecf20Sopenharmony_ci s8 temp[2]; /* 0: input, 1: critical limit */ 1068c2ecf20Sopenharmony_ci}; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci/* 1098c2ecf20Sopenharmony_ci * Sysfs stuff 1108c2ecf20Sopenharmony_ci */ 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic ssize_t show_temp(struct device *dev, struct device_attribute *devattr, 1138c2ecf20Sopenharmony_ci char *buf) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 1168c2ecf20Sopenharmony_ci struct w83l785ts_data *data = w83l785ts_update_device(dev); 1178c2ecf20Sopenharmony_ci return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); 1218c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL, 1); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci/* 1248c2ecf20Sopenharmony_ci * Real code 1258c2ecf20Sopenharmony_ci */ 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci/* Return 0 if detection is successful, -ENODEV otherwise */ 1288c2ecf20Sopenharmony_cistatic int w83l785ts_detect(struct i2c_client *client, 1298c2ecf20Sopenharmony_ci struct i2c_board_info *info) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci struct i2c_adapter *adapter = client->adapter; 1328c2ecf20Sopenharmony_ci u16 man_id; 1338c2ecf20Sopenharmony_ci u8 chip_id; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 1368c2ecf20Sopenharmony_ci return -ENODEV; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* detection */ 1398c2ecf20Sopenharmony_ci if ((w83l785ts_read_value(client, W83L785TS_REG_CONFIG, 0) & 0x80) 1408c2ecf20Sopenharmony_ci || (w83l785ts_read_value(client, W83L785TS_REG_TYPE, 0) & 0xFC)) { 1418c2ecf20Sopenharmony_ci dev_dbg(&adapter->dev, 1428c2ecf20Sopenharmony_ci "W83L785TS-S detection failed at 0x%02x\n", 1438c2ecf20Sopenharmony_ci client->addr); 1448c2ecf20Sopenharmony_ci return -ENODEV; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* Identification */ 1488c2ecf20Sopenharmony_ci man_id = (w83l785ts_read_value(client, W83L785TS_REG_MAN_ID1, 0) << 8) 1498c2ecf20Sopenharmony_ci + w83l785ts_read_value(client, W83L785TS_REG_MAN_ID2, 0); 1508c2ecf20Sopenharmony_ci chip_id = w83l785ts_read_value(client, W83L785TS_REG_CHIP_ID, 0); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (man_id != 0x5CA3 /* Winbond */ 1538c2ecf20Sopenharmony_ci || chip_id != 0x70) { /* W83L785TS-S */ 1548c2ecf20Sopenharmony_ci dev_dbg(&adapter->dev, 1558c2ecf20Sopenharmony_ci "Unsupported chip (man_id=0x%04X, chip_id=0x%02X)\n", 1568c2ecf20Sopenharmony_ci man_id, chip_id); 1578c2ecf20Sopenharmony_ci return -ENODEV; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci strlcpy(info->type, "w83l785ts", I2C_NAME_SIZE); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci return 0; 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic int w83l785ts_probe(struct i2c_client *client) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci struct w83l785ts_data *data; 1688c2ecf20Sopenharmony_ci struct device *dev = &client->dev; 1698c2ecf20Sopenharmony_ci int err; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(struct w83l785ts_data), GFP_KERNEL); 1728c2ecf20Sopenharmony_ci if (!data) 1738c2ecf20Sopenharmony_ci return -ENOMEM; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci i2c_set_clientdata(client, data); 1768c2ecf20Sopenharmony_ci mutex_init(&data->update_lock); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci /* 1798c2ecf20Sopenharmony_ci * Initialize the W83L785TS chip 1808c2ecf20Sopenharmony_ci * Nothing yet, assume it is already started. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci err = device_create_file(dev, &sensor_dev_attr_temp1_input.dev_attr); 1848c2ecf20Sopenharmony_ci if (err) 1858c2ecf20Sopenharmony_ci return err; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci err = device_create_file(dev, &sensor_dev_attr_temp1_max.dev_attr); 1888c2ecf20Sopenharmony_ci if (err) 1898c2ecf20Sopenharmony_ci goto exit_remove; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci /* Register sysfs hooks */ 1928c2ecf20Sopenharmony_ci data->hwmon_dev = hwmon_device_register(dev); 1938c2ecf20Sopenharmony_ci if (IS_ERR(data->hwmon_dev)) { 1948c2ecf20Sopenharmony_ci err = PTR_ERR(data->hwmon_dev); 1958c2ecf20Sopenharmony_ci goto exit_remove; 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci return 0; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ciexit_remove: 2018c2ecf20Sopenharmony_ci device_remove_file(dev, &sensor_dev_attr_temp1_input.dev_attr); 2028c2ecf20Sopenharmony_ci device_remove_file(dev, &sensor_dev_attr_temp1_max.dev_attr); 2038c2ecf20Sopenharmony_ci return err; 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic int w83l785ts_remove(struct i2c_client *client) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci struct w83l785ts_data *data = i2c_get_clientdata(client); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci hwmon_device_unregister(data->hwmon_dev); 2118c2ecf20Sopenharmony_ci device_remove_file(&client->dev, 2128c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_input.dev_attr); 2138c2ecf20Sopenharmony_ci device_remove_file(&client->dev, 2148c2ecf20Sopenharmony_ci &sensor_dev_attr_temp1_max.dev_attr); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci return 0; 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci int value, i; 2228c2ecf20Sopenharmony_ci struct device *dev; 2238c2ecf20Sopenharmony_ci const char *prefix; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* 2268c2ecf20Sopenharmony_ci * We might be called during detection, at which point the client 2278c2ecf20Sopenharmony_ci * isn't yet fully initialized, so we can't use dev_dbg on it 2288c2ecf20Sopenharmony_ci */ 2298c2ecf20Sopenharmony_ci if (i2c_get_clientdata(client)) { 2308c2ecf20Sopenharmony_ci dev = &client->dev; 2318c2ecf20Sopenharmony_ci prefix = ""; 2328c2ecf20Sopenharmony_ci } else { 2338c2ecf20Sopenharmony_ci dev = &client->adapter->dev; 2348c2ecf20Sopenharmony_ci prefix = "w83l785ts: "; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci /* 2388c2ecf20Sopenharmony_ci * Frequent read errors have been reported on Asus boards, so we 2398c2ecf20Sopenharmony_ci * retry on read errors. If it still fails (unlikely), return the 2408c2ecf20Sopenharmony_ci * default value requested by the caller. 2418c2ecf20Sopenharmony_ci */ 2428c2ecf20Sopenharmony_ci for (i = 1; i <= MAX_RETRIES; i++) { 2438c2ecf20Sopenharmony_ci value = i2c_smbus_read_byte_data(client, reg); 2448c2ecf20Sopenharmony_ci if (value >= 0) { 2458c2ecf20Sopenharmony_ci dev_dbg(dev, "%sRead 0x%02x from register 0x%02x.\n", 2468c2ecf20Sopenharmony_ci prefix, value, reg); 2478c2ecf20Sopenharmony_ci return value; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci dev_dbg(dev, "%sRead failed, will retry in %d.\n", prefix, i); 2508c2ecf20Sopenharmony_ci msleep(i); 2518c2ecf20Sopenharmony_ci } 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci dev_err(dev, "%sCouldn't read value from register 0x%02x.\n", prefix, 2548c2ecf20Sopenharmony_ci reg); 2558c2ecf20Sopenharmony_ci return defval; 2568c2ecf20Sopenharmony_ci} 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_cistatic struct w83l785ts_data *w83l785ts_update_device(struct device *dev) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 2618c2ecf20Sopenharmony_ci struct w83l785ts_data *data = i2c_get_clientdata(client); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci mutex_lock(&data->update_lock); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci if (!data->valid || time_after(jiffies, data->last_updated + HZ * 2)) { 2668c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "Updating w83l785ts data.\n"); 2678c2ecf20Sopenharmony_ci data->temp[0] = w83l785ts_read_value(client, 2688c2ecf20Sopenharmony_ci W83L785TS_REG_TEMP, data->temp[0]); 2698c2ecf20Sopenharmony_ci data->temp[1] = w83l785ts_read_value(client, 2708c2ecf20Sopenharmony_ci W83L785TS_REG_TEMP_OVER, data->temp[1]); 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci data->last_updated = jiffies; 2738c2ecf20Sopenharmony_ci data->valid = 1; 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci mutex_unlock(&data->update_lock); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci return data; 2798c2ecf20Sopenharmony_ci} 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cimodule_i2c_driver(w83l785ts_driver); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>"); 2848c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("W83L785TS-S driver"); 2858c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 286