414 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * adt7x10.c - Part of lm_sensors, Linux kernel modules for hardware4 * monitoring5 * This driver handles the ADT7410 and compatible digital temperature sensors.6 * Hartmut Knaack <knaack.h@gmx.de> 2012-07-227 * based on lm75.c by Frodo Looijaard <frodol@dds.nl>8 * and adt7410.c from iio-staging by Sonic Zhang <sonic.zhang@analog.com>9 */10 11#include <linux/device.h>12#include <linux/module.h>13#include <linux/init.h>14#include <linux/slab.h>15#include <linux/jiffies.h>16#include <linux/hwmon.h>17#include <linux/err.h>18#include <linux/mutex.h>19#include <linux/delay.h>20#include <linux/interrupt.h>21#include <linux/regmap.h>22 23#include "adt7x10.h"24 25/*26 * ADT7X10 status27 */28#define ADT7X10_STAT_T_LOW (1 << 4)29#define ADT7X10_STAT_T_HIGH (1 << 5)30#define ADT7X10_STAT_T_CRIT (1 << 6)31#define ADT7X10_STAT_NOT_RDY (1 << 7)32 33/*34 * ADT7X10 config35 */36#define ADT7X10_FAULT_QUEUE_MASK (1 << 0 | 1 << 1)37#define ADT7X10_CT_POLARITY (1 << 2)38#define ADT7X10_INT_POLARITY (1 << 3)39#define ADT7X10_EVENT_MODE (1 << 4)40#define ADT7X10_MODE_MASK (1 << 5 | 1 << 6)41#define ADT7X10_FULL (0 << 5 | 0 << 6)42#define ADT7X10_PD (1 << 5 | 1 << 6)43#define ADT7X10_RESOLUTION (1 << 7)44 45/*46 * ADT7X10 masks47 */48#define ADT7X10_T13_VALUE_MASK 0xFFF849#define ADT7X10_T_HYST_MASK 0xF50 51/* straight from the datasheet */52#define ADT7X10_TEMP_MIN (-55000)53#define ADT7X10_TEMP_MAX 15000054 55/* Each client has this additional data */56struct adt7x10_data {57 struct regmap *regmap;58 struct mutex update_lock;59 u8 config;60 u8 oldconfig;61 bool valid; /* true if temperature valid */62};63 64enum {65 adt7x10_temperature = 0,66 adt7x10_t_alarm_high,67 adt7x10_t_alarm_low,68 adt7x10_t_crit,69};70 71static const u8 ADT7X10_REG_TEMP[] = {72 [adt7x10_temperature] = ADT7X10_TEMPERATURE, /* input */73 [adt7x10_t_alarm_high] = ADT7X10_T_ALARM_HIGH, /* high */74 [adt7x10_t_alarm_low] = ADT7X10_T_ALARM_LOW, /* low */75 [adt7x10_t_crit] = ADT7X10_T_CRIT, /* critical */76};77 78static irqreturn_t adt7x10_irq_handler(int irq, void *private)79{80 struct device *dev = private;81 struct adt7x10_data *d = dev_get_drvdata(dev);82 unsigned int status;83 int ret;84 85 ret = regmap_read(d->regmap, ADT7X10_STATUS, &status);86 if (ret < 0)87 return IRQ_HANDLED;88 89 if (status & ADT7X10_STAT_T_HIGH)90 hwmon_notify_event(dev, hwmon_temp, hwmon_temp_max_alarm, 0);91 if (status & ADT7X10_STAT_T_LOW)92 hwmon_notify_event(dev, hwmon_temp, hwmon_temp_min_alarm, 0);93 if (status & ADT7X10_STAT_T_CRIT)94 hwmon_notify_event(dev, hwmon_temp, hwmon_temp_crit_alarm, 0);95 96 return IRQ_HANDLED;97}98 99static int adt7x10_temp_ready(struct regmap *regmap)100{101 unsigned int status;102 int i, ret;103 104 for (i = 0; i < 6; i++) {105 ret = regmap_read(regmap, ADT7X10_STATUS, &status);106 if (ret < 0)107 return ret;108 if (!(status & ADT7X10_STAT_NOT_RDY))109 return 0;110 msleep(60);111 }112 return -ETIMEDOUT;113}114 115static s16 ADT7X10_TEMP_TO_REG(long temp)116{117 return DIV_ROUND_CLOSEST(clamp_val(temp, ADT7X10_TEMP_MIN,118 ADT7X10_TEMP_MAX) * 128, 1000);119}120 121static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg)122{123 /* in 13 bit mode, bits 0-2 are status flags - mask them out */124 if (!(data->config & ADT7X10_RESOLUTION))125 reg &= ADT7X10_T13_VALUE_MASK;126 /*127 * temperature is stored in twos complement format, in steps of128 * 1/128°C129 */130 return DIV_ROUND_CLOSEST(reg * 1000, 128);131}132 133/*-----------------------------------------------------------------------*/134 135static int adt7x10_temp_read(struct adt7x10_data *data, int index, long *val)136{137 unsigned int regval;138 int ret;139 140 mutex_lock(&data->update_lock);141 if (index == adt7x10_temperature && !data->valid) {142 /* wait for valid temperature */143 ret = adt7x10_temp_ready(data->regmap);144 if (ret) {145 mutex_unlock(&data->update_lock);146 return ret;147 }148 data->valid = true;149 }150 mutex_unlock(&data->update_lock);151 152 ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], ®val);153 if (ret)154 return ret;155 156 *val = ADT7X10_REG_TO_TEMP(data, regval);157 return 0;158}159 160static int adt7x10_temp_write(struct adt7x10_data *data, int index, long temp)161{162 int ret;163 164 mutex_lock(&data->update_lock);165 ret = regmap_write(data->regmap, ADT7X10_REG_TEMP[index],166 ADT7X10_TEMP_TO_REG(temp));167 mutex_unlock(&data->update_lock);168 return ret;169}170 171static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val)172{173 unsigned int regs[2] = {ADT7X10_T_HYST, ADT7X10_REG_TEMP[index]};174 int hyst, ret;175 u16 regdata[2];176 177 ret = regmap_multi_reg_read(data->regmap, regs, regdata, 2);178 if (ret)179 return ret;180 181 hyst = (regdata[0] & ADT7X10_T_HYST_MASK) * 1000;182 183 /*184 * hysteresis is stored as a 4 bit offset in the device, convert it185 * to an absolute value186 */187 /* min has positive offset, others have negative */188 if (index == adt7x10_t_alarm_low)189 hyst = -hyst;190 191 *val = ADT7X10_REG_TO_TEMP(data, regdata[1]) - hyst;192 return 0;193}194 195static int adt7x10_hyst_write(struct adt7x10_data *data, long hyst)196{197 unsigned int regval;198 int limit, ret;199 200 mutex_lock(&data->update_lock);201 202 /* convert absolute hysteresis value to a 4 bit delta value */203 ret = regmap_read(data->regmap, ADT7X10_T_ALARM_HIGH, ®val);204 if (ret < 0)205 goto abort;206 207 limit = ADT7X10_REG_TO_TEMP(data, regval);208 209 hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX);210 regval = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), 0,211 ADT7X10_T_HYST_MASK);212 ret = regmap_write(data->regmap, ADT7X10_T_HYST, regval);213abort:214 mutex_unlock(&data->update_lock);215 return ret;216}217 218static int adt7x10_alarm_read(struct adt7x10_data *data, int index, long *val)219{220 unsigned int status;221 int ret;222 223 ret = regmap_read(data->regmap, ADT7X10_STATUS, &status);224 if (ret < 0)225 return ret;226 227 *val = !!(status & index);228 229 return 0;230}231 232static umode_t adt7x10_is_visible(const void *data,233 enum hwmon_sensor_types type,234 u32 attr, int channel)235{236 switch (attr) {237 case hwmon_temp_max:238 case hwmon_temp_min:239 case hwmon_temp_crit:240 case hwmon_temp_max_hyst:241 return 0644;242 case hwmon_temp_input:243 case hwmon_temp_min_alarm:244 case hwmon_temp_max_alarm:245 case hwmon_temp_crit_alarm:246 case hwmon_temp_min_hyst:247 case hwmon_temp_crit_hyst:248 return 0444;249 default:250 break;251 }252 253 return 0;254}255 256static int adt7x10_read(struct device *dev, enum hwmon_sensor_types type,257 u32 attr, int channel, long *val)258{259 struct adt7x10_data *data = dev_get_drvdata(dev);260 261 switch (attr) {262 case hwmon_temp_input:263 return adt7x10_temp_read(data, adt7x10_temperature, val);264 case hwmon_temp_max:265 return adt7x10_temp_read(data, adt7x10_t_alarm_high, val);266 case hwmon_temp_min:267 return adt7x10_temp_read(data, adt7x10_t_alarm_low, val);268 case hwmon_temp_crit:269 return adt7x10_temp_read(data, adt7x10_t_crit, val);270 case hwmon_temp_max_hyst:271 return adt7x10_hyst_read(data, adt7x10_t_alarm_high, val);272 case hwmon_temp_min_hyst:273 return adt7x10_hyst_read(data, adt7x10_t_alarm_low, val);274 case hwmon_temp_crit_hyst:275 return adt7x10_hyst_read(data, adt7x10_t_crit, val);276 case hwmon_temp_min_alarm:277 return adt7x10_alarm_read(data, ADT7X10_STAT_T_LOW, val);278 case hwmon_temp_max_alarm:279 return adt7x10_alarm_read(data, ADT7X10_STAT_T_HIGH, val);280 case hwmon_temp_crit_alarm:281 return adt7x10_alarm_read(data, ADT7X10_STAT_T_CRIT, val);282 default:283 return -EOPNOTSUPP;284 }285}286 287static int adt7x10_write(struct device *dev, enum hwmon_sensor_types type,288 u32 attr, int channel, long val)289{290 struct adt7x10_data *data = dev_get_drvdata(dev);291 292 switch (attr) {293 case hwmon_temp_max:294 return adt7x10_temp_write(data, adt7x10_t_alarm_high, val);295 case hwmon_temp_min:296 return adt7x10_temp_write(data, adt7x10_t_alarm_low, val);297 case hwmon_temp_crit:298 return adt7x10_temp_write(data, adt7x10_t_crit, val);299 case hwmon_temp_max_hyst:300 return adt7x10_hyst_write(data, val);301 default:302 return -EOPNOTSUPP;303 }304}305 306static const struct hwmon_channel_info * const adt7x10_info[] = {307 HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |308 HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_MIN_HYST |309 HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |310 HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM),311 NULL,312};313 314static const struct hwmon_ops adt7x10_hwmon_ops = {315 .is_visible = adt7x10_is_visible,316 .read = adt7x10_read,317 .write = adt7x10_write,318};319 320static const struct hwmon_chip_info adt7x10_chip_info = {321 .ops = &adt7x10_hwmon_ops,322 .info = adt7x10_info,323};324 325static void adt7x10_restore_config(void *private)326{327 struct adt7x10_data *data = private;328 329 regmap_write(data->regmap, ADT7X10_CONFIG, data->oldconfig);330}331 332int adt7x10_probe(struct device *dev, const char *name, int irq,333 struct regmap *regmap)334{335 struct adt7x10_data *data;336 unsigned int config;337 struct device *hdev;338 int ret;339 340 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);341 if (!data)342 return -ENOMEM;343 344 data->regmap = regmap;345 346 dev_set_drvdata(dev, data);347 mutex_init(&data->update_lock);348 349 /* configure as specified */350 ret = regmap_read(regmap, ADT7X10_CONFIG, &config);351 if (ret < 0) {352 dev_dbg(dev, "Can't read config? %d\n", ret);353 return ret;354 }355 data->oldconfig = config;356 357 /*358 * Set to 16 bit resolution, continous conversion and comparator mode.359 */360 data->config = data->oldconfig;361 data->config &= ~(ADT7X10_MODE_MASK | ADT7X10_CT_POLARITY |362 ADT7X10_INT_POLARITY);363 data->config |= ADT7X10_FULL | ADT7X10_RESOLUTION | ADT7X10_EVENT_MODE;364 365 if (data->config != data->oldconfig) {366 ret = regmap_write(regmap, ADT7X10_CONFIG, data->config);367 if (ret)368 return ret;369 ret = devm_add_action_or_reset(dev, adt7x10_restore_config, data);370 if (ret)371 return ret;372 }373 dev_dbg(dev, "Config %02x\n", data->config);374 375 hdev = devm_hwmon_device_register_with_info(dev, name, data,376 &adt7x10_chip_info, NULL);377 if (IS_ERR(hdev))378 return PTR_ERR(hdev);379 380 if (irq > 0) {381 ret = devm_request_threaded_irq(dev, irq, NULL,382 adt7x10_irq_handler,383 IRQF_TRIGGER_FALLING |384 IRQF_ONESHOT,385 dev_name(dev), hdev);386 if (ret)387 return ret;388 }389 390 return 0;391}392EXPORT_SYMBOL_GPL(adt7x10_probe);393 394static int adt7x10_suspend(struct device *dev)395{396 struct adt7x10_data *data = dev_get_drvdata(dev);397 398 return regmap_write(data->regmap, ADT7X10_CONFIG,399 data->config | ADT7X10_PD);400}401 402static int adt7x10_resume(struct device *dev)403{404 struct adt7x10_data *data = dev_get_drvdata(dev);405 406 return regmap_write(data->regmap, ADT7X10_CONFIG, data->config);407}408 409EXPORT_SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume);410 411MODULE_AUTHOR("Hartmut Knaack");412MODULE_DESCRIPTION("ADT7410/ADT7420, ADT7310/ADT7320 common code");413MODULE_LICENSE("GPL");414