2019-10-21 18:41:14 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Analog Devices LTC2947 high precision power and energy monitor
*
* Copyright 2019 Analog Devices Inc .
*/
# include <linux/bitfield.h>
# include <linux/bits.h>
# include <linux/clk.h>
# include <linux/device.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/regmap.h>
# include "ltc2947.h"
/* register's */
# define LTC2947_REG_PAGE_CTRL 0xFF
# define LTC2947_REG_CTRL 0xF0
# define LTC2947_REG_TBCTL 0xE9
# define LTC2947_CONT_MODE_MASK BIT(3)
# define LTC2947_CONT_MODE(x) FIELD_PREP(LTC2947_CONT_MODE_MASK, x)
# define LTC2947_PRE_MASK GENMASK(2, 0)
# define LTC2947_PRE(x) FIELD_PREP(LTC2947_PRE_MASK, x)
# define LTC2947_DIV_MASK GENMASK(7, 3)
# define LTC2947_DIV(x) FIELD_PREP(LTC2947_DIV_MASK, x)
# define LTC2947_SHUTDOWN_MASK BIT(0)
# define LTC2947_REG_ACCUM_POL 0xE1
# define LTC2947_ACCUM_POL_1_MASK GENMASK(1, 0)
# define LTC2947_ACCUM_POL_1(x) FIELD_PREP(LTC2947_ACCUM_POL_1_MASK, x)
# define LTC2947_ACCUM_POL_2_MASK GENMASK(3, 2)
# define LTC2947_ACCUM_POL_2(x) FIELD_PREP(LTC2947_ACCUM_POL_2_MASK, x)
# define LTC2947_REG_ACCUM_DEADBAND 0xE4
# define LTC2947_REG_GPIOSTATCTL 0x67
# define LTC2947_GPIO_EN_MASK BIT(0)
# define LTC2947_GPIO_EN(x) FIELD_PREP(LTC2947_GPIO_EN_MASK, x)
# define LTC2947_GPIO_FAN_EN_MASK BIT(6)
# define LTC2947_GPIO_FAN_EN(x) FIELD_PREP(LTC2947_GPIO_FAN_EN_MASK, x)
# define LTC2947_GPIO_FAN_POL_MASK BIT(7)
# define LTC2947_GPIO_FAN_POL(x) FIELD_PREP(LTC2947_GPIO_FAN_POL_MASK, x)
# define LTC2947_REG_GPIO_ACCUM 0xE3
/* 200Khz */
# define LTC2947_CLK_MIN 200000
/* 25Mhz */
# define LTC2947_CLK_MAX 25000000
# define LTC2947_PAGE0 0
# define LTC2947_PAGE1 1
/* Voltage registers */
# define LTC2947_REG_VOLTAGE 0xA0
# define LTC2947_REG_VOLTAGE_MAX 0x50
# define LTC2947_REG_VOLTAGE_MIN 0x52
# define LTC2947_REG_VOLTAGE_THRE_H 0x90
# define LTC2947_REG_VOLTAGE_THRE_L 0x92
# define LTC2947_REG_DVCC 0xA4
# define LTC2947_REG_DVCC_MAX 0x58
# define LTC2947_REG_DVCC_MIN 0x5A
# define LTC2947_REG_DVCC_THRE_H 0x98
# define LTC2947_REG_DVCC_THRE_L 0x9A
# define LTC2947_VOLTAGE_GEN_CHAN 0
# define LTC2947_VOLTAGE_DVCC_CHAN 1
/* in mV */
# define VOLTAGE_MAX 15500
# define VOLTAGE_MIN -300
# define VDVCC_MAX 15000
# define VDVCC_MIN 4750
/* Current registers */
# define LTC2947_REG_CURRENT 0x90
# define LTC2947_REG_CURRENT_MAX 0x40
# define LTC2947_REG_CURRENT_MIN 0x42
# define LTC2947_REG_CURRENT_THRE_H 0x80
# define LTC2947_REG_CURRENT_THRE_L 0x82
/* in mA */
# define CURRENT_MAX 30000
# define CURRENT_MIN -30000
/* Power registers */
# define LTC2947_REG_POWER 0x93
# define LTC2947_REG_POWER_MAX 0x44
# define LTC2947_REG_POWER_MIN 0x46
# define LTC2947_REG_POWER_THRE_H 0x84
# define LTC2947_REG_POWER_THRE_L 0x86
/* in uW */
# define POWER_MAX 450000000
# define POWER_MIN -450000000
/* Temperature registers */
# define LTC2947_REG_TEMP 0xA2
# define LTC2947_REG_TEMP_MAX 0x54
# define LTC2947_REG_TEMP_MIN 0x56
# define LTC2947_REG_TEMP_THRE_H 0x94
# define LTC2947_REG_TEMP_THRE_L 0x96
# define LTC2947_REG_TEMP_FAN_THRE_H 0x9C
# define LTC2947_REG_TEMP_FAN_THRE_L 0x9E
# define LTC2947_TEMP_FAN_CHAN 1
/* in millidegress Celsius */
# define TEMP_MAX 85000
# define TEMP_MIN -40000
/* Energy registers */
# define LTC2947_REG_ENERGY1 0x06
# define LTC2947_REG_ENERGY2 0x16
/* Status/Alarm/Overflow registers */
# define LTC2947_REG_STATUS 0x80
# define LTC2947_REG_STATVT 0x81
# define LTC2947_REG_STATIP 0x82
# define LTC2947_REG_STATVDVCC 0x87
# define LTC2947_ALERTS_SIZE (LTC2947_REG_STATVDVCC - LTC2947_REG_STATUS)
# define LTC2947_MAX_VOLTAGE_MASK BIT(0)
# define LTC2947_MIN_VOLTAGE_MASK BIT(1)
# define LTC2947_MAX_CURRENT_MASK BIT(0)
# define LTC2947_MIN_CURRENT_MASK BIT(1)
# define LTC2947_MAX_POWER_MASK BIT(2)
# define LTC2947_MIN_POWER_MASK BIT(3)
# define LTC2947_MAX_TEMP_MASK BIT(2)
# define LTC2947_MIN_TEMP_MASK BIT(3)
# define LTC2947_MAX_TEMP_FAN_MASK BIT(4)
# define LTC2947_MIN_TEMP_FAN_MASK BIT(5)
struct ltc2947_data {
struct regmap * map ;
struct device * dev ;
/*
* The mutex is needed because the device has 2 memory pages . When
* reading / writing the correct page needs to be set so that , the
* complete sequence select_page - > read / write needs to be protected .
*/
struct mutex lock ;
u32 lsb_energy ;
bool gpio_out ;
} ;
static int __ltc2947_val_read16 ( const struct ltc2947_data * st , const u8 reg ,
u64 * val )
{
__be16 __val = 0 ;
int ret ;
ret = regmap_bulk_read ( st - > map , reg , & __val , 2 ) ;
if ( ret )
return ret ;
* val = be16_to_cpu ( __val ) ;
return 0 ;
}
static int __ltc2947_val_read24 ( const struct ltc2947_data * st , const u8 reg ,
u64 * val )
{
__be32 __val = 0 ;
int ret ;
ret = regmap_bulk_read ( st - > map , reg , & __val , 3 ) ;
if ( ret )
return ret ;
* val = be32_to_cpu ( __val ) > > 8 ;
return 0 ;
}
static int __ltc2947_val_read64 ( const struct ltc2947_data * st , const u8 reg ,
u64 * val )
{
__be64 __val = 0 ;
int ret ;
ret = regmap_bulk_read ( st - > map , reg , & __val , 6 ) ;
if ( ret )
return ret ;
* val = be64_to_cpu ( __val ) > > 16 ;
return 0 ;
}
static int ltc2947_val_read ( struct ltc2947_data * st , const u8 reg ,
const u8 page , const size_t size , s64 * val )
{
int ret ;
u64 __val = 0 ;
mutex_lock ( & st - > lock ) ;
ret = regmap_write ( st - > map , LTC2947_REG_PAGE_CTRL , page ) ;
if ( ret ) {
mutex_unlock ( & st - > lock ) ;
return ret ;
}
dev_dbg ( st - > dev , " Read val, reg:%02X, p:%d sz:%zu \n " , reg , page ,
size ) ;
switch ( size ) {
case 2 :
ret = __ltc2947_val_read16 ( st , reg , & __val ) ;
break ;
case 3 :
ret = __ltc2947_val_read24 ( st , reg , & __val ) ;
break ;
case 6 :
ret = __ltc2947_val_read64 ( st , reg , & __val ) ;
break ;
default :
ret = - EINVAL ;
break ;
}
mutex_unlock ( & st - > lock ) ;
if ( ret )
return ret ;
* val = sign_extend64 ( __val , ( 8 * size ) - 1 ) ;
dev_dbg ( st - > dev , " Got s:%lld, u:%016llX \n " , * val , __val ) ;
return 0 ;
}
static int __ltc2947_val_write64 ( const struct ltc2947_data * st , const u8 reg ,
const u64 val )
{
__be64 __val ;
__val = cpu_to_be64 ( val < < 16 ) ;
return regmap_bulk_write ( st - > map , reg , & __val , 6 ) ;
}
static int __ltc2947_val_write16 ( const struct ltc2947_data * st , const u8 reg ,
const u16 val )
{
__be16 __val ;
__val = cpu_to_be16 ( val ) ;
return regmap_bulk_write ( st - > map , reg , & __val , 2 ) ;
}
static int ltc2947_val_write ( struct ltc2947_data * st , const u8 reg ,
const u8 page , const size_t size , const u64 val )
{
int ret ;
mutex_lock ( & st - > lock ) ;
/* set device on correct page */
ret = regmap_write ( st - > map , LTC2947_REG_PAGE_CTRL , page ) ;
if ( ret ) {
mutex_unlock ( & st - > lock ) ;
return ret ;
}
dev_dbg ( st - > dev , " Write val, r:%02X, p:%d, sz:%zu, val:%016llX \n " ,
reg , page , size , val ) ;
switch ( size ) {
case 2 :
ret = __ltc2947_val_write16 ( st , reg , val ) ;
break ;
case 6 :
ret = __ltc2947_val_write64 ( st , reg , val ) ;
break ;
default :
ret = - EINVAL ;
break ;
}
mutex_unlock ( & st - > lock ) ;
return ret ;
}
static int ltc2947_reset_history ( struct ltc2947_data * st , const u8 reg_h ,
const u8 reg_l )
{
int ret ;
/*
* let ' s reset the tracking register ' s . Tracking register ' s have all
* 2 bytes size
*/
ret = ltc2947_val_write ( st , reg_h , LTC2947_PAGE0 , 2 , 0x8000U ) ;
if ( ret )
return ret ;
return ltc2947_val_write ( st , reg_l , LTC2947_PAGE0 , 2 , 0x7FFFU ) ;
}
static int ltc2947_alarm_read ( struct ltc2947_data * st , const u8 reg ,
const u32 mask , long * val )
{
u8 offset = reg - LTC2947_REG_STATUS ;
/* +1 to include status reg */
char alarms [ LTC2947_ALERTS_SIZE + 1 ] ;
int ret = 0 ;
memset ( alarms , 0 , sizeof ( alarms ) ) ;
mutex_lock ( & st - > lock ) ;
ret = regmap_write ( st - > map , LTC2947_REG_PAGE_CTRL , LTC2947_PAGE0 ) ;
if ( ret )
goto unlock ;
dev_dbg ( st - > dev , " Read alarm, reg:%02X, mask:%02X \n " , reg , mask ) ;
/*
* As stated in the datasheet , when Threshold and Overflow registers
* are used , the status and all alert registers must be read in one
* multi - byte transaction .
*/
ret = regmap_bulk_read ( st - > map , LTC2947_REG_STATUS , alarms ,
sizeof ( alarms ) ) ;
if ( ret )
goto unlock ;
/* get the alarm */
* val = ! ! ( alarms [ offset ] & mask ) ;
unlock :
mutex_unlock ( & st - > lock ) ;
return ret ;
}
static ssize_t ltc2947_show_value ( struct device * dev ,
struct device_attribute * da , char * buf )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int ret ;
s64 val = 0 ;
ret = ltc2947_val_read ( st , attr - > index , LTC2947_PAGE0 , 6 , & val ) ;
if ( ret )
return ret ;
/* value in microJoule. st->lsb_energy was multiplied by 10E9 */
val = div_s64 ( val * st - > lsb_energy , 1000 ) ;
return sprintf ( buf , " %lld \n " , val ) ;
}
static int ltc2947_read_temp ( struct device * dev , const u32 attr , long * val ,
const int channel )
{
int ret ;
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
s64 __val = 0 ;
switch ( attr ) {
case hwmon_temp_input :
ret = ltc2947_val_read ( st , LTC2947_REG_TEMP , LTC2947_PAGE0 ,
2 , & __val ) ;
break ;
case hwmon_temp_highest :
ret = ltc2947_val_read ( st , LTC2947_REG_TEMP_MAX , LTC2947_PAGE0 ,
2 , & __val ) ;
break ;
case hwmon_temp_lowest :
ret = ltc2947_val_read ( st , LTC2947_REG_TEMP_MIN , LTC2947_PAGE0 ,
2 , & __val ) ;
break ;
case hwmon_temp_max_alarm :
if ( channel = = LTC2947_TEMP_FAN_CHAN )
return ltc2947_alarm_read ( st , LTC2947_REG_STATVT ,
LTC2947_MAX_TEMP_FAN_MASK ,
val ) ;
return ltc2947_alarm_read ( st , LTC2947_REG_STATVT ,
LTC2947_MAX_TEMP_MASK , val ) ;
case hwmon_temp_min_alarm :
if ( channel = = LTC2947_TEMP_FAN_CHAN )
return ltc2947_alarm_read ( st , LTC2947_REG_STATVT ,
LTC2947_MIN_TEMP_FAN_MASK ,
val ) ;
return ltc2947_alarm_read ( st , LTC2947_REG_STATVT ,
LTC2947_MIN_TEMP_MASK , val ) ;
case hwmon_temp_max :
if ( channel = = LTC2947_TEMP_FAN_CHAN )
ret = ltc2947_val_read ( st , LTC2947_REG_TEMP_FAN_THRE_H ,
LTC2947_PAGE1 , 2 , & __val ) ;
else
ret = ltc2947_val_read ( st , LTC2947_REG_TEMP_THRE_H ,
LTC2947_PAGE1 , 2 , & __val ) ;
break ;
case hwmon_temp_min :
if ( channel = = LTC2947_TEMP_FAN_CHAN )
ret = ltc2947_val_read ( st , LTC2947_REG_TEMP_FAN_THRE_L ,
LTC2947_PAGE1 , 2 , & __val ) ;
else
ret = ltc2947_val_read ( st , LTC2947_REG_TEMP_THRE_L ,
LTC2947_PAGE1 , 2 , & __val ) ;
break ;
default :
return - ENOTSUPP ;
}
if ( ret )
return ret ;
/* in milidegrees celcius, temp is given by: */
2022-11-10 22:21:08 +03:00
* val = ( __val * 204 ) + 5500 ;
2019-10-21 18:41:14 +03:00
return 0 ;
}
static int ltc2947_read_power ( struct device * dev , const u32 attr , long * val )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
int ret ;
u32 lsb = 200000 ; /* in uW */
s64 __val = 0 ;
switch ( attr ) {
case hwmon_power_input :
ret = ltc2947_val_read ( st , LTC2947_REG_POWER , LTC2947_PAGE0 ,
3 , & __val ) ;
lsb = 50000 ;
break ;
case hwmon_power_input_highest :
ret = ltc2947_val_read ( st , LTC2947_REG_POWER_MAX , LTC2947_PAGE0 ,
2 , & __val ) ;
break ;
case hwmon_power_input_lowest :
ret = ltc2947_val_read ( st , LTC2947_REG_POWER_MIN , LTC2947_PAGE0 ,
2 , & __val ) ;
break ;
case hwmon_power_max_alarm :
return ltc2947_alarm_read ( st , LTC2947_REG_STATIP ,
LTC2947_MAX_POWER_MASK , val ) ;
case hwmon_power_min_alarm :
return ltc2947_alarm_read ( st , LTC2947_REG_STATIP ,
LTC2947_MIN_POWER_MASK , val ) ;
case hwmon_power_max :
ret = ltc2947_val_read ( st , LTC2947_REG_POWER_THRE_H ,
LTC2947_PAGE1 , 2 , & __val ) ;
break ;
case hwmon_power_min :
ret = ltc2947_val_read ( st , LTC2947_REG_POWER_THRE_L ,
LTC2947_PAGE1 , 2 , & __val ) ;
break ;
default :
return - ENOTSUPP ;
}
if ( ret )
return ret ;
* val = __val * lsb ;
return 0 ;
}
static int ltc2947_read_curr ( struct device * dev , const u32 attr , long * val )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
int ret ;
u8 lsb = 12 ; /* in mA */
s64 __val = 0 ;
switch ( attr ) {
case hwmon_curr_input :
ret = ltc2947_val_read ( st , LTC2947_REG_CURRENT ,
LTC2947_PAGE0 , 3 , & __val ) ;
lsb = 3 ;
break ;
case hwmon_curr_highest :
ret = ltc2947_val_read ( st , LTC2947_REG_CURRENT_MAX ,
LTC2947_PAGE0 , 2 , & __val ) ;
break ;
case hwmon_curr_lowest :
ret = ltc2947_val_read ( st , LTC2947_REG_CURRENT_MIN ,
LTC2947_PAGE0 , 2 , & __val ) ;
break ;
case hwmon_curr_max_alarm :
return ltc2947_alarm_read ( st , LTC2947_REG_STATIP ,
LTC2947_MAX_CURRENT_MASK , val ) ;
case hwmon_curr_min_alarm :
return ltc2947_alarm_read ( st , LTC2947_REG_STATIP ,
LTC2947_MIN_CURRENT_MASK , val ) ;
case hwmon_curr_max :
ret = ltc2947_val_read ( st , LTC2947_REG_CURRENT_THRE_H ,
LTC2947_PAGE1 , 2 , & __val ) ;
break ;
case hwmon_curr_min :
ret = ltc2947_val_read ( st , LTC2947_REG_CURRENT_THRE_L ,
LTC2947_PAGE1 , 2 , & __val ) ;
break ;
default :
return - ENOTSUPP ;
}
if ( ret )
return ret ;
* val = __val * lsb ;
return 0 ;
}
static int ltc2947_read_in ( struct device * dev , const u32 attr , long * val ,
const int channel )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
int ret ;
u8 lsb = 2 ; /* in mV */
s64 __val = 0 ;
if ( channel < 0 | | channel > LTC2947_VOLTAGE_DVCC_CHAN ) {
dev_err ( st - > dev , " Invalid chan%d for voltage " , channel ) ;
return - EINVAL ;
}
switch ( attr ) {
case hwmon_in_input :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN ) {
ret = ltc2947_val_read ( st , LTC2947_REG_DVCC ,
LTC2947_PAGE0 , 2 , & __val ) ;
lsb = 145 ;
} else {
ret = ltc2947_val_read ( st , LTC2947_REG_VOLTAGE ,
LTC2947_PAGE0 , 2 , & __val ) ;
}
break ;
case hwmon_in_highest :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN ) {
ret = ltc2947_val_read ( st , LTC2947_REG_DVCC_MAX ,
LTC2947_PAGE0 , 2 , & __val ) ;
lsb = 145 ;
} else {
ret = ltc2947_val_read ( st , LTC2947_REG_VOLTAGE_MAX ,
LTC2947_PAGE0 , 2 , & __val ) ;
}
break ;
case hwmon_in_lowest :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN ) {
ret = ltc2947_val_read ( st , LTC2947_REG_DVCC_MIN ,
LTC2947_PAGE0 , 2 , & __val ) ;
lsb = 145 ;
} else {
ret = ltc2947_val_read ( st , LTC2947_REG_VOLTAGE_MIN ,
LTC2947_PAGE0 , 2 , & __val ) ;
}
break ;
case hwmon_in_max_alarm :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN )
return ltc2947_alarm_read ( st , LTC2947_REG_STATVDVCC ,
LTC2947_MAX_VOLTAGE_MASK ,
val ) ;
return ltc2947_alarm_read ( st , LTC2947_REG_STATVT ,
LTC2947_MAX_VOLTAGE_MASK , val ) ;
case hwmon_in_min_alarm :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN )
return ltc2947_alarm_read ( st , LTC2947_REG_STATVDVCC ,
LTC2947_MIN_VOLTAGE_MASK ,
val ) ;
return ltc2947_alarm_read ( st , LTC2947_REG_STATVT ,
LTC2947_MIN_VOLTAGE_MASK , val ) ;
case hwmon_in_max :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN ) {
ret = ltc2947_val_read ( st , LTC2947_REG_DVCC_THRE_H ,
LTC2947_PAGE1 , 2 , & __val ) ;
lsb = 145 ;
} else {
ret = ltc2947_val_read ( st , LTC2947_REG_VOLTAGE_THRE_H ,
LTC2947_PAGE1 , 2 , & __val ) ;
}
break ;
case hwmon_in_min :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN ) {
ret = ltc2947_val_read ( st , LTC2947_REG_DVCC_THRE_L ,
LTC2947_PAGE1 , 2 , & __val ) ;
lsb = 145 ;
} else {
ret = ltc2947_val_read ( st , LTC2947_REG_VOLTAGE_THRE_L ,
LTC2947_PAGE1 , 2 , & __val ) ;
}
break ;
default :
return - ENOTSUPP ;
}
if ( ret )
return ret ;
* val = __val * lsb ;
return 0 ;
}
static int ltc2947_read ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long * val )
{
switch ( type ) {
case hwmon_in :
return ltc2947_read_in ( dev , attr , val , channel ) ;
case hwmon_curr :
return ltc2947_read_curr ( dev , attr , val ) ;
case hwmon_power :
return ltc2947_read_power ( dev , attr , val ) ;
case hwmon_temp :
return ltc2947_read_temp ( dev , attr , val , channel ) ;
default :
return - ENOTSUPP ;
}
}
static int ltc2947_write_temp ( struct device * dev , const u32 attr ,
long val , const int channel )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
if ( channel < 0 | | channel > LTC2947_TEMP_FAN_CHAN ) {
dev_err ( st - > dev , " Invalid chan%d for temperature " , channel ) ;
return - EINVAL ;
}
switch ( attr ) {
case hwmon_temp_reset_history :
if ( val ! = 1 )
return - EINVAL ;
return ltc2947_reset_history ( st , LTC2947_REG_TEMP_MAX ,
LTC2947_REG_TEMP_MIN ) ;
case hwmon_temp_max :
val = clamp_val ( val , TEMP_MIN , TEMP_MAX ) ;
if ( channel = = LTC2947_TEMP_FAN_CHAN ) {
if ( ! st - > gpio_out )
return - ENOTSUPP ;
return ltc2947_val_write ( st ,
LTC2947_REG_TEMP_FAN_THRE_H ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val - 550 , 204 ) ) ;
}
return ltc2947_val_write ( st , LTC2947_REG_TEMP_THRE_H ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val - 550 , 204 ) ) ;
case hwmon_temp_min :
val = clamp_val ( val , TEMP_MIN , TEMP_MAX ) ;
if ( channel = = LTC2947_TEMP_FAN_CHAN ) {
if ( ! st - > gpio_out )
return - ENOTSUPP ;
return ltc2947_val_write ( st ,
LTC2947_REG_TEMP_FAN_THRE_L ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val - 550 , 204 ) ) ;
}
return ltc2947_val_write ( st , LTC2947_REG_TEMP_THRE_L ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val - 550 , 204 ) ) ;
default :
return - ENOTSUPP ;
}
}
static int ltc2947_write_power ( struct device * dev , const u32 attr ,
long val )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
switch ( attr ) {
case hwmon_power_reset_history :
if ( val ! = 1 )
return - EINVAL ;
return ltc2947_reset_history ( st , LTC2947_REG_POWER_MAX ,
LTC2947_REG_POWER_MIN ) ;
case hwmon_power_max :
val = clamp_val ( val , POWER_MIN , POWER_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_POWER_THRE_H ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 200000 ) ) ;
case hwmon_power_min :
val = clamp_val ( val , POWER_MIN , POWER_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_POWER_THRE_L ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 200000 ) ) ;
default :
return - ENOTSUPP ;
}
}
static int ltc2947_write_curr ( struct device * dev , const u32 attr ,
long val )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
switch ( attr ) {
case hwmon_curr_reset_history :
if ( val ! = 1 )
return - EINVAL ;
return ltc2947_reset_history ( st , LTC2947_REG_CURRENT_MAX ,
LTC2947_REG_CURRENT_MIN ) ;
case hwmon_curr_max :
val = clamp_val ( val , CURRENT_MIN , CURRENT_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_CURRENT_THRE_H ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 12 ) ) ;
case hwmon_curr_min :
val = clamp_val ( val , CURRENT_MIN , CURRENT_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_CURRENT_THRE_L ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 12 ) ) ;
default :
return - ENOTSUPP ;
}
}
static int ltc2947_write_in ( struct device * dev , const u32 attr , long val ,
const int channel )
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
if ( channel > LTC2947_VOLTAGE_DVCC_CHAN ) {
dev_err ( st - > dev , " Invalid chan%d for voltage " , channel ) ;
return - EINVAL ;
}
switch ( attr ) {
case hwmon_in_reset_history :
if ( val ! = 1 )
return - EINVAL ;
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN )
return ltc2947_reset_history ( st , LTC2947_REG_DVCC_MAX ,
LTC2947_REG_DVCC_MIN ) ;
return ltc2947_reset_history ( st , LTC2947_REG_VOLTAGE_MAX ,
LTC2947_REG_VOLTAGE_MIN ) ;
case hwmon_in_max :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN ) {
val = clamp_val ( val , VDVCC_MIN , VDVCC_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_DVCC_THRE_H ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 145 ) ) ;
}
val = clamp_val ( val , VOLTAGE_MIN , VOLTAGE_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_VOLTAGE_THRE_H ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 2 ) ) ;
case hwmon_in_min :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN ) {
val = clamp_val ( val , VDVCC_MIN , VDVCC_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_DVCC_THRE_L ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 145 ) ) ;
}
val = clamp_val ( val , VOLTAGE_MIN , VOLTAGE_MAX ) ;
return ltc2947_val_write ( st , LTC2947_REG_VOLTAGE_THRE_L ,
LTC2947_PAGE1 , 2 ,
DIV_ROUND_CLOSEST ( val , 2 ) ) ;
default :
return - ENOTSUPP ;
}
}
static int ltc2947_write ( struct device * dev ,
enum hwmon_sensor_types type ,
u32 attr , int channel , long val )
{
switch ( type ) {
case hwmon_in :
return ltc2947_write_in ( dev , attr , val , channel ) ;
case hwmon_curr :
return ltc2947_write_curr ( dev , attr , val ) ;
case hwmon_power :
return ltc2947_write_power ( dev , attr , val ) ;
case hwmon_temp :
return ltc2947_write_temp ( dev , attr , val , channel ) ;
default :
return - ENOTSUPP ;
}
}
static int ltc2947_read_labels ( struct device * dev ,
enum hwmon_sensor_types type ,
u32 attr , int channel , const char * * str )
{
switch ( type ) {
case hwmon_in :
if ( channel = = LTC2947_VOLTAGE_DVCC_CHAN )
* str = " DVCC " ;
else
* str = " VP-VM " ;
return 0 ;
case hwmon_curr :
* str = " IP-IM " ;
return 0 ;
case hwmon_temp :
if ( channel = = LTC2947_TEMP_FAN_CHAN )
* str = " TEMPFAN " ;
else
* str = " Ambient " ;
return 0 ;
case hwmon_power :
* str = " Power " ;
return 0 ;
default :
return - ENOTSUPP ;
}
}
static int ltc2947_in_is_visible ( const u32 attr )
{
switch ( attr ) {
case hwmon_in_input :
case hwmon_in_highest :
case hwmon_in_lowest :
case hwmon_in_max_alarm :
case hwmon_in_min_alarm :
case hwmon_in_label :
return 0444 ;
case hwmon_in_reset_history :
return 0200 ;
case hwmon_in_max :
case hwmon_in_min :
return 0644 ;
default :
return 0 ;
}
}
static int ltc2947_curr_is_visible ( const u32 attr )
{
switch ( attr ) {
case hwmon_curr_input :
case hwmon_curr_highest :
case hwmon_curr_lowest :
case hwmon_curr_max_alarm :
case hwmon_curr_min_alarm :
case hwmon_curr_label :
return 0444 ;
case hwmon_curr_reset_history :
return 0200 ;
case hwmon_curr_max :
case hwmon_curr_min :
return 0644 ;
default :
return 0 ;
}
}
static int ltc2947_power_is_visible ( const u32 attr )
{
switch ( attr ) {
case hwmon_power_input :
case hwmon_power_input_highest :
case hwmon_power_input_lowest :
case hwmon_power_label :
case hwmon_power_max_alarm :
case hwmon_power_min_alarm :
return 0444 ;
case hwmon_power_reset_history :
return 0200 ;
case hwmon_power_max :
case hwmon_power_min :
return 0644 ;
default :
return 0 ;
}
}
static int ltc2947_temp_is_visible ( const u32 attr )
{
switch ( attr ) {
case hwmon_temp_input :
case hwmon_temp_highest :
case hwmon_temp_lowest :
case hwmon_temp_max_alarm :
case hwmon_temp_min_alarm :
case hwmon_temp_label :
return 0444 ;
case hwmon_temp_reset_history :
return 0200 ;
case hwmon_temp_max :
case hwmon_temp_min :
return 0644 ;
default :
return 0 ;
}
}
static umode_t ltc2947_is_visible ( const void * data ,
enum hwmon_sensor_types type ,
u32 attr , int channel )
{
switch ( type ) {
case hwmon_in :
return ltc2947_in_is_visible ( attr ) ;
case hwmon_curr :
return ltc2947_curr_is_visible ( attr ) ;
case hwmon_power :
return ltc2947_power_is_visible ( attr ) ;
case hwmon_temp :
return ltc2947_temp_is_visible ( attr ) ;
default :
return 0 ;
}
}
2023-04-06 23:30:26 +03:00
static const struct hwmon_channel_info * const ltc2947_info [ ] = {
2019-10-21 18:41:14 +03:00
HWMON_CHANNEL_INFO ( in ,
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM |
HWMON_I_LABEL ,
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
HWMON_I_MAX | HWMON_I_MIN | HWMON_I_RESET_HISTORY |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM |
HWMON_I_LABEL ) ,
HWMON_CHANNEL_INFO ( curr ,
HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
HWMON_C_MAX | HWMON_C_MIN | HWMON_C_RESET_HISTORY |
HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM |
HWMON_C_LABEL ) ,
HWMON_CHANNEL_INFO ( power ,
HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
HWMON_P_RESET_HISTORY | HWMON_P_MAX_ALARM |
HWMON_P_MIN_ALARM | HWMON_P_LABEL ) ,
HWMON_CHANNEL_INFO ( temp ,
HWMON_T_INPUT | HWMON_T_LOWEST | HWMON_T_HIGHEST |
HWMON_T_MAX | HWMON_T_MIN | HWMON_T_RESET_HISTORY |
HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
HWMON_T_LABEL ,
HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM | HWMON_T_MAX |
HWMON_T_MIN | HWMON_T_LABEL ) ,
NULL
} ;
static const struct hwmon_ops ltc2947_hwmon_ops = {
. is_visible = ltc2947_is_visible ,
. read = ltc2947_read ,
. write = ltc2947_write ,
. read_string = ltc2947_read_labels ,
} ;
static const struct hwmon_chip_info ltc2947_chip_info = {
. ops = & ltc2947_hwmon_ops ,
. info = ltc2947_info ,
} ;
/* energy attributes are 6bytes wide so we need u64 */
static SENSOR_DEVICE_ATTR ( energy1_input , 0444 , ltc2947_show_value , NULL ,
LTC2947_REG_ENERGY1 ) ;
static SENSOR_DEVICE_ATTR ( energy2_input , 0444 , ltc2947_show_value , NULL ,
LTC2947_REG_ENERGY2 ) ;
static struct attribute * ltc2947_attrs [ ] = {
& sensor_dev_attr_energy1_input . dev_attr . attr ,
& sensor_dev_attr_energy2_input . dev_attr . attr ,
NULL ,
} ;
ATTRIBUTE_GROUPS ( ltc2947 ) ;
static int ltc2947_setup ( struct ltc2947_data * st )
{
int ret ;
struct clk * extclk ;
u32 dummy , deadband , pol ;
u32 accum [ 2 ] ;
/* clear status register by reading it */
ret = regmap_read ( st - > map , LTC2947_REG_STATUS , & dummy ) ;
if ( ret )
return ret ;
/*
* Set max / min for power here since the default values x scale
* would overflow on 32 bit arch
*/
ret = ltc2947_val_write ( st , LTC2947_REG_POWER_THRE_H , LTC2947_PAGE1 , 2 ,
POWER_MAX / 200000 ) ;
if ( ret )
return ret ;
ret = ltc2947_val_write ( st , LTC2947_REG_POWER_THRE_L , LTC2947_PAGE1 , 2 ,
POWER_MIN / 200000 ) ;
if ( ret )
return ret ;
/* check external clock presence */
2022-08-08 09:06:40 +03:00
extclk = devm_clk_get_optional_enabled ( st - > dev , NULL ) ;
2021-09-23 23:11:13 +03:00
if ( IS_ERR ( extclk ) )
return dev_err_probe ( st - > dev , PTR_ERR ( extclk ) ,
" Failed to get external clock \n " ) ;
if ( extclk ) {
2019-10-21 18:41:14 +03:00
unsigned long rate_hz ;
u8 pre = 0 , div , tbctl ;
u64 aux ;
/* let's calculate and set the right valus in TBCTL */
rate_hz = clk_get_rate ( extclk ) ;
if ( rate_hz < LTC2947_CLK_MIN | | rate_hz > LTC2947_CLK_MAX ) {
dev_err ( st - > dev , " Invalid rate:%lu for external clock " ,
rate_hz ) ;
return - EINVAL ;
}
/* as in table 1 of the datasheet */
if ( rate_hz > = LTC2947_CLK_MIN & & rate_hz < = 1000000 )
pre = 0 ;
else if ( rate_hz > 1000000 & & rate_hz < = 2000000 )
pre = 1 ;
else if ( rate_hz > 2000000 & & rate_hz < = 4000000 )
pre = 2 ;
else if ( rate_hz > 4000000 & & rate_hz < = 8000000 )
pre = 3 ;
else if ( rate_hz > 8000000 & & rate_hz < = 16000000 )
pre = 4 ;
else if ( rate_hz > 16000000 & & rate_hz < = LTC2947_CLK_MAX )
pre = 5 ;
/*
* Div is given by :
* floor ( fref / ( 2 ^ PRE * 32768 ) )
*/
div = rate_hz / ( ( 1 < < pre ) * 32768 ) ;
tbctl = LTC2947_PRE ( pre ) | LTC2947_DIV ( div ) ;
ret = regmap_write ( st - > map , LTC2947_REG_TBCTL , tbctl ) ;
if ( ret )
return ret ;
/*
* The energy lsb is given by ( in W * s ) :
* 06416 * ( 1 / fref ) * 2 ^ PRE * ( DIV + 1 )
* The value is multiplied by 10E9
*/
aux = ( div + 1 ) * ( ( 1 < < pre ) * 641600000ULL ) ;
st - > lsb_energy = DIV_ROUND_CLOSEST_ULL ( aux , rate_hz ) ;
} else {
/* 19.89E-6 * 10E9 */
st - > lsb_energy = 19890 ;
}
ret = of_property_read_u32_array ( st - > dev - > of_node ,
" adi,accumulator-ctl-pol " , accum ,
ARRAY_SIZE ( accum ) ) ;
if ( ! ret ) {
u32 accum_reg = LTC2947_ACCUM_POL_1 ( accum [ 0 ] ) |
LTC2947_ACCUM_POL_2 ( accum [ 1 ] ) ;
ret = regmap_write ( st - > map , LTC2947_REG_ACCUM_POL , accum_reg ) ;
if ( ret )
return ret ;
}
ret = of_property_read_u32 ( st - > dev - > of_node ,
" adi,accumulation-deadband-microamp " ,
& deadband ) ;
if ( ! ret ) {
/* the LSB is the same as the current, so 3mA */
ret = regmap_write ( st - > map , LTC2947_REG_ACCUM_DEADBAND ,
deadband / ( 1000 * 3 ) ) ;
if ( ret )
return ret ;
}
/* check gpio cfg */
ret = of_property_read_u32 ( st - > dev - > of_node , " adi,gpio-out-pol " , & pol ) ;
if ( ! ret ) {
/* setup GPIO as output */
u32 gpio_ctl = LTC2947_GPIO_EN ( 1 ) | LTC2947_GPIO_FAN_EN ( 1 ) |
LTC2947_GPIO_FAN_POL ( pol ) ;
st - > gpio_out = true ;
ret = regmap_write ( st - > map , LTC2947_REG_GPIOSTATCTL , gpio_ctl ) ;
if ( ret )
return ret ;
}
ret = of_property_read_u32_array ( st - > dev - > of_node , " adi,gpio-in-accum " ,
accum , ARRAY_SIZE ( accum ) ) ;
if ( ! ret ) {
/*
* Setup the accum options . The gpioctl is already defined as
* input by default .
*/
u32 accum_val = LTC2947_ACCUM_POL_1 ( accum [ 0 ] ) |
LTC2947_ACCUM_POL_2 ( accum [ 1 ] ) ;
if ( st - > gpio_out ) {
dev_err ( st - > dev ,
" Cannot have input gpio config if already configured as output " ) ;
return - EINVAL ;
}
ret = regmap_write ( st - > map , LTC2947_REG_GPIO_ACCUM , accum_val ) ;
if ( ret )
return ret ;
}
/* set continuos mode */
return regmap_update_bits ( st - > map , LTC2947_REG_CTRL ,
LTC2947_CONT_MODE_MASK , LTC2947_CONT_MODE ( 1 ) ) ;
}
int ltc2947_core_probe ( struct regmap * map , const char * name )
{
struct ltc2947_data * st ;
struct device * dev = regmap_get_device ( map ) ;
struct device * hwmon ;
int ret ;
st = devm_kzalloc ( dev , sizeof ( * st ) , GFP_KERNEL ) ;
if ( ! st )
return - ENOMEM ;
st - > map = map ;
st - > dev = dev ;
dev_set_drvdata ( dev , st ) ;
mutex_init ( & st - > lock ) ;
ret = ltc2947_setup ( st ) ;
if ( ret )
return ret ;
hwmon = devm_hwmon_device_register_with_info ( dev , name , st ,
& ltc2947_chip_info ,
ltc2947_groups ) ;
return PTR_ERR_OR_ZERO ( hwmon ) ;
}
EXPORT_SYMBOL_GPL ( ltc2947_core_probe ) ;
2022-09-25 20:27:49 +03:00
static int ltc2947_resume ( struct device * dev )
2019-10-21 18:41:14 +03:00
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
u32 ctrl = 0 ;
int ret ;
/* dummy read to wake the device */
ret = regmap_read ( st - > map , LTC2947_REG_CTRL , & ctrl ) ;
if ( ret )
return ret ;
/*
* Wait for the device . It takes 100 ms to wake up so , 10 ms extra
* should be enough .
*/
msleep ( 110 ) ;
ret = regmap_read ( st - > map , LTC2947_REG_CTRL , & ctrl ) ;
if ( ret )
return ret ;
/* ctrl should be 0 */
if ( ctrl ! = 0 ) {
dev_err ( st - > dev , " Device failed to wake up, ctl:%02X \n " , ctrl ) ;
return - ETIMEDOUT ;
}
/* set continuous mode */
return regmap_update_bits ( st - > map , LTC2947_REG_CTRL ,
LTC2947_CONT_MODE_MASK , LTC2947_CONT_MODE ( 1 ) ) ;
}
2022-09-25 20:27:49 +03:00
static int ltc2947_suspend ( struct device * dev )
2019-10-21 18:41:14 +03:00
{
struct ltc2947_data * st = dev_get_drvdata ( dev ) ;
return regmap_update_bits ( st - > map , LTC2947_REG_CTRL ,
LTC2947_SHUTDOWN_MASK , 1 ) ;
}
2022-09-25 20:27:49 +03:00
EXPORT_SIMPLE_DEV_PM_OPS ( ltc2947_pm_ops , ltc2947_suspend , ltc2947_resume ) ;
2019-10-21 18:41:14 +03:00
const struct of_device_id ltc2947_of_match [ ] = {
{ . compatible = " adi,ltc2947 " } ,
{ }
} ;
EXPORT_SYMBOL_GPL ( ltc2947_of_match ) ;
MODULE_DEVICE_TABLE ( of , ltc2947_of_match ) ;
MODULE_AUTHOR ( " Nuno Sa <nuno.sa@analog.com> " ) ;
MODULE_DESCRIPTION ( " LTC2947 power and energy monitor core driver " ) ;
MODULE_LICENSE ( " GPL " ) ;