2016-12-01 18:32:41 -08:00
/* Texas Instruments TMP108 SMBus temperature sensor driver
*
* Copyright ( C ) 2016 John Muir < john @ jmuir . com >
*
* 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 ,
& regval ) ;
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 , & regval ) ;
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 , & regval ) ;
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 , & regval ) ;
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 , & regval ) ;
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 , & regval ) ;
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 ,
& regval ) ;
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 ,
2018-09-01 09:50:41 -07:00
. use_single_read = true ,
. use_single_write = true ,
2016-12-01 18:32:41 -08:00
} ;
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 <john@jmuir.com> " ) ;
MODULE_DESCRIPTION ( " Texas Instruments TMP108 temperature sensor driver " ) ;
MODULE_LICENSE ( " GPL " ) ;