2019-05-27 09:55:06 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-10-11 00:26:31 +03:00
/*
* tc654 . c - Linux kernel modules for fan speed controller
*
* Copyright ( C ) 2016 Allied Telesis Labs NZ
*/
# include <linux/bitops.h>
# include <linux/err.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/i2c.h>
# include <linux/init.h>
# include <linux/jiffies.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/slab.h>
2022-02-13 03:47:33 +03:00
# include <linux/thermal.h>
2016-10-11 00:26:31 +03:00
# include <linux/util_macros.h>
enum tc654_regs {
TC654_REG_RPM1 = 0x00 , /* RPM Output 1 */
TC654_REG_RPM2 = 0x01 , /* RPM Output 2 */
TC654_REG_FAN_FAULT1 = 0x02 , /* Fan Fault 1 Threshold */
TC654_REG_FAN_FAULT2 = 0x03 , /* Fan Fault 2 Threshold */
TC654_REG_CONFIG = 0x04 , /* Configuration */
TC654_REG_STATUS = 0x05 , /* Status */
TC654_REG_DUTY_CYCLE = 0x06 , /* Fan Speed Duty Cycle */
TC654_REG_MFR_ID = 0x07 , /* Manufacturer Identification */
TC654_REG_VER_ID = 0x08 , /* Version Identification */
} ;
/* Macros to easily index the registers */
# define TC654_REG_RPM(idx) (TC654_REG_RPM1 + (idx))
# define TC654_REG_FAN_FAULT(idx) (TC654_REG_FAN_FAULT1 + (idx))
/* Config register bits */
# define TC654_REG_CONFIG_RES BIT(6) /* Resolution Selection */
# define TC654_REG_CONFIG_DUTYC BIT(5) /* Duty Cycle Control */
# define TC654_REG_CONFIG_SDM BIT(0) /* Shutdown Mode */
/* Status register bits */
# define TC654_REG_STATUS_F2F BIT(1) /* Fan 2 Fault */
# define TC654_REG_STATUS_F1F BIT(0) /* Fan 1 Fault */
/* RPM resolution for RPM Output registers */
# define TC654_HIGH_RPM_RESOLUTION 25 /* 25 RPM resolution */
# define TC654_LOW_RPM_RESOLUTION 50 /* 50 RPM resolution */
/* Convert to the fan fault RPM threshold from register value */
# define TC654_FAN_FAULT_FROM_REG(val) ((val) * 50) /* 50 RPM resolution */
/* Convert to register value from the fan fault RPM threshold */
# define TC654_FAN_FAULT_TO_REG(val) (((val) / 50) & 0xff)
/* Register data is read (and cached) at most once per second. */
# define TC654_UPDATE_INTERVAL HZ
struct tc654_data {
struct i2c_client * client ;
/* update mutex */
struct mutex update_lock ;
/* tc654 register cache */
bool valid ;
unsigned long last_updated ; /* in jiffies */
u8 rpm_output [ 2 ] ; /* The fan RPM data for fans 1 and 2 is then
* written to registers RPM1 and RPM2
*/
u8 fan_fault [ 2 ] ; /* The Fan Fault Threshold Registers are used to
* set the fan fault threshold levels for fan 1
* and fan 2
*/
u8 config ; /* The Configuration Register is an 8-bit read/
* writable multi - function control register
* 7 : Fan Fault Clear
* 1 = Clear Fan Fault
* 0 = Normal Operation ( default )
* 6 : Resolution Selection for RPM Output Registers
* RPM Output Registers ( RPM1 and RPM2 ) will be
* set for
* 1 = 25 RPM ( 9 - bit ) resolution
* 0 = 50 RPM ( 8 - bit ) resolution ( default )
* 5 : Duty Cycle Control Method
* The V OUT duty cycle will be controlled via
* 1 = the SMBus interface .
* 0 = via the V IN analog input pin . ( default )
* 4 , 3 : Fan 2 Pulses Per Rotation
* 00 = 1
* 01 = 2 ( default )
* 10 = 4
* 11 = 8
* 2 , 1 : Fan 1 Pulses Per Rotation
* 00 = 1
* 01 = 2 ( default )
* 10 = 4
* 11 = 8
* 0 : Shutdown Mode
* 1 = Shutdown mode .
* 0 = Normal operation . ( default )
*/
u8 status ; /* The Status register provides all the information
* about what is going on within the TC654 / TC655
* devices .
* 7 , 6 : Unimplemented , Read as ' 0 '
* 5 : Over - Temperature Fault Condition
* 1 = Over - Temperature condition has occurred
* 0 = Normal operation . V IN is less than 2.6 V
* 4 : RPM2 Counter Overflow
* 1 = Fault condition
* 0 = Normal operation
* 3 : RPM1 Counter Overflow
* 1 = Fault condition
* 0 = Normal operation
* 2 : V IN Input Status
* 1 = V IN is open
* 0 = Normal operation . voltage present at V IN
* 1 : Fan 2 Fault
* 1 = Fault condition
* 0 = Normal operation
* 0 : Fan 1 Fault
* 1 = Fault condition
* 0 = Normal operation
*/
u8 duty_cycle ; /* The DUTY_CYCLE register is a 4-bit read/
* writable register used to control the duty
* cycle of the V OUT output .
*/
} ;
/* helper to grab and cache data, at most one time per second */
static struct tc654_data * tc654_update_client ( struct device * dev )
{
struct tc654_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
int ret = 0 ;
mutex_lock ( & data - > update_lock ) ;
if ( time_before ( jiffies , data - > last_updated + TC654_UPDATE_INTERVAL ) & &
likely ( data - > valid ) )
goto out ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_RPM ( 0 ) ) ;
if ( ret < 0 )
goto out ;
data - > rpm_output [ 0 ] = ret ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_RPM ( 1 ) ) ;
if ( ret < 0 )
goto out ;
data - > rpm_output [ 1 ] = ret ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_FAN_FAULT ( 0 ) ) ;
if ( ret < 0 )
goto out ;
data - > fan_fault [ 0 ] = ret ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_FAN_FAULT ( 1 ) ) ;
if ( ret < 0 )
goto out ;
data - > fan_fault [ 1 ] = ret ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_CONFIG ) ;
if ( ret < 0 )
goto out ;
data - > config = ret ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_STATUS ) ;
if ( ret < 0 )
goto out ;
data - > status = ret ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_DUTY_CYCLE ) ;
if ( ret < 0 )
goto out ;
data - > duty_cycle = ret & 0x0f ;
data - > last_updated = jiffies ;
data - > valid = true ;
out :
mutex_unlock ( & data - > update_lock ) ;
if ( ret < 0 ) /* upon error, encode it in return value */
data = ERR_PTR ( ret ) ;
return data ;
}
/*
* sysfs attributes
*/
2018-12-11 01:02:21 +03:00
static ssize_t fan_show ( struct device * dev , struct device_attribute * da ,
2016-10-11 00:26:31 +03:00
char * buf )
{
int nr = to_sensor_dev_attr ( da ) - > index ;
struct tc654_data * data = tc654_update_client ( dev ) ;
int val ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
if ( data - > config & TC654_REG_CONFIG_RES )
val = data - > rpm_output [ nr ] * TC654_HIGH_RPM_RESOLUTION ;
else
val = data - > rpm_output [ nr ] * TC654_LOW_RPM_RESOLUTION ;
return sprintf ( buf , " %d \n " , val ) ;
}
2018-12-11 01:02:21 +03:00
static ssize_t fan_min_show ( struct device * dev , struct device_attribute * da ,
2016-10-11 00:26:31 +03:00
char * buf )
{
int nr = to_sensor_dev_attr ( da ) - > index ;
struct tc654_data * data = tc654_update_client ( dev ) ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
return sprintf ( buf , " %d \n " ,
TC654_FAN_FAULT_FROM_REG ( data - > fan_fault [ nr ] ) ) ;
}
2018-12-11 01:02:21 +03:00
static ssize_t fan_min_store ( struct device * dev , struct device_attribute * da ,
const char * buf , size_t count )
2016-10-11 00:26:31 +03:00
{
int nr = to_sensor_dev_attr ( da ) - > index ;
struct tc654_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
unsigned long val ;
int ret ;
if ( kstrtoul ( buf , 10 , & val ) )
return - EINVAL ;
val = clamp_val ( val , 0 , 12750 ) ;
mutex_lock ( & data - > update_lock ) ;
data - > fan_fault [ nr ] = TC654_FAN_FAULT_TO_REG ( val ) ;
ret = i2c_smbus_write_byte_data ( client , TC654_REG_FAN_FAULT ( nr ) ,
data - > fan_fault [ nr ] ) ;
mutex_unlock ( & data - > update_lock ) ;
return ret < 0 ? ret : count ;
}
2018-12-11 01:02:21 +03:00
static ssize_t fan_alarm_show ( struct device * dev , struct device_attribute * da ,
2016-10-11 00:26:31 +03:00
char * buf )
{
int nr = to_sensor_dev_attr ( da ) - > index ;
struct tc654_data * data = tc654_update_client ( dev ) ;
int val ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
if ( nr = = 0 )
val = ! ! ( data - > status & TC654_REG_STATUS_F1F ) ;
else
val = ! ! ( data - > status & TC654_REG_STATUS_F2F ) ;
return sprintf ( buf , " %d \n " , val ) ;
}
static const u8 TC654_FAN_PULSE_SHIFT [ ] = { 1 , 3 } ;
2018-12-11 01:02:21 +03:00
static ssize_t fan_pulses_show ( struct device * dev ,
struct device_attribute * da , char * buf )
2016-10-11 00:26:31 +03:00
{
int nr = to_sensor_dev_attr ( da ) - > index ;
struct tc654_data * data = tc654_update_client ( dev ) ;
u8 val ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
val = BIT ( ( data - > config > > TC654_FAN_PULSE_SHIFT [ nr ] ) & 0x03 ) ;
return sprintf ( buf , " %d \n " , val ) ;
}
2018-12-11 01:02:21 +03:00
static ssize_t fan_pulses_store ( struct device * dev ,
struct device_attribute * da , const char * buf ,
size_t count )
2016-10-11 00:26:31 +03:00
{
int nr = to_sensor_dev_attr ( da ) - > index ;
struct tc654_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
u8 config ;
unsigned long val ;
int ret ;
if ( kstrtoul ( buf , 10 , & val ) )
return - EINVAL ;
switch ( val ) {
case 1 :
config = 0 ;
break ;
case 2 :
config = 1 ;
break ;
case 4 :
config = 2 ;
break ;
case 8 :
config = 3 ;
break ;
default :
return - EINVAL ;
}
mutex_lock ( & data - > update_lock ) ;
data - > config & = ~ ( 0x03 < < TC654_FAN_PULSE_SHIFT [ nr ] ) ;
data - > config | = ( config < < TC654_FAN_PULSE_SHIFT [ nr ] ) ;
ret = i2c_smbus_write_byte_data ( client , TC654_REG_CONFIG , data - > config ) ;
mutex_unlock ( & data - > update_lock ) ;
return ret < 0 ? ret : count ;
}
2018-12-11 01:02:21 +03:00
static ssize_t pwm_mode_show ( struct device * dev , struct device_attribute * da ,
char * buf )
2016-10-11 00:26:31 +03:00
{
struct tc654_data * data = tc654_update_client ( dev ) ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
return sprintf ( buf , " %d \n " , ! ! ( data - > config & TC654_REG_CONFIG_DUTYC ) ) ;
}
2018-12-11 01:02:21 +03:00
static ssize_t pwm_mode_store ( struct device * dev , struct device_attribute * da ,
const char * buf , size_t count )
2016-10-11 00:26:31 +03:00
{
struct tc654_data * data = dev_get_drvdata ( dev ) ;
struct i2c_client * client = data - > client ;
unsigned long val ;
int ret ;
if ( kstrtoul ( buf , 10 , & val ) )
return - EINVAL ;
if ( val ! = 0 & & val ! = 1 )
return - EINVAL ;
mutex_lock ( & data - > update_lock ) ;
if ( val )
data - > config | = TC654_REG_CONFIG_DUTYC ;
else
data - > config & = ~ TC654_REG_CONFIG_DUTYC ;
ret = i2c_smbus_write_byte_data ( client , TC654_REG_CONFIG , data - > config ) ;
mutex_unlock ( & data - > update_lock ) ;
return ret < 0 ? ret : count ;
}
static const int tc654_pwm_map [ 16 ] = { 77 , 88 , 102 , 112 , 124 , 136 , 148 , 160 ,
172 , 184 , 196 , 207 , 219 , 231 , 243 , 255 } ;
2018-12-11 01:02:21 +03:00
static ssize_t pwm_show ( struct device * dev , struct device_attribute * da ,
2016-10-11 00:26:31 +03:00
char * buf )
{
struct tc654_data * data = tc654_update_client ( dev ) ;
int pwm ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
if ( data - > config & TC654_REG_CONFIG_SDM )
pwm = 0 ;
else
pwm = tc654_pwm_map [ data - > duty_cycle ] ;
return sprintf ( buf , " %d \n " , pwm ) ;
}
2022-02-13 03:47:33 +03:00
static int _set_pwm ( struct tc654_data * data , unsigned long val )
2016-10-11 00:26:31 +03:00
{
struct i2c_client * client = data - > client ;
int ret ;
mutex_lock ( & data - > update_lock ) ;
2022-02-13 03:47:33 +03:00
if ( val = = 0 ) {
2016-10-11 00:26:31 +03:00
data - > config | = TC654_REG_CONFIG_SDM ;
2022-02-13 03:47:33 +03:00
data - > duty_cycle = 0 ;
} else {
2016-10-11 00:26:31 +03:00
data - > config & = ~ TC654_REG_CONFIG_SDM ;
2022-02-13 03:47:33 +03:00
data - > duty_cycle = val - 1 ;
}
2016-10-11 00:26:31 +03:00
ret = i2c_smbus_write_byte_data ( client , TC654_REG_CONFIG , data - > config ) ;
if ( ret < 0 )
goto out ;
ret = i2c_smbus_write_byte_data ( client , TC654_REG_DUTY_CYCLE ,
data - > duty_cycle ) ;
out :
mutex_unlock ( & data - > update_lock ) ;
2022-02-13 03:47:33 +03:00
return ret ;
}
static ssize_t pwm_store ( struct device * dev , struct device_attribute * da ,
const char * buf , size_t count )
{
struct tc654_data * data = dev_get_drvdata ( dev ) ;
unsigned long val ;
int ret ;
if ( kstrtoul ( buf , 10 , & val ) )
return - EINVAL ;
if ( val > 255 )
return - EINVAL ;
if ( val > 0 )
val = find_closest ( val , tc654_pwm_map , ARRAY_SIZE ( tc654_pwm_map ) ) + 1 ;
ret = _set_pwm ( data , val ) ;
2016-10-11 00:26:31 +03:00
return ret < 0 ? ret : count ;
}
2018-12-11 01:02:21 +03:00
static SENSOR_DEVICE_ATTR_RO ( fan1_input , fan , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan2_input , fan , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan1_min , fan_min , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan2_min , fan_min , 1 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan1_alarm , fan_alarm , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan2_alarm , fan_alarm , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan1_pulses , fan_pulses , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan2_pulses , fan_pulses , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( pwm1_mode , pwm_mode , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( pwm1 , pwm , 0 ) ;
2016-10-11 00:26:31 +03:00
/* Driver data */
static struct attribute * tc654_attrs [ ] = {
& sensor_dev_attr_fan1_input . dev_attr . attr ,
& sensor_dev_attr_fan2_input . dev_attr . attr ,
& sensor_dev_attr_fan1_min . dev_attr . attr ,
& sensor_dev_attr_fan2_min . dev_attr . attr ,
& sensor_dev_attr_fan1_alarm . dev_attr . attr ,
& sensor_dev_attr_fan2_alarm . dev_attr . attr ,
& sensor_dev_attr_fan1_pulses . dev_attr . attr ,
& sensor_dev_attr_fan2_pulses . dev_attr . attr ,
& sensor_dev_attr_pwm1_mode . dev_attr . attr ,
& sensor_dev_attr_pwm1 . dev_attr . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( tc654 ) ;
2022-02-13 03:47:33 +03:00
/*
* thermal cooling device functions
*
* Account for the " ShutDown Mode (SDM) " state by offsetting
* the 16 PWM duty cycle states by 1.
*
* State 0 = 0 % PWM | Shutdown - Fan ( s ) are off
* State 1 = 30 % PWM | duty_cycle = 0
* State 2 = ~ 35 % PWM | duty_cycle = 1
* [ . . . ]
* State 15 = ~ 95 % PWM | duty_cycle = 14
* State 16 = 100 % PWM | duty_cycle = 15
*/
# define TC654_MAX_COOLING_STATE 16
static int tc654_get_max_state ( struct thermal_cooling_device * cdev , unsigned long * state )
{
* state = TC654_MAX_COOLING_STATE ;
return 0 ;
}
static int tc654_get_cur_state ( struct thermal_cooling_device * cdev , unsigned long * state )
{
struct tc654_data * data = tc654_update_client ( cdev - > devdata ) ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
if ( data - > config & TC654_REG_CONFIG_SDM )
* state = 0 ; /* FAN is off */
else
* state = data - > duty_cycle + 1 ; /* offset PWM States by 1 */
return 0 ;
}
static int tc654_set_cur_state ( struct thermal_cooling_device * cdev , unsigned long state )
{
struct tc654_data * data = tc654_update_client ( cdev - > devdata ) ;
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
return _set_pwm ( data , clamp_val ( state , 0 , TC654_MAX_COOLING_STATE ) ) ;
}
static const struct thermal_cooling_device_ops tc654_fan_cool_ops = {
. get_max_state = tc654_get_max_state ,
. get_cur_state = tc654_get_cur_state ,
. set_cur_state = tc654_set_cur_state ,
} ;
2016-10-11 00:26:31 +03:00
/*
* device probe and removal
*/
2020-08-13 19:02:22 +03:00
static int tc654_probe ( struct i2c_client * client )
2016-10-11 00:26:31 +03:00
{
struct device * dev = & client - > dev ;
struct tc654_data * data ;
struct device * hwmon_dev ;
int ret ;
if ( ! i2c_check_functionality ( client - > adapter , I2C_FUNC_SMBUS_BYTE_DATA ) )
return - ENODEV ;
data = devm_kzalloc ( dev , sizeof ( struct tc654_data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > client = client ;
mutex_init ( & data - > update_lock ) ;
ret = i2c_smbus_read_byte_data ( client , TC654_REG_CONFIG ) ;
if ( ret < 0 )
return ret ;
data - > config = ret ;
hwmon_dev =
devm_hwmon_device_register_with_groups ( dev , client - > name , data ,
tc654_groups ) ;
2022-02-13 03:47:33 +03:00
if ( IS_ERR ( hwmon_dev ) )
return PTR_ERR ( hwmon_dev ) ;
if ( IS_ENABLED ( CONFIG_THERMAL ) ) {
struct thermal_cooling_device * cdev ;
cdev = devm_thermal_of_cooling_device_register ( dev , dev - > of_node , client - > name ,
hwmon_dev , & tc654_fan_cool_ops ) ;
return PTR_ERR_OR_ZERO ( cdev ) ;
}
return 0 ;
2016-10-11 00:26:31 +03:00
}
static const struct i2c_device_id tc654_id [ ] = {
{ " tc654 " , 0 } ,
{ " tc655 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , tc654_id ) ;
static struct i2c_driver tc654_driver = {
. driver = {
. name = " tc654 " ,
} ,
2020-08-13 19:02:22 +03:00
. probe_new = tc654_probe ,
2016-10-11 00:26:31 +03:00
. id_table = tc654_id ,
} ;
module_i2c_driver ( tc654_driver ) ;
MODULE_AUTHOR ( " Allied Telesis Labs " ) ;
MODULE_DESCRIPTION ( " Microchip TC654/TC655 driver " ) ;
MODULE_LICENSE ( " GPL " ) ;