brintos

brintos / linux-shallow public Read only

0
0
Text · 10.3 KiB · 2d32939 Raw
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], &regval);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, &regval);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