2019-05-20 10:19:02 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 02:20:36 +04:00
/*
2012-01-15 10:21:41 +04:00
* sis5595 . c - Part of lm_sensors , Linux kernel modules
* for hardware monitoring
*
* Copyright ( C ) 1998 - 2001 Frodo Looijaard < frodol @ dds . nl > ,
* Kyösti Mälkki < kmalkki @ cc . hut . fi > , and
* Mark D . Studebaker < mdsxyz123 @ yahoo . com >
* Ported to Linux 2.6 by Aurelien Jarno < aurelien @ aurel32 . net > with
2014-01-29 23:40:08 +04:00
* the help of Jean Delvare < jdelvare @ suse . de >
2012-01-15 10:21:41 +04:00
*/
2005-04-17 02:20:36 +04:00
/*
2012-01-15 10:21:41 +04:00
* SiS southbridge has a LM78 - like chip integrated on the same IC .
* This driver is a customized copy of lm78 . c
*
* Supports following revisions :
* Version PCI ID PCI Revision
* 1 1039 / 000 8 AF or less
* 2 1039 / 000 8 B0 or greater
*
* Note : these chips contain a 000 8 device which is incompatible with the
* 5595. We recognize these by the presence of the listed
* " blacklist " PCI ID and refuse to load .
*
* NOT SUPPORTED PCI ID BLACKLIST PCI ID
* 540 000 8 0540
* 550 000 8 0550
* 5513 000 8 5511
* 5581 000 8 5597
* 5582 000 8 5597
* 5597 000 8 5597
* 5598 000 8 5597 / 5598
* 630 000 8 0630
* 645 000 8 0645
* 730 000 8 0730
* 735 000 8 0735
*/
2005-04-17 02:20:36 +04:00
2022-09-22 10:48:59 +03:00
# define DRIVER_NAME "sis5595"
2010-10-20 10:51:47 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/ioport.h>
# include <linux/pci.h>
2007-06-09 18:11:16 +04:00
# include <linux/platform_device.h>
2005-07-16 05:39:18 +04:00
# include <linux/hwmon.h>
2007-06-09 18:11:16 +04:00
# include <linux/hwmon-sysfs.h>
2005-07-16 05:39:18 +04:00
# include <linux/err.h>
2005-04-17 02:20:36 +04:00
# include <linux/init.h>
2005-05-16 20:12:18 +04:00
# include <linux/jiffies.h>
2006-01-19 01:19:26 +03:00
# include <linux/mutex.h>
2006-09-24 23:24:46 +04:00
# include <linux/sysfs.h>
2009-01-07 18:37:35 +03:00
# include <linux/acpi.h>
2009-09-15 19:18:13 +04:00
# include <linux/io.h>
2005-04-17 02:20:36 +04:00
2012-01-15 10:21:41 +04:00
/*
* If force_addr is set to anything different from 0 , we forcibly enable
* the device at the given address .
*/
2005-04-17 02:20:36 +04:00
static u16 force_addr ;
module_param ( force_addr , ushort , 0 ) ;
MODULE_PARM_DESC ( force_addr ,
" Initialize the base address of the sensors " ) ;
2007-06-09 18:11:16 +04:00
static struct platform_device * pdev ;
2005-04-17 02:20:36 +04:00
/* Many SIS5595 constants specified below */
/* Length of ISA address segment */
# define SIS5595_EXTENT 8
/* PCI Config Registers */
# define SIS5595_BASE_REG 0x68
# define SIS5595_PIN_REG 0x7A
# define SIS5595_ENABLE_REG 0x7B
/* Where are the ISA address/data registers relative to the base address */
# define SIS5595_ADDR_REG_OFFSET 5
# define SIS5595_DATA_REG_OFFSET 6
/* The SIS5595 registers */
# define SIS5595_REG_IN_MAX(nr) (0x2b + (nr) * 2)
# define SIS5595_REG_IN_MIN(nr) (0x2c + (nr) * 2)
# define SIS5595_REG_IN(nr) (0x20 + (nr))
# define SIS5595_REG_FAN_MIN(nr) (0x3b + (nr))
# define SIS5595_REG_FAN(nr) (0x28 + (nr))
2012-01-15 10:21:41 +04:00
/*
* On the first version of the chip , the temp registers are separate .
* On the second version ,
* TEMP pin is shared with IN4 , configured in PCI register 0x7A .
* The registers are the same as well .
* OVER and HYST are really MAX and MIN .
*/
2005-04-17 02:20:36 +04:00
# define REV2MIN 0xb0
2012-01-15 10:21:41 +04:00
# define SIS5595_REG_TEMP (((data->revision) >= REV2MIN) ? \
SIS5595_REG_IN ( 4 ) : 0x27 )
# define SIS5595_REG_TEMP_OVER (((data->revision) >= REV2MIN) ? \
SIS5595_REG_IN_MAX ( 4 ) : 0x39 )
# define SIS5595_REG_TEMP_HYST (((data->revision) >= REV2MIN) ? \
SIS5595_REG_IN_MIN ( 4 ) : 0x3a )
2005-04-17 02:20:36 +04:00
# define SIS5595_REG_CONFIG 0x40
# define SIS5595_REG_ALARM1 0x41
# define SIS5595_REG_ALARM2 0x42
# define SIS5595_REG_FANDIV 0x47
2012-01-15 10:21:41 +04:00
/*
* Conversions . Limit checking is only done on the TO_REG
* variants .
*/
2005-04-17 02:20:36 +04:00
2012-01-15 10:21:41 +04:00
/*
* IN : mV , ( 0 V to 4.08 V )
* REG : 16 mV / bit
*/
2005-04-17 02:20:36 +04:00
static inline u8 IN_TO_REG ( unsigned long val )
{
2013-01-09 20:09:34 +04:00
unsigned long nval = clamp_val ( val , 0 , 4080 ) ;
2005-04-17 02:20:36 +04:00
return ( nval + 8 ) / 16 ;
}
# define IN_FROM_REG(val) ((val) * 16)
static inline u8 FAN_TO_REG ( long rpm , int div )
{
if ( rpm < = 0 )
return 255 ;
2013-12-12 11:05:33 +04:00
if ( rpm > 1350000 )
return 1 ;
2013-01-09 20:09:34 +04:00
return clamp_val ( ( 1350000 + rpm * div / 2 ) / ( rpm * div ) , 1 , 254 ) ;
2005-04-17 02:20:36 +04:00
}
static inline int FAN_FROM_REG ( u8 val , int div )
{
2012-01-15 10:21:41 +04:00
return val = = 0 ? - 1 : val = = 255 ? 0 : 1350000 / ( val * div ) ;
2005-04-17 02:20:36 +04:00
}
2012-01-15 10:21:41 +04:00
/*
* TEMP : mC ( - 54.12 C to + 157.53 C )
* REG : 0.83 C / bit + 52.12 , two ' s complement
*/
2005-04-17 02:20:36 +04:00
static inline int TEMP_FROM_REG ( s8 val )
{
return val * 830 + 52120 ;
}
2014-07-31 18:27:04 +04:00
static inline s8 TEMP_TO_REG ( long val )
2005-04-17 02:20:36 +04:00
{
2013-01-09 20:09:34 +04:00
int nval = clamp_val ( val , - 54120 , 157530 ) ;
2012-01-15 10:21:41 +04:00
return nval < 0 ? ( nval - 5212 - 415 ) / 830 : ( nval - 5212 + 415 ) / 830 ;
2005-04-17 02:20:36 +04:00
}
2012-01-15 10:21:41 +04:00
/*
* FAN DIV : 1 , 2 , 4 , or 8 ( defaults to 2 )
* REG : 0 , 1 , 2 , or 3 ( respectively ) ( defaults to 1 )
*/
2005-04-17 02:20:36 +04:00
static inline u8 DIV_TO_REG ( int val )
{
2012-01-15 10:21:41 +04:00
return val = = 8 ? 3 : val = = 4 ? 2 : val = = 1 ? 0 : 1 ;
2005-04-17 02:20:36 +04:00
}
# define DIV_FROM_REG(val) (1 << (val))
2012-01-15 10:21:41 +04:00
/*
* For each registered chip , we need to keep some data in memory .
* The structure is dynamically allocated .
*/
2005-04-17 02:20:36 +04:00
struct sis5595_data {
2007-06-09 18:11:16 +04:00
unsigned short addr ;
const char * name ;
2007-08-21 00:46:20 +04:00
struct device * hwmon_dev ;
2006-01-19 01:19:26 +03:00
struct mutex lock ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
struct mutex update_lock ;
2021-09-24 22:52:02 +03:00
bool valid ; /* true if following fields are valid */
2005-04-17 02:20:36 +04:00
unsigned long last_updated ; /* In jiffies */
char maxins ; /* == 3 if temp enabled, otherwise == 4 */
u8 revision ; /* Reg. value */
u8 in [ 5 ] ; /* Register value */
u8 in_max [ 5 ] ; /* Register value */
u8 in_min [ 5 ] ; /* Register value */
u8 fan [ 2 ] ; /* Register value */
u8 fan_min [ 2 ] ; /* Register value */
s8 temp ; /* Register value */
s8 temp_over ; /* Register value */
s8 temp_hyst ; /* Register value */
u8 fan_div [ 2 ] ; /* Register encoding, shifted right */
u16 alarms ; /* Register encoding, combined */
} ;
static struct pci_dev * s_bridge ; /* pointer to the (only) sis5595 */
2022-09-22 10:49:00 +03:00
/* ISA access must be locked explicitly. */
static int sis5595_read_value ( struct sis5595_data * data , u8 reg )
{
int res ;
2005-04-17 02:20:36 +04:00
2022-09-22 10:49:00 +03:00
mutex_lock ( & data - > lock ) ;
outb_p ( reg , data - > addr + SIS5595_ADDR_REG_OFFSET ) ;
res = inb_p ( data - > addr + SIS5595_DATA_REG_OFFSET ) ;
mutex_unlock ( & data - > lock ) ;
return res ;
}
2005-04-17 02:20:36 +04:00
2022-09-22 10:49:00 +03:00
static void sis5595_write_value ( struct sis5595_data * data , u8 reg , u8 value )
{
mutex_lock ( & data - > lock ) ;
outb_p ( reg , data - > addr + SIS5595_ADDR_REG_OFFSET ) ;
outb_p ( value , data - > addr + SIS5595_DATA_REG_OFFSET ) ;
mutex_unlock ( & data - > lock ) ;
}
static struct sis5595_data * sis5595_update_device ( struct device * dev )
{
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
int i ;
mutex_lock ( & data - > update_lock ) ;
if ( time_after ( jiffies , data - > last_updated + HZ + HZ / 2 )
| | ! data - > valid ) {
for ( i = 0 ; i < = data - > maxins ; i + + ) {
data - > in [ i ] =
sis5595_read_value ( data , SIS5595_REG_IN ( i ) ) ;
data - > in_min [ i ] =
sis5595_read_value ( data ,
SIS5595_REG_IN_MIN ( i ) ) ;
data - > in_max [ i ] =
sis5595_read_value ( data ,
SIS5595_REG_IN_MAX ( i ) ) ;
}
for ( i = 0 ; i < 2 ; i + + ) {
data - > fan [ i ] =
sis5595_read_value ( data , SIS5595_REG_FAN ( i ) ) ;
data - > fan_min [ i ] =
sis5595_read_value ( data ,
SIS5595_REG_FAN_MIN ( i ) ) ;
}
if ( data - > maxins = = 3 ) {
data - > temp =
sis5595_read_value ( data , SIS5595_REG_TEMP ) ;
data - > temp_over =
sis5595_read_value ( data , SIS5595_REG_TEMP_OVER ) ;
data - > temp_hyst =
sis5595_read_value ( data , SIS5595_REG_TEMP_HYST ) ;
}
i = sis5595_read_value ( data , SIS5595_REG_FANDIV ) ;
data - > fan_div [ 0 ] = ( i > > 4 ) & 0x03 ;
data - > fan_div [ 1 ] = i > > 6 ;
data - > alarms =
sis5595_read_value ( data , SIS5595_REG_ALARM1 ) |
( sis5595_read_value ( data , SIS5595_REG_ALARM2 ) < < 8 ) ;
data - > last_updated = jiffies ;
data - > valid = true ;
}
mutex_unlock ( & data - > update_lock ) ;
return data ;
}
2005-04-17 02:20:36 +04:00
/* 4 Voltages */
2019-01-23 01:28:50 +03:00
static ssize_t in_show ( struct device * dev , struct device_attribute * da ,
2007-06-09 18:11:16 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2005-04-17 02:20:36 +04:00
return sprintf ( buf , " %d \n " , IN_FROM_REG ( data - > in [ nr ] ) ) ;
}
2019-01-23 01:28:50 +03:00
static ssize_t in_min_show ( struct device * dev , struct device_attribute * da ,
2007-06-09 18:11:16 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2005-04-17 02:20:36 +04:00
return sprintf ( buf , " %d \n " , IN_FROM_REG ( data - > in_min [ nr ] ) ) ;
}
2019-01-23 01:28:50 +03:00
static ssize_t in_max_show ( struct device * dev , struct device_attribute * da ,
2007-06-09 18:11:16 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2005-04-17 02:20:36 +04:00
return sprintf ( buf , " %d \n " , IN_FROM_REG ( data - > in_max [ nr ] ) ) ;
}
2019-01-23 01:28:50 +03:00
static ssize_t in_min_store ( struct device * dev , struct device_attribute * da ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
2007-06-09 18:11:16 +04:00
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2012-01-15 10:21:41 +04:00
unsigned long val ;
int err ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
data - > in_min [ nr ] = IN_TO_REG ( val ) ;
2007-06-09 18:11:16 +04:00
sis5595_write_value ( data , SIS5595_REG_IN_MIN ( nr ) , data - > in_min [ nr ] ) ;
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
2019-01-23 01:28:50 +03:00
static ssize_t in_max_store ( struct device * dev , struct device_attribute * da ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
2007-06-09 18:11:16 +04:00
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2012-01-15 10:21:41 +04:00
unsigned long val ;
int err ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
data - > in_max [ nr ] = IN_TO_REG ( val ) ;
2007-06-09 18:11:16 +04:00
sis5595_write_value ( data , SIS5595_REG_IN_MAX ( nr ) , data - > in_max [ nr ] ) ;
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
2019-01-23 01:28:50 +03:00
static SENSOR_DEVICE_ATTR_RO ( in0_input , in , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( in0_min , in_min , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( in0_max , in_max , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( in1_input , in , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( in1_min , in_min , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( in1_max , in_max , 1 ) ;
static SENSOR_DEVICE_ATTR_RO ( in2_input , in , 2 ) ;
static SENSOR_DEVICE_ATTR_RW ( in2_min , in_min , 2 ) ;
static SENSOR_DEVICE_ATTR_RW ( in2_max , in_max , 2 ) ;
static SENSOR_DEVICE_ATTR_RO ( in3_input , in , 3 ) ;
static SENSOR_DEVICE_ATTR_RW ( in3_min , in_min , 3 ) ;
static SENSOR_DEVICE_ATTR_RW ( in3_max , in_max , 3 ) ;
static SENSOR_DEVICE_ATTR_RO ( in4_input , in , 4 ) ;
static SENSOR_DEVICE_ATTR_RW ( in4_min , in_min , 4 ) ;
static SENSOR_DEVICE_ATTR_RW ( in4_max , in_max , 4 ) ;
2005-04-17 02:20:36 +04:00
/* Temperature */
2016-12-22 15:05:05 +03:00
static ssize_t temp1_input_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , TEMP_FROM_REG ( data - > temp ) ) ;
}
2016-12-22 15:05:05 +03:00
static ssize_t temp1_max_show ( struct device * dev , struct device_attribute * attr ,
2012-01-15 10:21:41 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , TEMP_FROM_REG ( data - > temp_over ) ) ;
}
2016-12-22 15:05:05 +03:00
static ssize_t temp1_max_store ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t count )
2005-04-17 02:20:36 +04:00
{
2007-06-09 18:11:16 +04:00
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
2012-01-15 10:21:41 +04:00
long val ;
int err ;
err = kstrtol ( buf , 10 , & val ) ;
if ( err )
return err ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
data - > temp_over = TEMP_TO_REG ( val ) ;
2007-06-09 18:11:16 +04:00
sis5595_write_value ( data , SIS5595_REG_TEMP_OVER , data - > temp_over ) ;
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
2016-12-22 15:05:05 +03:00
static ssize_t temp1_max_hyst_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , TEMP_FROM_REG ( data - > temp_hyst ) ) ;
}
2016-12-22 15:05:05 +03:00
static ssize_t temp1_max_hyst_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
2007-06-09 18:11:16 +04:00
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
2012-01-15 10:21:41 +04:00
long val ;
int err ;
err = kstrtol ( buf , 10 , & val ) ;
if ( err )
return err ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
data - > temp_hyst = TEMP_TO_REG ( val ) ;
2007-06-09 18:11:16 +04:00
sis5595_write_value ( data , SIS5595_REG_TEMP_HYST , data - > temp_hyst ) ;
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
2016-12-22 15:05:05 +03:00
static DEVICE_ATTR_RO ( temp1_input ) ;
static DEVICE_ATTR_RW ( temp1_max ) ;
static DEVICE_ATTR_RW ( temp1_max_hyst ) ;
2005-04-17 02:20:36 +04:00
/* 2 Fans */
2019-01-23 01:28:50 +03:00
static ssize_t fan_show ( struct device * dev , struct device_attribute * da ,
2007-06-09 18:11:16 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2005-04-17 02:20:36 +04:00
return sprintf ( buf , " %d \n " , FAN_FROM_REG ( data - > fan [ nr ] ,
2012-01-15 10:21:41 +04:00
DIV_FROM_REG ( data - > fan_div [ nr ] ) ) ) ;
2005-04-17 02:20:36 +04:00
}
2019-01-23 01:28:50 +03:00
static ssize_t fan_min_show ( struct device * dev , struct device_attribute * da ,
2007-06-09 18:11:16 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2012-01-15 10:21:41 +04:00
return sprintf ( buf , " %d \n " , FAN_FROM_REG ( data - > fan_min [ nr ] ,
DIV_FROM_REG ( data - > fan_div [ nr ] ) ) ) ;
2005-04-17 02:20:36 +04:00
}
2019-01-23 01:28:50 +03:00
static ssize_t fan_min_store ( struct device * dev , struct device_attribute * da ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
2007-06-09 18:11:16 +04:00
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2012-01-15 10:21:41 +04:00
unsigned long val ;
int err ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
data - > fan_min [ nr ] = FAN_TO_REG ( val , DIV_FROM_REG ( data - > fan_div [ nr ] ) ) ;
2007-06-09 18:11:16 +04:00
sis5595_write_value ( data , SIS5595_REG_FAN_MIN ( nr ) , data - > fan_min [ nr ] ) ;
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
2019-01-23 01:28:50 +03:00
static ssize_t fan_div_show ( struct device * dev , struct device_attribute * da ,
2007-06-09 18:11:16 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2012-01-15 10:21:41 +04:00
return sprintf ( buf , " %d \n " , DIV_FROM_REG ( data - > fan_div [ nr ] ) ) ;
2005-04-17 02:20:36 +04:00
}
2012-01-15 10:21:41 +04:00
/*
* Note : we save and restore the fan minimum here , because its value is
* determined in part by the fan divisor . This follows the principle of
* least surprise ; the user doesn ' t expect the fan minimum to change just
* because the divisor changed .
*/
2019-01-23 01:28:50 +03:00
static ssize_t fan_div_store ( struct device * dev , struct device_attribute * da ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
2007-06-09 18:11:16 +04:00
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
2007-06-09 18:11:16 +04:00
struct sensor_device_attribute * attr = to_sensor_dev_attr ( da ) ;
int nr = attr - > index ;
2005-04-17 02:20:36 +04:00
unsigned long min ;
int reg ;
2012-01-15 10:21:41 +04:00
unsigned long val ;
int err ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
min = FAN_FROM_REG ( data - > fan_min [ nr ] ,
DIV_FROM_REG ( data - > fan_div [ nr ] ) ) ;
2007-06-09 18:11:16 +04:00
reg = sis5595_read_value ( data , SIS5595_REG_FANDIV ) ;
2005-04-17 02:20:36 +04:00
switch ( val ) {
2012-01-15 10:21:41 +04:00
case 1 :
data - > fan_div [ nr ] = 0 ;
break ;
case 2 :
data - > fan_div [ nr ] = 1 ;
break ;
case 4 :
data - > fan_div [ nr ] = 2 ;
break ;
case 8 :
data - > fan_div [ nr ] = 3 ;
break ;
2005-04-17 02:20:36 +04:00
default :
2013-01-10 22:01:24 +04:00
dev_err ( dev ,
" fan_div value %ld not supported. Choose one of 1, 2, 4 or 8! \n " ,
val ) ;
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2012-01-15 10:21:41 +04:00
2005-04-17 02:20:36 +04:00
switch ( nr ) {
case 0 :
reg = ( reg & 0xcf ) | ( data - > fan_div [ nr ] < < 4 ) ;
break ;
case 1 :
reg = ( reg & 0x3f ) | ( data - > fan_div [ nr ] < < 6 ) ;
break ;
}
2007-06-09 18:11:16 +04:00
sis5595_write_value ( data , SIS5595_REG_FANDIV , reg ) ;
2005-04-17 02:20:36 +04:00
data - > fan_min [ nr ] =
FAN_TO_REG ( min , DIV_FROM_REG ( data - > fan_div [ nr ] ) ) ;
2007-06-09 18:11:16 +04:00
sis5595_write_value ( data , SIS5595_REG_FAN_MIN ( nr ) , data - > fan_min [ nr ] ) ;
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
2019-01-23 01:28:50 +03:00
static SENSOR_DEVICE_ATTR_RO ( fan1_input , fan , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan1_min , fan_min , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan1_div , fan_div , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan2_input , fan , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan2_min , fan_min , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( fan2_div , fan_div , 1 ) ;
2005-04-17 02:20:36 +04:00
/* Alarms */
2016-12-22 15:05:05 +03:00
static ssize_t alarms_show ( struct device * dev , struct device_attribute * attr ,
2012-01-15 10:21:41 +04:00
char * buf )
2005-04-17 02:20:36 +04:00
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , data - > alarms ) ;
}
2016-12-22 15:05:05 +03:00
static DEVICE_ATTR_RO ( alarms ) ;
2006-09-24 23:24:46 +04:00
2019-01-23 01:28:50 +03:00
static ssize_t alarm_show ( struct device * dev , struct device_attribute * da ,
2007-10-15 22:50:53 +04:00
char * buf )
{
struct sis5595_data * data = sis5595_update_device ( dev ) ;
int nr = to_sensor_dev_attr ( da ) - > index ;
return sprintf ( buf , " %u \n " , ( data - > alarms > > nr ) & 1 ) ;
}
2019-01-23 01:28:50 +03:00
static SENSOR_DEVICE_ATTR_RO ( in0_alarm , alarm , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( in1_alarm , alarm , 1 ) ;
static SENSOR_DEVICE_ATTR_RO ( in2_alarm , alarm , 2 ) ;
static SENSOR_DEVICE_ATTR_RO ( in3_alarm , alarm , 3 ) ;
static SENSOR_DEVICE_ATTR_RO ( in4_alarm , alarm , 15 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan1_alarm , alarm , 6 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan2_alarm , alarm , 7 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp1_alarm , alarm , 15 ) ;
2007-10-15 22:50:53 +04:00
2016-12-22 15:05:05 +03:00
static ssize_t name_show ( struct device * dev , struct device_attribute * attr ,
2007-06-09 18:11:16 +04:00
char * buf )
{
struct sis5595_data * data = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %s \n " , data - > name ) ;
}
2016-12-22 15:05:05 +03:00
static DEVICE_ATTR_RO ( name ) ;
2007-06-09 18:11:16 +04:00
2006-09-24 23:24:46 +04:00
static struct attribute * sis5595_attributes [ ] = {
2007-06-09 18:11:16 +04:00
& sensor_dev_attr_in0_input . dev_attr . attr ,
& sensor_dev_attr_in0_min . dev_attr . attr ,
& sensor_dev_attr_in0_max . dev_attr . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_in0_alarm . dev_attr . attr ,
2007-06-09 18:11:16 +04:00
& sensor_dev_attr_in1_input . dev_attr . attr ,
& sensor_dev_attr_in1_min . dev_attr . attr ,
& sensor_dev_attr_in1_max . dev_attr . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_in1_alarm . dev_attr . attr ,
2007-06-09 18:11:16 +04:00
& sensor_dev_attr_in2_input . dev_attr . attr ,
& sensor_dev_attr_in2_min . dev_attr . attr ,
& sensor_dev_attr_in2_max . dev_attr . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_in2_alarm . dev_attr . attr ,
2007-06-09 18:11:16 +04:00
& sensor_dev_attr_in3_input . dev_attr . attr ,
& sensor_dev_attr_in3_min . dev_attr . attr ,
& sensor_dev_attr_in3_max . dev_attr . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_in3_alarm . dev_attr . attr ,
2007-06-09 18:11:16 +04:00
& sensor_dev_attr_fan1_input . dev_attr . attr ,
& sensor_dev_attr_fan1_min . dev_attr . attr ,
& sensor_dev_attr_fan1_div . dev_attr . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_fan1_alarm . dev_attr . attr ,
2007-06-09 18:11:16 +04:00
& sensor_dev_attr_fan2_input . dev_attr . attr ,
& sensor_dev_attr_fan2_min . dev_attr . attr ,
& sensor_dev_attr_fan2_div . dev_attr . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_fan2_alarm . dev_attr . attr ,
2006-09-24 23:24:46 +04:00
& dev_attr_alarms . attr ,
2007-06-09 18:11:16 +04:00
& dev_attr_name . attr ,
2006-09-24 23:24:46 +04:00
NULL
} ;
static const struct attribute_group sis5595_group = {
. attrs = sis5595_attributes ,
} ;
2007-10-15 15:27:13 +04:00
static struct attribute * sis5595_attributes_in4 [ ] = {
2007-06-09 18:11:16 +04:00
& sensor_dev_attr_in4_input . dev_attr . attr ,
& sensor_dev_attr_in4_min . dev_attr . attr ,
& sensor_dev_attr_in4_max . dev_attr . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_in4_alarm . dev_attr . attr ,
2007-10-15 15:27:13 +04:00
NULL
} ;
static const struct attribute_group sis5595_group_in4 = {
. attrs = sis5595_attributes_in4 ,
} ;
2006-09-24 23:24:46 +04:00
2007-10-15 15:27:13 +04:00
static struct attribute * sis5595_attributes_temp1 [ ] = {
2006-09-24 23:24:46 +04:00
& dev_attr_temp1_input . attr ,
& dev_attr_temp1_max . attr ,
& dev_attr_temp1_max_hyst . attr ,
2007-10-15 22:50:53 +04:00
& sensor_dev_attr_temp1_alarm . dev_attr . attr ,
2006-09-24 23:24:46 +04:00
NULL
} ;
2007-10-15 15:27:13 +04:00
static const struct attribute_group sis5595_group_temp1 = {
. attrs = sis5595_attributes_temp1 ,
2006-09-24 23:24:46 +04:00
} ;
2012-01-15 10:21:41 +04:00
2022-09-22 10:49:00 +03:00
/* Called when we have found a new SIS5595. */
static void sis5595_init_device ( struct sis5595_data * data )
{
u8 config = sis5595_read_value ( data , SIS5595_REG_CONFIG ) ;
if ( ! ( config & 0x01 ) )
sis5595_write_value ( data , SIS5595_REG_CONFIG ,
( config & 0xf7 ) | 0x01 ) ;
}
2005-04-17 02:20:36 +04:00
/* This is called when the module is loaded */
2012-11-19 22:22:35 +04:00
static int sis5595_probe ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
int err = 0 ;
int i ;
struct sis5595_data * data ;
2007-06-09 18:11:16 +04:00
struct resource * res ;
2005-04-17 02:20:36 +04:00
char val ;
/* Reserve the ISA region */
2007-06-09 18:11:16 +04:00
res = platform_get_resource ( pdev , IORESOURCE_IO , 0 ) ;
2012-06-02 22:20:23 +04:00
if ( ! devm_request_region ( & pdev - > dev , res - > start , SIS5595_EXTENT ,
2022-09-22 10:48:59 +03:00
DRIVER_NAME ) )
2012-06-02 22:20:23 +04:00
return - EBUSY ;
2005-04-17 02:20:36 +04:00
2012-06-02 22:20:23 +04:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( struct sis5595_data ) ,
GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
2006-01-19 01:19:26 +03:00
mutex_init ( & data - > lock ) ;
2007-06-09 18:11:16 +04:00
mutex_init ( & data - > update_lock ) ;
data - > addr = res - > start ;
2022-09-22 10:48:59 +03:00
data - > name = DRIVER_NAME ;
2007-06-09 18:11:16 +04:00
platform_set_drvdata ( pdev , data ) ;
2005-04-17 02:20:36 +04:00
2012-01-15 10:21:41 +04:00
/*
* Check revision and pin registers to determine whether 4 or 5 voltages
*/
2007-08-28 03:17:01 +04:00
data - > revision = s_bridge - > revision ;
2005-04-17 02:20:36 +04:00
/* 4 voltages, 1 temp */
data - > maxins = 3 ;
if ( data - > revision > = REV2MIN ) {
pci_read_config_byte ( s_bridge , SIS5595_PIN_REG , & val ) ;
if ( ! ( val & 0x80 ) )
/* 5 voltages, no temps */
data - > maxins = 4 ;
}
2012-01-15 10:21:41 +04:00
2005-04-17 02:20:36 +04:00
/* Initialize the SIS5595 chip */
2007-06-09 18:11:16 +04:00
sis5595_init_device ( data ) ;
2005-04-17 02:20:36 +04:00
/* A few vars need to be filled upon startup */
for ( i = 0 ; i < 2 ; i + + ) {
2007-06-09 18:11:16 +04:00
data - > fan_min [ i ] = sis5595_read_value ( data ,
2005-04-17 02:20:36 +04:00
SIS5595_REG_FAN_MIN ( i ) ) ;
}
/* Register sysfs hooks */
2012-01-15 10:21:41 +04:00
err = sysfs_create_group ( & pdev - > dev . kobj , & sis5595_group ) ;
if ( err )
2012-06-02 22:20:23 +04:00
return err ;
2006-09-24 23:24:46 +04:00
if ( data - > maxins = = 4 ) {
2012-01-15 10:21:41 +04:00
err = sysfs_create_group ( & pdev - > dev . kobj , & sis5595_group_in4 ) ;
if ( err )
2006-09-24 23:24:46 +04:00
goto exit_remove_files ;
} else {
2012-01-15 10:21:41 +04:00
err = sysfs_create_group ( & pdev - > dev . kobj , & sis5595_group_temp1 ) ;
if ( err )
2006-09-24 23:24:46 +04:00
goto exit_remove_files ;
}
2007-08-21 00:46:20 +04:00
data - > hwmon_dev = hwmon_device_register ( & pdev - > dev ) ;
if ( IS_ERR ( data - > hwmon_dev ) ) {
err = PTR_ERR ( data - > hwmon_dev ) ;
2006-09-24 23:24:46 +04:00
goto exit_remove_files ;
2005-07-16 05:39:18 +04:00
}
2005-04-17 02:20:36 +04:00
return 0 ;
2005-07-16 05:39:18 +04:00
2006-09-24 23:24:46 +04:00
exit_remove_files :
2007-06-09 18:11:16 +04:00
sysfs_remove_group ( & pdev - > dev . kobj , & sis5595_group ) ;
2007-10-15 15:27:13 +04:00
sysfs_remove_group ( & pdev - > dev . kobj , & sis5595_group_in4 ) ;
sysfs_remove_group ( & pdev - > dev . kobj , & sis5595_group_temp1 ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
2012-11-19 22:25:51 +04:00
static int sis5595_remove ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
2007-06-09 18:11:16 +04:00
struct sis5595_data * data = platform_get_drvdata ( pdev ) ;
2005-04-17 02:20:36 +04:00
2007-08-21 00:46:20 +04:00
hwmon_device_unregister ( data - > hwmon_dev ) ;
2007-06-09 18:11:16 +04:00
sysfs_remove_group ( & pdev - > dev . kobj , & sis5595_group ) ;
2007-10-15 15:27:13 +04:00
sysfs_remove_group ( & pdev - > dev . kobj , & sis5595_group_in4 ) ;
sysfs_remove_group ( & pdev - > dev . kobj , & sis5595_group_temp1 ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2013-12-03 11:10:29 +04:00
static const struct pci_device_id sis5595_pci_ids [ ] = {
2005-04-17 02:20:36 +04:00
{ PCI_DEVICE ( PCI_VENDOR_ID_SI , PCI_DEVICE_ID_SI_503 ) } ,
{ 0 , }
} ;
MODULE_DEVICE_TABLE ( pci , sis5595_pci_ids ) ;
2012-11-19 22:24:37 +04:00
static int blacklist [ ] = {
2005-04-17 02:20:36 +04:00
PCI_DEVICE_ID_SI_540 ,
PCI_DEVICE_ID_SI_550 ,
PCI_DEVICE_ID_SI_630 ,
PCI_DEVICE_ID_SI_645 ,
PCI_DEVICE_ID_SI_730 ,
PCI_DEVICE_ID_SI_735 ,
2012-01-15 10:21:41 +04:00
PCI_DEVICE_ID_SI_5511 , /*
* 5513 chip has the 000 8 device but
* that ID shows up in other chips so we
* use the 5511 ID for recognition
*/
2005-04-17 02:20:36 +04:00
PCI_DEVICE_ID_SI_5597 ,
PCI_DEVICE_ID_SI_5598 ,
0 } ;
2012-11-19 22:22:35 +04:00
static int sis5595_device_add ( unsigned short address )
2007-06-09 18:11:16 +04:00
{
struct resource res = {
. start = address ,
. end = address + SIS5595_EXTENT - 1 ,
2022-09-22 10:48:59 +03:00
. name = DRIVER_NAME ,
2007-06-09 18:11:16 +04:00
. flags = IORESOURCE_IO ,
} ;
int err ;
2009-01-07 18:37:35 +03:00
err = acpi_check_resource_conflict ( & res ) ;
if ( err )
goto exit ;
2022-09-22 10:48:59 +03:00
pdev = platform_device_alloc ( DRIVER_NAME , address ) ;
2007-06-09 18:11:16 +04:00
if ( ! pdev ) {
err = - ENOMEM ;
2010-10-20 10:51:47 +04:00
pr_err ( " Device allocation failed \n " ) ;
2007-06-09 18:11:16 +04:00
goto exit ;
}
err = platform_device_add_resources ( pdev , & res , 1 ) ;
if ( err ) {
2010-10-20 10:51:47 +04:00
pr_err ( " Device resource addition failed (%d) \n " , err ) ;
2007-06-09 18:11:16 +04:00
goto exit_device_put ;
}
err = platform_device_add ( pdev ) ;
if ( err ) {
2010-10-20 10:51:47 +04:00
pr_err ( " Device addition failed (%d) \n " , err ) ;
2007-06-09 18:11:16 +04:00
goto exit_device_put ;
}
return 0 ;
exit_device_put :
platform_device_put ( pdev ) ;
exit :
return err ;
}
2022-09-22 10:49:00 +03:00
static struct platform_driver sis5595_driver = {
. driver = {
. name = DRIVER_NAME ,
} ,
. probe = sis5595_probe ,
. remove = sis5595_remove ,
} ;
2012-11-19 22:22:35 +04:00
static int sis5595_pci_probe ( struct pci_dev * dev ,
2005-04-17 02:20:36 +04:00
const struct pci_device_id * id )
{
2007-06-09 18:11:16 +04:00
u16 address ;
u8 enable ;
2005-04-17 02:20:36 +04:00
int * i ;
for ( i = blacklist ; * i ! = 0 ; i + + ) {
2007-10-14 22:57:35 +04:00
struct pci_dev * d ;
2012-01-15 10:21:41 +04:00
d = pci_get_device ( PCI_VENDOR_ID_SI , * i , NULL ) ;
if ( d ) {
dev_err ( & d - > dev ,
" Looked for SIS5595 but found unsupported device %.4x \n " ,
* i ) ;
2007-10-14 22:57:35 +04:00
pci_dev_put ( d ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
}
2012-01-15 10:21:41 +04:00
2007-06-09 18:11:16 +04:00
force_addr & = ~ ( SIS5595_EXTENT - 1 ) ;
if ( force_addr ) {
dev_warn ( & dev - > dev , " Forcing ISA address 0x%x \n " , force_addr ) ;
pci_write_config_word ( dev , SIS5595_BASE_REG , force_addr ) ;
}
2005-04-17 02:20:36 +04:00
if ( PCIBIOS_SUCCESSFUL ! =
2007-06-09 18:11:16 +04:00
pci_read_config_word ( dev , SIS5595_BASE_REG , & address ) ) {
dev_err ( & dev - > dev , " Failed to read ISA address \n " ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2007-06-09 18:11:16 +04:00
}
2012-01-15 10:21:41 +04:00
2007-06-09 18:11:16 +04:00
address & = ~ ( SIS5595_EXTENT - 1 ) ;
if ( ! address ) {
2012-01-15 10:21:41 +04:00
dev_err ( & dev - > dev ,
" Base address not set - upgrade BIOS or use force_addr=0xaddr \n " ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
2007-06-09 18:11:16 +04:00
if ( force_addr & & address ! = force_addr ) {
/* doesn't work for some chips? */
dev_err ( & dev - > dev , " Failed to force ISA address \n " ) ;
return - ENODEV ;
}
2005-04-17 02:20:36 +04:00
2007-06-09 18:11:16 +04:00
if ( PCIBIOS_SUCCESSFUL ! =
pci_read_config_byte ( dev , SIS5595_ENABLE_REG , & enable ) ) {
dev_err ( & dev - > dev , " Failed to read enable register \n " ) ;
return - ENODEV ;
}
if ( ! ( enable & 0x80 ) ) {
if ( ( PCIBIOS_SUCCESSFUL ! =
pci_write_config_byte ( dev , SIS5595_ENABLE_REG ,
enable | 0x80 ) )
| | ( PCIBIOS_SUCCESSFUL ! =
pci_read_config_byte ( dev , SIS5595_ENABLE_REG , & enable ) )
| | ( ! ( enable & 0x80 ) ) ) {
/* doesn't work for some chips! */
dev_err ( & dev - > dev , " Failed to enable HWM device \n " ) ;
return - ENODEV ;
}
2005-04-17 02:20:36 +04:00
}
2007-06-09 18:11:16 +04:00
if ( platform_driver_register ( & sis5595_driver ) ) {
dev_dbg ( & dev - > dev , " Failed to register sis5595 driver \n " ) ;
goto exit ;
}
s_bridge = pci_dev_get ( dev ) ;
/* Sets global pdev as a side effect */
if ( sis5595_device_add ( address ) )
goto exit_unregister ;
2012-01-15 10:21:41 +04:00
/*
* Always return failure here . This is to allow other drivers to bind
2005-04-17 02:20:36 +04:00
* to this pci device . We don ' t really want to have control over the
* pci device , we only wanted to read as few register values from it .
*/
return - ENODEV ;
2007-06-09 18:11:16 +04:00
exit_unregister :
pci_dev_put ( dev ) ;
platform_driver_unregister ( & sis5595_driver ) ;
exit :
return - ENODEV ;
2005-04-17 02:20:36 +04:00
}
static struct pci_driver sis5595_pci_driver = {
2022-09-22 10:48:59 +03:00
. name = DRIVER_NAME ,
2005-04-17 02:20:36 +04:00
. id_table = sis5595_pci_ids ,
. probe = sis5595_pci_probe ,
} ;
static int __init sm_sis5595_init ( void )
{
return pci_register_driver ( & sis5595_pci_driver ) ;
}
static void __exit sm_sis5595_exit ( void )
{
pci_unregister_driver ( & sis5595_pci_driver ) ;
if ( s_bridge ! = NULL ) {
2007-06-09 18:11:16 +04:00
platform_device_unregister ( pdev ) ;
platform_driver_unregister ( & sis5595_driver ) ;
2005-04-17 02:20:36 +04:00
pci_dev_put ( s_bridge ) ;
s_bridge = NULL ;
}
}
MODULE_AUTHOR ( " Aurelien Jarno <aurelien@aurel32.net> " ) ;
MODULE_DESCRIPTION ( " SiS 5595 Sensor device " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( sm_sis5595_init ) ;
module_exit ( sm_sis5595_exit ) ;