2012-05-12 12:36:38 +04:00
/*
* Driver for Texas Instruments INA219 , INA226 power monitor chips
*
* INA219 :
* Zero Drift Bi - Directional Current / Power Monitor with I2C Interface
* Datasheet : http : //www.ti.com/product/ina219
*
2012-05-12 22:33:11 +04:00
* INA220 :
* Bi - Directional Current / Power Monitor with I2C Interface
* Datasheet : http : //www.ti.com/product/ina220
*
2012-05-12 12:36:38 +04:00
* INA226 :
* Bi - Directional Current / Power Monitor with I2C Interface
* Datasheet : http : //www.ti.com/product/ina226
*
2012-05-12 22:33:11 +04:00
* INA230 :
* Bi - directional Current / Power Monitor with I2C Interface
* Datasheet : http : //www.ti.com/product/ina230
*
2012-05-12 12:36:38 +04:00
* Copyright ( C ) 2012 Lothar Felten < l - felten @ ti . com >
* Thanks to Jan Volkering
*
* 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 ; version 2 of the License .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/i2c.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
2012-10-10 17:25:56 +04:00
# include <linux/jiffies.h>
2013-06-19 10:50:20 +04:00
# include <linux/of.h>
2012-05-12 12:36:38 +04:00
# include <linux/platform_data/ina2xx.h>
/* common register definitions */
# define INA2XX_CONFIG 0x00
# define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */
# define INA2XX_BUS_VOLTAGE 0x02 /* readonly */
# define INA2XX_POWER 0x03 /* readonly */
# define INA2XX_CURRENT 0x04 /* readonly */
# define INA2XX_CALIBRATION 0x05
/* INA226 register definitions */
# define INA226_MASK_ENABLE 0x06
# define INA226_ALERT_LIMIT 0x07
# define INA226_DIE_ID 0xFF
/* register count */
# define INA219_REGISTERS 6
# define INA226_REGISTERS 8
# define INA2XX_MAX_REGISTERS 8
/* settings - depend on use case */
# define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */
# define INA226_CONFIG_DEFAULT 0x4527 /* averages=16 */
/* worst case is 68.10 ms (~14.6Hz, ina219) */
# define INA2XX_CONVERSION_RATE 15
enum ina2xx_ids { ina219 , ina226 } ;
2012-05-12 22:21:01 +04:00
struct ina2xx_config {
u16 config_default ;
int calibration_factor ;
int registers ;
int shunt_div ;
int bus_voltage_shift ;
int bus_voltage_lsb ; /* uV */
int power_lsb ; /* uW */
} ;
2012-05-12 12:36:38 +04:00
struct ina2xx_data {
struct device * hwmon_dev ;
2012-05-12 22:21:01 +04:00
const struct ina2xx_config * config ;
2012-05-12 12:36:38 +04:00
struct mutex update_lock ;
bool valid ;
unsigned long last_updated ;
int kind ;
u16 regs [ INA2XX_MAX_REGISTERS ] ;
} ;
2012-05-12 22:21:01 +04:00
static const struct ina2xx_config ina2xx_config [ ] = {
[ ina219 ] = {
. config_default = INA219_CONFIG_DEFAULT ,
. calibration_factor = 40960000 ,
. registers = INA219_REGISTERS ,
. shunt_div = 100 ,
. bus_voltage_shift = 3 ,
. bus_voltage_lsb = 4000 ,
. power_lsb = 20000 ,
} ,
[ ina226 ] = {
. config_default = INA226_CONFIG_DEFAULT ,
. calibration_factor = 5120000 ,
. registers = INA226_REGISTERS ,
. shunt_div = 400 ,
. bus_voltage_shift = 0 ,
. bus_voltage_lsb = 1250 ,
. power_lsb = 25000 ,
} ,
} ;
2012-05-12 12:36:38 +04:00
static struct ina2xx_data * ina2xx_update_device ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct ina2xx_data * data = i2c_get_clientdata ( client ) ;
struct ina2xx_data * ret = data ;
mutex_lock ( & data - > update_lock ) ;
if ( time_after ( jiffies , data - > last_updated +
HZ / INA2XX_CONVERSION_RATE ) | | ! data - > valid ) {
int i ;
dev_dbg ( & client - > dev , " Starting ina2xx update \n " ) ;
/* Read all registers */
2012-05-12 22:21:01 +04:00
for ( i = 0 ; i < data - > config - > registers ; i + + ) {
2012-09-11 19:22:14 +04:00
int rv = i2c_smbus_read_word_swapped ( client , i ) ;
2012-05-12 12:36:38 +04:00
if ( rv < 0 ) {
ret = ERR_PTR ( rv ) ;
goto abort ;
}
data - > regs [ i ] = rv ;
}
data - > last_updated = jiffies ;
data - > valid = 1 ;
}
abort :
mutex_unlock ( & data - > update_lock ) ;
return ret ;
}
2012-05-12 22:21:01 +04:00
static int ina2xx_get_value ( struct ina2xx_data * data , u8 reg )
2012-05-12 12:36:38 +04:00
{
2012-05-12 22:21:01 +04:00
int val ;
2012-05-12 12:36:38 +04:00
switch ( reg ) {
case INA2XX_SHUNT_VOLTAGE :
2012-05-12 22:21:01 +04:00
val = DIV_ROUND_CLOSEST ( data - > regs [ reg ] ,
data - > config - > shunt_div ) ;
2012-05-12 12:36:38 +04:00
break ;
case INA2XX_BUS_VOLTAGE :
2012-05-12 22:21:01 +04:00
val = ( data - > regs [ reg ] > > data - > config - > bus_voltage_shift )
* data - > config - > bus_voltage_lsb ;
val = DIV_ROUND_CLOSEST ( val , 1000 ) ;
2012-05-12 12:36:38 +04:00
break ;
case INA2XX_POWER :
2012-05-12 22:21:01 +04:00
val = data - > regs [ reg ] * data - > config - > power_lsb ;
2012-05-12 12:36:38 +04:00
break ;
case INA2XX_CURRENT :
/* LSB=1mA (selected). Is in mA */
2012-05-12 22:21:01 +04:00
val = data - > regs [ reg ] ;
2012-05-12 12:36:38 +04:00
break ;
default :
/* programmer goofed */
WARN_ON_ONCE ( 1 ) ;
val = 0 ;
break ;
}
return val ;
}
static ssize_t ina2xx_show_value ( struct device * dev ,
struct device_attribute * da , char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
struct ina2xx_data * data = ina2xx_update_device ( dev ) ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
2012-05-12 22:21:01 +04:00
return snprintf ( buf , PAGE_SIZE , " %d \n " ,
ina2xx_get_value ( data , attr - > index ) ) ;
2012-05-12 12:36:38 +04:00
}
/* shunt voltage */
2013-01-10 22:04:06 +04:00
static SENSOR_DEVICE_ATTR ( in0_input , S_IRUGO , ina2xx_show_value , NULL ,
INA2XX_SHUNT_VOLTAGE ) ;
2012-05-12 12:36:38 +04:00
/* bus voltage */
2013-01-10 22:04:06 +04:00
static SENSOR_DEVICE_ATTR ( in1_input , S_IRUGO , ina2xx_show_value , NULL ,
INA2XX_BUS_VOLTAGE ) ;
2012-05-12 12:36:38 +04:00
/* calculated current */
2013-01-10 22:04:06 +04:00
static SENSOR_DEVICE_ATTR ( curr1_input , S_IRUGO , ina2xx_show_value , NULL ,
INA2XX_CURRENT ) ;
2012-05-12 12:36:38 +04:00
/* calculated power */
2013-01-10 22:04:06 +04:00
static SENSOR_DEVICE_ATTR ( power1_input , S_IRUGO , ina2xx_show_value , NULL ,
INA2XX_POWER ) ;
2012-05-12 12:36:38 +04:00
/* pointers to created device attributes */
static struct attribute * ina2xx_attributes [ ] = {
& sensor_dev_attr_in0_input . dev_attr . attr ,
& sensor_dev_attr_in1_input . dev_attr . attr ,
& sensor_dev_attr_curr1_input . dev_attr . attr ,
& sensor_dev_attr_power1_input . dev_attr . attr ,
NULL ,
} ;
static const struct attribute_group ina2xx_group = {
. attrs = ina2xx_attributes ,
} ;
static int ina2xx_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct i2c_adapter * adapter = client - > adapter ;
struct ina2xx_data * data ;
struct ina2xx_platform_data * pdata ;
2012-05-12 22:21:01 +04:00
int ret ;
2013-06-19 10:50:20 +04:00
u32 val ;
2012-05-12 12:36:38 +04:00
long shunt = 10000 ; /* default shunt value 10mOhms */
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_WORD_DATA ) )
return - ENODEV ;
data = devm_kzalloc ( & client - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
if ( client - > dev . platform_data ) {
pdata =
( struct ina2xx_platform_data * ) client - > dev . platform_data ;
shunt = pdata - > shunt_uohms ;
2013-06-19 10:50:20 +04:00
} else if ( ! of_property_read_u32 ( client - > dev . of_node ,
" shunt-resistor " , & val ) ) {
shunt = val ;
2012-05-12 12:36:38 +04:00
}
if ( shunt < = 0 )
return - ENODEV ;
/* set the device type */
data - > kind = id - > driver_data ;
2012-05-12 22:21:01 +04:00
data - > config = & ina2xx_config [ data - > kind ] ;
2012-05-12 12:36:38 +04:00
2012-05-12 22:21:01 +04:00
/* device configuration */
i2c_smbus_write_word_swapped ( client , INA2XX_CONFIG ,
data - > config - > config_default ) ;
/* set current LSB to 1mA, shunt is in uOhms */
/* (equation 13 in datasheet) */
i2c_smbus_write_word_swapped ( client , INA2XX_CALIBRATION ,
data - > config - > calibration_factor / shunt ) ;
2012-05-12 12:36:38 +04:00
i2c_set_clientdata ( client , data ) ;
mutex_init ( & data - > update_lock ) ;
ret = sysfs_create_group ( & client - > dev . kobj , & ina2xx_group ) ;
if ( ret )
return ret ;
data - > hwmon_dev = hwmon_device_register ( & client - > dev ) ;
if ( IS_ERR ( data - > hwmon_dev ) ) {
ret = PTR_ERR ( data - > hwmon_dev ) ;
goto out_err_hwmon ;
}
2012-05-12 22:21:01 +04:00
dev_info ( & client - > dev , " power monitor %s (Rshunt = %li uOhm) \n " ,
id - > name , shunt ) ;
2012-05-12 12:36:38 +04:00
return 0 ;
out_err_hwmon :
sysfs_remove_group ( & client - > dev . kobj , & ina2xx_group ) ;
return ret ;
}
static int ina2xx_remove ( struct i2c_client * client )
{
struct ina2xx_data * data = i2c_get_clientdata ( client ) ;
hwmon_device_unregister ( data - > hwmon_dev ) ;
sysfs_remove_group ( & client - > dev . kobj , & ina2xx_group ) ;
return 0 ;
}
static const struct i2c_device_id ina2xx_id [ ] = {
{ " ina219 " , ina219 } ,
2012-05-12 22:33:11 +04:00
{ " ina220 " , ina219 } ,
2012-05-12 12:36:38 +04:00
{ " ina226 " , ina226 } ,
2012-05-12 22:33:11 +04:00
{ " ina230 " , ina226 } ,
2012-05-12 12:36:38 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , ina2xx_id ) ;
static struct i2c_driver ina2xx_driver = {
. driver = {
. name = " ina2xx " ,
} ,
. probe = ina2xx_probe ,
. remove = ina2xx_remove ,
. id_table = ina2xx_id ,
} ;
2012-10-08 16:40:35 +04:00
module_i2c_driver ( ina2xx_driver ) ;
2012-05-12 12:36:38 +04:00
MODULE_AUTHOR ( " Lothar Felten <l-felten@ti.com> " ) ;
MODULE_DESCRIPTION ( " ina2xx driver " ) ;
MODULE_LICENSE ( " GPL " ) ;