1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * An hwmon driver for the Microchip TC74 4 * 5 * Copyright 2015 Maciej Szmigiero <mail@maciej.szmigiero.name> 6 * 7 * Based on ad7414.c: 8 * Copyright 2006 Stefan Roese, DENX Software Engineering 9 * Copyright 2008 Sean MacLennan, PIKA Technologies 10 * Copyright 2008 Frank Edelhaeuser, Spansion Inc. 11 */ 12 13 #include <linux/bitops.h> 14 #include <linux/err.h> 15 #include <linux/hwmon.h> 16 #include <linux/hwmon-sysfs.h> 17 #include <linux/i2c.h> 18 #include <linux/jiffies.h> 19 #include <linux/module.h> 20 #include <linux/mutex.h> 21 #include <linux/slab.h> 22 #include <linux/sysfs.h> 23 24 /* TC74 registers */ 25 #define TC74_REG_TEMP 0x00 26 #define TC74_REG_CONFIG 0x01 27 28 struct tc74_data { 29 struct i2c_client *client; 30 struct mutex lock; /* atomic read data updates */ 31 bool valid; /* validity of fields below */ 32 unsigned long next_update; /* In jiffies */ 33 s8 temp_input; /* Temp value in dC */ 34 }; 35 36 static int tc74_update_device(struct device *dev) 37 { 38 struct tc74_data *data = dev_get_drvdata(dev); 39 struct i2c_client *client = data->client; 40 int ret; 41 42 ret = mutex_lock_interruptible(&data->lock); 43 if (ret) 44 return ret; 45 46 if (time_after(jiffies, data->next_update) || !data->valid) { 47 s32 value; 48 49 value = i2c_smbus_read_byte_data(client, TC74_REG_CONFIG); 50 if (value < 0) { 51 dev_dbg(&client->dev, "TC74_REG_CONFIG read err %d\n", 52 (int)value); 53 54 ret = value; 55 goto ret_unlock; 56 } 57 58 if (!(value & BIT(6))) { 59 /* not ready yet */ 60 61 ret = -EAGAIN; 62 goto ret_unlock; 63 } 64 65 value = i2c_smbus_read_byte_data(client, TC74_REG_TEMP); 66 if (value < 0) { 67 dev_dbg(&client->dev, "TC74_REG_TEMP read err %d\n", 68 (int)value); 69 70 ret = value; 71 goto ret_unlock; 72 } 73 74 data->temp_input = value; 75 data->next_update = jiffies + HZ / 4; 76 data->valid = true; 77 } 78 79 ret_unlock: 80 mutex_unlock(&data->lock); 81 82 return ret; 83 } 84 85 static ssize_t temp_input_show(struct device *dev, 86 struct device_attribute *attr, char *buf) 87 { 88 struct tc74_data *data = dev_get_drvdata(dev); 89 int ret; 90 91 ret = tc74_update_device(dev); 92 if (ret) 93 return ret; 94 95 return sprintf(buf, "%d\n", data->temp_input * 1000); 96 } 97 static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); 98 99 static struct attribute *tc74_attrs[] = { 100 &sensor_dev_attr_temp1_input.dev_attr.attr, 101 NULL 102 }; 103 104 ATTRIBUTE_GROUPS(tc74); 105 106 static int tc74_probe(struct i2c_client *client, 107 const struct i2c_device_id *dev_id) 108 { 109 struct device *dev = &client->dev; 110 struct tc74_data *data; 111 struct device *hwmon_dev; 112 s32 conf; 113 114 if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 115 return -EOPNOTSUPP; 116 117 data = devm_kzalloc(dev, sizeof(struct tc74_data), GFP_KERNEL); 118 if (!data) 119 return -ENOMEM; 120 121 data->client = client; 122 mutex_init(&data->lock); 123 124 /* Make sure the chip is powered up. */ 125 conf = i2c_smbus_read_byte_data(client, TC74_REG_CONFIG); 126 if (conf < 0) { 127 dev_err(dev, "unable to read config register\n"); 128 129 return conf; 130 } 131 132 if (conf & 0x3f) { 133 dev_err(dev, "invalid config register value\n"); 134 135 return -ENODEV; 136 } 137 138 if (conf & BIT(7)) { 139 s32 ret; 140 141 conf &= ~BIT(7); 142 143 ret = i2c_smbus_write_byte_data(client, TC74_REG_CONFIG, conf); 144 if (ret) 145 dev_warn(dev, "unable to disable STANDBY\n"); 146 } 147 148 hwmon_dev = devm_hwmon_device_register_with_groups(dev, 149 client->name, 150 data, tc74_groups); 151 return PTR_ERR_OR_ZERO(hwmon_dev); 152 } 153 154 static const struct i2c_device_id tc74_id[] = { 155 { "tc74", 0 }, 156 {} 157 }; 158 MODULE_DEVICE_TABLE(i2c, tc74_id); 159 160 static struct i2c_driver tc74_driver = { 161 .driver = { 162 .name = "tc74", 163 }, 164 .probe = tc74_probe, 165 .id_table = tc74_id, 166 }; 167 168 module_i2c_driver(tc74_driver); 169 170 MODULE_AUTHOR("Maciej Szmigiero <mail@maciej.szmigiero.name>"); 171 172 MODULE_DESCRIPTION("TC74 driver"); 173 MODULE_LICENSE("GPL"); 174