diff options
Diffstat (limited to 'drivers/hwmon/tmp108.c')
| -rw-r--r-- | drivers/hwmon/tmp108.c | 469 | 
1 files changed, 469 insertions, 0 deletions
diff --git a/drivers/hwmon/tmp108.c b/drivers/hwmon/tmp108.c new file mode 100644 index 000000000000..91bb94639286 --- /dev/null +++ b/drivers/hwmon/tmp108.c @@ -0,0 +1,469 @@ +/* Texas Instruments TMP108 SMBus temperature sensor driver + * + * Copyright (C) 2016 John Muir <[email protected]> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define	DRIVER_NAME "tmp108" + +#define	TMP108_REG_TEMP		0x00 +#define	TMP108_REG_CONF		0x01 +#define	TMP108_REG_TLOW		0x02 +#define	TMP108_REG_THIGH	0x03 + +#define TMP108_TEMP_MIN_MC	-50000 /* Minimum millicelcius. */ +#define TMP108_TEMP_MAX_MC	127937 /* Maximum millicelcius. */ + +/* Configuration register bits. + * Note: these bit definitions are byte swapped. + */ +#define TMP108_CONF_M0		0x0100 /* Sensor mode. */ +#define TMP108_CONF_M1		0x0200 +#define TMP108_CONF_TM		0x0400 /* Thermostat mode. */ +#define TMP108_CONF_FL		0x0800 /* Watchdog flag - TLOW */ +#define TMP108_CONF_FH		0x1000 /* Watchdog flag - THIGH */ +#define TMP108_CONF_CR0		0x2000 /* Conversion rate. */ +#define TMP108_CONF_CR1		0x4000 +#define TMP108_CONF_ID		0x8000 +#define TMP108_CONF_HYS0	0x0010 /* Hysteresis. */ +#define TMP108_CONF_HYS1	0x0020 +#define TMP108_CONF_POL		0x0080 /* Polarity of alert. */ + +/* Defaults set by the hardware upon reset. */ +#define TMP108_CONF_DEFAULTS		(TMP108_CONF_CR0 | TMP108_CONF_TM |\ +					 TMP108_CONF_HYS0 | TMP108_CONF_M1) +/* These bits are read-only. */ +#define TMP108_CONF_READ_ONLY		(TMP108_CONF_FL | TMP108_CONF_FH |\ +					 TMP108_CONF_ID) + +#define TMP108_CONF_MODE_MASK		(TMP108_CONF_M0|TMP108_CONF_M1) +#define TMP108_MODE_SHUTDOWN		0x0000 +#define TMP108_MODE_ONE_SHOT		TMP108_CONF_M0 +#define TMP108_MODE_CONTINUOUS		TMP108_CONF_M1		/* Default */ +					/* When M1 is set, M0 is ignored. */ + +#define TMP108_CONF_CONVRATE_MASK	(TMP108_CONF_CR0|TMP108_CONF_CR1) +#define TMP108_CONVRATE_0P25HZ		0x0000 +#define TMP108_CONVRATE_1HZ		TMP108_CONF_CR0		/* Default */ +#define TMP108_CONVRATE_4HZ		TMP108_CONF_CR1 +#define TMP108_CONVRATE_16HZ		(TMP108_CONF_CR0|TMP108_CONF_CR1) + +#define TMP108_CONF_HYSTERESIS_MASK	(TMP108_CONF_HYS0|TMP108_CONF_HYS1) +#define TMP108_HYSTERESIS_0C		0x0000 +#define TMP108_HYSTERESIS_1C		TMP108_CONF_HYS0	/* Default */ +#define TMP108_HYSTERESIS_2C		TMP108_CONF_HYS1 +#define TMP108_HYSTERESIS_4C		(TMP108_CONF_HYS0|TMP108_CONF_HYS1) + +#define TMP108_CONVERSION_TIME_MS	30	/* in milli-seconds */ + +struct tmp108 { +	struct regmap *regmap; +	u16 orig_config; +	unsigned long ready_time; +}; + +/* convert 12-bit TMP108 register value to milliCelsius */ +static inline int tmp108_temp_reg_to_mC(s16 val) +{ +	return (val & ~0x0f) * 1000 / 256; +} + +/* convert milliCelsius to left adjusted 12-bit TMP108 register value */ +static inline u16 tmp108_mC_to_temp_reg(int val) +{ +	return (val * 256) / 1000; +} + +static int tmp108_read(struct device *dev, enum hwmon_sensor_types type, +		       u32 attr, int channel, long *temp) +{ +	struct tmp108 *tmp108 = dev_get_drvdata(dev); +	unsigned int regval; +	int err, hyst; + +	if (type == hwmon_chip) { +		if (attr == hwmon_chip_update_interval) { +			err = regmap_read(tmp108->regmap, TMP108_REG_CONF, +					  ®val); +			if (err < 0) +				return err; +			switch (regval & TMP108_CONF_CONVRATE_MASK) { +			case TMP108_CONVRATE_0P25HZ: +			default: +				*temp = 4000; +				break; +			case TMP108_CONVRATE_1HZ: +				*temp = 1000; +				break; +			case TMP108_CONVRATE_4HZ: +				*temp = 250; +				break; +			case TMP108_CONVRATE_16HZ: +				*temp = 63; +				break; +			} +			return 0; +		} +		return -EOPNOTSUPP; +	} + +	switch (attr) { +	case hwmon_temp_input: +		/* Is it too early to return a conversion ? */ +		if (time_before(jiffies, tmp108->ready_time)) { +			dev_dbg(dev, "%s: Conversion not ready yet..\n", +				__func__); +			return -EAGAIN; +		} +		err = regmap_read(tmp108->regmap, TMP108_REG_TEMP, ®val); +		if (err < 0) +			return err; +		*temp = tmp108_temp_reg_to_mC(regval); +		break; +	case hwmon_temp_min: +	case hwmon_temp_max: +		err = regmap_read(tmp108->regmap, attr == hwmon_temp_min ? +				  TMP108_REG_TLOW : TMP108_REG_THIGH, ®val); +		if (err < 0) +			return err; +		*temp = tmp108_temp_reg_to_mC(regval); +		break; +	case hwmon_temp_min_alarm: +	case hwmon_temp_max_alarm: +		err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val); +		if (err < 0) +			return err; +		*temp = !!(regval & (attr == hwmon_temp_min_alarm ? +				     TMP108_CONF_FL : TMP108_CONF_FH)); +		break; +	case hwmon_temp_min_hyst: +	case hwmon_temp_max_hyst: +		err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val); +		if (err < 0) +			return err; +		switch (regval & TMP108_CONF_HYSTERESIS_MASK) { +		case TMP108_HYSTERESIS_0C: +		default: +			hyst = 0; +			break; +		case TMP108_HYSTERESIS_1C: +			hyst = 1000; +			break; +		case TMP108_HYSTERESIS_2C: +			hyst = 2000; +			break; +		case TMP108_HYSTERESIS_4C: +			hyst = 4000; +			break; +		} +		err = regmap_read(tmp108->regmap, attr == hwmon_temp_min_hyst ? +				  TMP108_REG_TLOW : TMP108_REG_THIGH, ®val); +		if (err < 0) +			return err; +		*temp = tmp108_temp_reg_to_mC(regval); +		if (attr == hwmon_temp_min_hyst) +			*temp += hyst; +		else +			*temp -= hyst; +		break; +	default: +		return -EOPNOTSUPP; +	} + +	return 0; +} + +static int tmp108_write(struct device *dev, enum hwmon_sensor_types type, +			u32 attr, int channel, long temp) +{ +	struct tmp108 *tmp108 = dev_get_drvdata(dev); +	u32 regval, mask; +	int err; + +	if (type == hwmon_chip) { +		if (attr == hwmon_chip_update_interval) { +			if (temp < 156) +				mask = TMP108_CONVRATE_16HZ; +			else if (temp < 625) +				mask = TMP108_CONVRATE_4HZ; +			else if (temp < 2500) +				mask = TMP108_CONVRATE_1HZ; +			else +				mask = TMP108_CONVRATE_0P25HZ; +			return regmap_update_bits(tmp108->regmap, +						  TMP108_REG_CONF, +						  TMP108_CONF_CONVRATE_MASK, +						  mask); +		} +		return -EOPNOTSUPP; +	} + +	switch (attr) { +	case hwmon_temp_min: +	case hwmon_temp_max: +		temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC); +		return regmap_write(tmp108->regmap, +				    attr == hwmon_temp_min ? +					TMP108_REG_TLOW : TMP108_REG_THIGH, +				    tmp108_mC_to_temp_reg(temp)); +	case hwmon_temp_min_hyst: +	case hwmon_temp_max_hyst: +		temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC); +		err = regmap_read(tmp108->regmap, +				  attr == hwmon_temp_min_hyst ? +					TMP108_REG_TLOW : TMP108_REG_THIGH, +				  ®val); +		if (err < 0) +			return err; +		if (attr == hwmon_temp_min_hyst) +			temp -= tmp108_temp_reg_to_mC(regval); +		else +			temp = tmp108_temp_reg_to_mC(regval) - temp; +		if (temp < 500) +			mask = TMP108_HYSTERESIS_0C; +		else if (temp < 1500) +			mask = TMP108_HYSTERESIS_1C; +		else if (temp < 3000) +			mask = TMP108_HYSTERESIS_2C; +		else +			mask = TMP108_HYSTERESIS_4C; +		return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, +					  TMP108_CONF_HYSTERESIS_MASK, mask); +	default: +		return -EOPNOTSUPP; +	} +} + +static umode_t tmp108_is_visible(const void *data, enum hwmon_sensor_types type, +				 u32 attr, int channel) +{ +	if (type == hwmon_chip && attr == hwmon_chip_update_interval) +		return 0644; + +	if (type != hwmon_temp) +		return 0; + +	switch (attr) { +	case hwmon_temp_input: +	case hwmon_temp_min_alarm: +	case hwmon_temp_max_alarm: +		return 0444; +	case hwmon_temp_min: +	case hwmon_temp_max: +	case hwmon_temp_min_hyst: +	case hwmon_temp_max_hyst: +		return 0644; +	default: +		return 0; +	} +} + +static u32 tmp108_chip_config[] = { +	HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, +	0 +}; + +static const struct hwmon_channel_info tmp108_chip = { +	.type = hwmon_chip, +	.config = tmp108_chip_config, +}; + +static u32 tmp108_temp_config[] = { +	HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_MIN_HYST +		| HWMON_T_MAX_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM, +	0 +}; + +static const struct hwmon_channel_info tmp108_temp = { +	.type = hwmon_temp, +	.config = tmp108_temp_config, +}; + +static const struct hwmon_channel_info *tmp108_info[] = { +	&tmp108_chip, +	&tmp108_temp, +	NULL +}; + +static const struct hwmon_ops tmp108_hwmon_ops = { +	.is_visible = tmp108_is_visible, +	.read = tmp108_read, +	.write = tmp108_write, +}; + +static const struct hwmon_chip_info tmp108_chip_info = { +	.ops = &tmp108_hwmon_ops, +	.info = tmp108_info, +}; + +static void tmp108_restore_config(void *data) +{ +	struct tmp108 *tmp108 = data; + +	regmap_write(tmp108->regmap, TMP108_REG_CONF, tmp108->orig_config); +} + +static bool tmp108_is_writeable_reg(struct device *dev, unsigned int reg) +{ +	return reg != TMP108_REG_TEMP; +} + +static bool tmp108_is_volatile_reg(struct device *dev, unsigned int reg) +{ +	/* Configuration register must be volatile to enable FL and FH. */ +	return reg == TMP108_REG_TEMP || reg == TMP108_REG_CONF; +} + +static const struct regmap_config tmp108_regmap_config = { +	.reg_bits = 8, +	.val_bits = 16, +	.max_register = TMP108_REG_THIGH, +	.writeable_reg = tmp108_is_writeable_reg, +	.volatile_reg = tmp108_is_volatile_reg, +	.val_format_endian = REGMAP_ENDIAN_BIG, +	.cache_type = REGCACHE_RBTREE, +	.use_single_rw = true, +}; + +static int tmp108_probe(struct i2c_client *client, +			const struct i2c_device_id *id) +{ +	struct device *dev = &client->dev; +	struct device *hwmon_dev; +	struct tmp108 *tmp108; +	int err; +	u32 config; + +	if (!i2c_check_functionality(client->adapter, +				     I2C_FUNC_SMBUS_WORD_DATA)) { +		dev_err(dev, +			"adapter doesn't support SMBus word transactions\n"); +		return -ENODEV; +	} + +	tmp108 = devm_kzalloc(dev, sizeof(*tmp108), GFP_KERNEL); +	if (!tmp108) +		return -ENOMEM; + +	dev_set_drvdata(dev, tmp108); + +	tmp108->regmap = devm_regmap_init_i2c(client, &tmp108_regmap_config); +	if (IS_ERR(tmp108->regmap)) { +		err = PTR_ERR(tmp108->regmap); +		dev_err(dev, "regmap init failed: %d", err); +		return err; +	} + +	err = regmap_read(tmp108->regmap, TMP108_REG_CONF, &config); +	if (err < 0) { +		dev_err(dev, "error reading config register: %d", err); +		return err; +	} +	tmp108->orig_config = config; + +	/* Only continuous mode is supported. */ +	config &= ~TMP108_CONF_MODE_MASK; +	config |= TMP108_MODE_CONTINUOUS; + +	/* Only comparator mode is supported. */ +	config &= ~TMP108_CONF_TM; + +	err = regmap_write(tmp108->regmap, TMP108_REG_CONF, config); +	if (err < 0) { +		dev_err(dev, "error writing config register: %d", err); +		return err; +	} + +	tmp108->ready_time = jiffies; +	if ((tmp108->orig_config & TMP108_CONF_MODE_MASK) == +	    TMP108_MODE_SHUTDOWN) +		tmp108->ready_time += +			msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); + +	err = devm_add_action_or_reset(dev, tmp108_restore_config, tmp108); +	if (err) { +		dev_err(dev, "add action or reset failed: %d", err); +		return err; +	} + +	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, +							 tmp108, +							 &tmp108_chip_info, +							 NULL); +	return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int __maybe_unused tmp108_suspend(struct device *dev) +{ +	struct tmp108 *tmp108 = dev_get_drvdata(dev); + +	return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, +				  TMP108_CONF_MODE_MASK, TMP108_MODE_SHUTDOWN); +} + +static int __maybe_unused tmp108_resume(struct device *dev) +{ +	struct tmp108 *tmp108 = dev_get_drvdata(dev); +	int err; + +	err = regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, +				 TMP108_CONF_MODE_MASK, TMP108_MODE_CONTINUOUS); +	tmp108->ready_time = jiffies + +			     msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); +	return err; +} + +static SIMPLE_DEV_PM_OPS(tmp108_dev_pm_ops, tmp108_suspend, tmp108_resume); + +static const struct i2c_device_id tmp108_i2c_ids[] = { +	{ "tmp108", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tmp108_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id tmp108_of_ids[] = { +	{ .compatible = "ti,tmp108", }, +	{} +}; +MODULE_DEVICE_TABLE(of, tmp108_of_ids); +#endif + +static struct i2c_driver tmp108_driver = { +	.driver = { +		.name	= DRIVER_NAME, +		.pm	= &tmp108_dev_pm_ops, +		.of_match_table = of_match_ptr(tmp108_of_ids), +	}, +	.probe		= tmp108_probe, +	.id_table	= tmp108_i2c_ids, +}; + +module_i2c_driver(tmp108_driver); + +MODULE_AUTHOR("John Muir <[email protected]>"); +MODULE_DESCRIPTION("Texas Instruments TMP108 temperature sensor driver"); +MODULE_LICENSE("GPL");  |