2014-04-06 19:57:20 +04:00
/*
* nct6683 - Driver for the hardware monitoring functionality of
* Nuvoton NCT6683D eSIO
*
* Copyright ( C ) 2013 Guenter Roeck < linux @ roeck - us . net >
*
* Derived from nct6775 driver
* Copyright ( C ) 2012 , 2013 Guenter Roeck < linux @ roeck - us . net >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* Supports the following chips :
*
* Chip # vin # fan # pwm # temp chip ID
* nct6683d 21 ( 1 ) 16 8 32 ( 1 ) 0xc730
*
* Notes :
* ( 1 ) Total number of vin and temp inputs is 32.
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/acpi.h>
# include <linux/dmi.h>
# include <linux/err.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/jiffies.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
enum kinds { nct6683 } ;
static bool force ;
module_param ( force , bool , 0 ) ;
MODULE_PARM_DESC ( force , " Set to one to enable detection on non-Intel boards " ) ;
static const char * const nct6683_device_names [ ] = {
" nct6683 " ,
} ;
static const char * const nct6683_chip_names [ ] = {
" NCT6683D " ,
} ;
# define DRVNAME "nct6683"
/*
* Super - I / O constants and functions
*/
# define NCT6683_LD_ACPI 0x0a
# define NCT6683_LD_HWM 0x0b
# define NCT6683_LD_VID 0x0d
# define SIO_REG_LDSEL 0x07 /* Logical device select */
# define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
# define SIO_REG_ENABLE 0x30 /* Logical device enable */
# define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
# define SIO_NCT6681_ID 0xb270 /* for later */
# define SIO_NCT6683_ID 0xc730
# define SIO_ID_MASK 0xFFF0
static inline void
superio_outb ( int ioreg , int reg , int val )
{
outb ( reg , ioreg ) ;
outb ( val , ioreg + 1 ) ;
}
static inline int
superio_inb ( int ioreg , int reg )
{
outb ( reg , ioreg ) ;
return inb ( ioreg + 1 ) ;
}
static inline void
superio_select ( int ioreg , int ld )
{
outb ( SIO_REG_LDSEL , ioreg ) ;
outb ( ld , ioreg + 1 ) ;
}
static inline int
superio_enter ( int ioreg )
{
/*
* Try to reserve < ioreg > and < ioreg + 1 > for exclusive access .
*/
if ( ! request_muxed_region ( ioreg , 2 , DRVNAME ) )
return - EBUSY ;
outb ( 0x87 , ioreg ) ;
outb ( 0x87 , ioreg ) ;
return 0 ;
}
static inline void
superio_exit ( int ioreg )
{
outb ( 0xaa , ioreg ) ;
outb ( 0x02 , ioreg ) ;
outb ( 0x02 , ioreg + 1 ) ;
release_region ( ioreg , 2 ) ;
}
/*
* ISA constants
*/
# define IOREGION_ALIGNMENT (~7)
# define IOREGION_OFFSET 4 /* Use EC port 1 */
# define IOREGION_LENGTH 4
# define EC_PAGE_REG 0
# define EC_INDEX_REG 1
# define EC_DATA_REG 2
# define EC_EVENT_REG 3
/* Common and NCT6683 specific data */
# define NCT6683_NUM_REG_MON 32
# define NCT6683_NUM_REG_FAN 16
# define NCT6683_NUM_REG_PWM 8
# define NCT6683_REG_MON(x) (0x100 + (x) * 2)
# define NCT6683_REG_FAN_RPM(x) (0x140 + (x) * 2)
# define NCT6683_REG_PWM(x) (0x160 + (x))
# define NCT6683_REG_MON_STS(x) (0x174 + (x))
# define NCT6683_REG_IDLE(x) (0x178 + (x))
# define NCT6683_REG_FAN_STS(x) (0x17c + (x))
# define NCT6683_REG_FAN_ERRSTS 0x17e
# define NCT6683_REG_FAN_INITSTS 0x17f
# define NCT6683_HWM_CFG 0x180
# define NCT6683_REG_MON_CFG(x) (0x1a0 + (x))
# define NCT6683_REG_FANIN_CFG(x) (0x1c0 + (x))
# define NCT6683_REG_FANOUT_CFG(x) (0x1d0 + (x))
# define NCT6683_REG_INTEL_TEMP_MAX(x) (0x901 + (x) * 16)
# define NCT6683_REG_INTEL_TEMP_CRIT(x) (0x90d + (x) * 16)
# define NCT6683_REG_TEMP_HYST(x) (0x330 + (x)) /* 8 bit */
# define NCT6683_REG_TEMP_MAX(x) (0x350 + (x)) /* 8 bit */
# define NCT6683_REG_MON_HIGH(x) (0x370 + (x) * 2) /* 8 bit */
# define NCT6683_REG_MON_LOW(x) (0x371 + (x) * 2) /* 8 bit */
# define NCT6683_REG_FAN_MIN(x) (0x3b8 + (x) * 2) /* 16 bit */
# define NCT6683_REG_CUSTOMER_ID 0x602
# define NCT6683_CUSTOMER_ID_INTEL 0x805
# define NCT6683_REG_BUILD_YEAR 0x604
# define NCT6683_REG_BUILD_MONTH 0x605
# define NCT6683_REG_BUILD_DAY 0x606
# define NCT6683_REG_SERIAL 0x607
# define NCT6683_REG_VERSION_HI 0x608
# define NCT6683_REG_VERSION_LO 0x609
# define NCT6683_REG_CR_CASEOPEN 0xe8
# define NCT6683_CR_CASEOPEN_MASK (1 << 7)
# define NCT6683_REG_CR_BEEP 0xe0
# define NCT6683_CR_BEEP_MASK (1 << 6)
static const char * const nct6683_mon_label [ ] = {
NULL , /* disabled */
" Local " ,
" Diode 0 (curr) " ,
" Diode 1 (curr) " ,
" Diode 2 (curr) " ,
" Diode 0 (volt) " ,
" Diode 1 (volt) " ,
" Diode 2 (volt) " ,
" Thermistor 14 " ,
" Thermistor 15 " ,
" Thermistor 16 " ,
" Thermistor 0 " ,
" Thermistor 1 " ,
" Thermistor 2 " ,
" Thermistor 3 " ,
" Thermistor 4 " ,
" Thermistor 5 " , /* 0x10 */
" Thermistor 6 " ,
" Thermistor 7 " ,
" Thermistor 8 " ,
" Thermistor 9 " ,
" Thermistor 10 " ,
" Thermistor 11 " ,
" Thermistor 12 " ,
" Thermistor 13 " ,
NULL , NULL , NULL , NULL , NULL , NULL , NULL ,
" PECI 0.0 " , /* 0x20 */
" PECI 1.0 " ,
" PECI 2.0 " ,
" PECI 3.0 " ,
" PECI 0.1 " ,
" PECI 1.1 " ,
" PECI 2.1 " ,
" PECI 3.1 " ,
" PECI DIMM 0 " ,
" PECI DIMM 1 " ,
" PECI DIMM 2 " ,
" PECI DIMM 3 " ,
NULL , NULL , NULL , NULL ,
" PCH CPU " , /* 0x30 */
" PCH CHIP " ,
" PCH CHIP CPU MAX " ,
" PCH MCH " ,
" PCH DIMM 0 " ,
" PCH DIMM 1 " ,
" PCH DIMM 2 " ,
" PCH DIMM 3 " ,
" SMBus 0 " ,
" SMBus 1 " ,
" SMBus 2 " ,
" SMBus 3 " ,
" SMBus 4 " ,
" SMBus 5 " ,
" DIMM 0 " ,
" DIMM 1 " ,
" DIMM 2 " , /* 0x40 */
" DIMM 3 " ,
" AMD TSI Addr 90h " ,
" AMD TSI Addr 92h " ,
" AMD TSI Addr 94h " ,
" AMD TSI Addr 96h " ,
" AMD TSI Addr 98h " ,
" AMD TSI Addr 9ah " ,
" AMD TSI Addr 9ch " ,
" AMD TSI Addr 9dh " ,
NULL , NULL , NULL , NULL , NULL , NULL ,
" Virtual 0 " , /* 0x50 */
" Virtual 1 " ,
" Virtual 2 " ,
" Virtual 3 " ,
" Virtual 4 " ,
" Virtual 5 " ,
" Virtual 6 " ,
" Virtual 7 " ,
NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL ,
" VCC " , /* 0x60 voltage sensors */
" VSB " ,
" AVSB " ,
" VTT " ,
" VBAT " ,
" VREF " ,
" VIN0 " ,
" VIN1 " ,
" VIN2 " ,
" VIN3 " ,
" VIN4 " ,
" VIN5 " ,
" VIN6 " ,
" VIN7 " ,
" VIN8 " ,
" VIN9 " ,
" VIN10 " ,
" VIN11 " ,
" VIN12 " ,
" VIN13 " ,
" VIN14 " ,
" VIN15 " ,
" VIN16 " ,
} ;
# define NUM_MON_LABELS ARRAY_SIZE(nct6683_mon_label)
# define MON_VOLTAGE_START 0x60
/* ------------------------------------------------------- */
struct nct6683_data {
int addr ; /* IO base of EC space */
int sioreg ; /* SIO register */
enum kinds kind ;
u16 customer_id ;
struct device * hwmon_dev ;
const struct attribute_group * groups [ 6 ] ;
int temp_num ; /* number of temperature attributes */
u8 temp_index [ NCT6683_NUM_REG_MON ] ;
u8 temp_src [ NCT6683_NUM_REG_MON ] ;
u8 in_num ; /* number of voltage attributes */
u8 in_index [ NCT6683_NUM_REG_MON ] ;
u8 in_src [ NCT6683_NUM_REG_MON ] ;
struct mutex update_lock ; /* used to protect sensor updates */
bool valid ; /* true if following fields are valid */
unsigned long last_updated ; /* In jiffies */
/* Voltage attribute values */
u8 in [ 3 ] [ NCT6683_NUM_REG_MON ] ; /* [0]=in, [1]=in_max, [2]=in_min */
/* Temperature attribute values */
s16 temp_in [ NCT6683_NUM_REG_MON ] ;
s8 temp [ 4 ] [ NCT6683_NUM_REG_MON ] ; /* [0]=min, [1]=max, [2]=hyst,
* [ 3 ] = crit
*/
/* Fan attribute values */
unsigned int rpm [ NCT6683_NUM_REG_FAN ] ;
u16 fan_min [ NCT6683_NUM_REG_FAN ] ;
u8 fanin_cfg [ NCT6683_NUM_REG_FAN ] ;
u8 fanout_cfg [ NCT6683_NUM_REG_FAN ] ;
u16 have_fan ; /* some fan inputs can be disabled */
u8 have_pwm ;
u8 pwm [ NCT6683_NUM_REG_PWM ] ;
# ifdef CONFIG_PM
/* Remember extra register values over suspend/resume */
u8 hwm_cfg ;
# endif
} ;
struct nct6683_sio_data {
int sioreg ;
enum kinds kind ;
} ;
struct sensor_device_template {
struct device_attribute dev_attr ;
union {
struct {
u8 nr ;
u8 index ;
} s ;
int index ;
} u ;
bool s2 ; /* true if both index and nr are used */
} ;
struct sensor_device_attr_u {
union {
struct sensor_device_attribute a1 ;
struct sensor_device_attribute_2 a2 ;
} u ;
char name [ 32 ] ;
} ;
# define __TEMPLATE_ATTR(_template, _mode, _show, _store) { \
. attr = { . name = _template , . mode = _mode } , \
. show = _show , \
. store = _store , \
}
# define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \
{ . dev_attr = __TEMPLATE_ATTR ( _template , _mode , _show , _store ) , \
. u . index = _index , \
. s2 = false }
# define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \
_nr , _index ) \
{ . dev_attr = __TEMPLATE_ATTR ( _template , _mode , _show , _store ) , \
. u . s . index = _index , \
. u . s . nr = _nr , \
. s2 = true }
# define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \
static struct sensor_device_template sensor_dev_template_ # # _name \
= SENSOR_DEVICE_TEMPLATE ( _template , _mode , _show , _store , \
_index )
# define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \
_nr , _index ) \
static struct sensor_device_template sensor_dev_template_ # # _name \
= SENSOR_DEVICE_TEMPLATE_2 ( _template , _mode , _show , _store , \
_nr , _index )
struct sensor_template_group {
struct sensor_device_template * * templates ;
umode_t ( * is_visible ) ( struct kobject * , struct attribute * , int ) ;
int base ;
} ;
static struct attribute_group *
nct6683_create_attr_group ( struct device * dev , struct sensor_template_group * tg ,
int repeat )
{
struct sensor_device_attribute_2 * a2 ;
struct sensor_device_attribute * a ;
struct sensor_device_template * * t ;
struct sensor_device_attr_u * su ;
struct attribute_group * group ;
struct attribute * * attrs ;
int i , j , count ;
if ( repeat < = 0 )
return ERR_PTR ( - EINVAL ) ;
t = tg - > templates ;
for ( count = 0 ; * t ; t + + , count + + )
;
if ( count = = 0 )
return ERR_PTR ( - EINVAL ) ;
group = devm_kzalloc ( dev , sizeof ( * group ) , GFP_KERNEL ) ;
if ( group = = NULL )
return ERR_PTR ( - ENOMEM ) ;
attrs = devm_kzalloc ( dev , sizeof ( * attrs ) * ( repeat * count + 1 ) ,
GFP_KERNEL ) ;
if ( attrs = = NULL )
return ERR_PTR ( - ENOMEM ) ;
su = devm_kzalloc ( dev , sizeof ( * su ) * repeat * count ,
GFP_KERNEL ) ;
if ( su = = NULL )
return ERR_PTR ( - ENOMEM ) ;
group - > attrs = attrs ;
group - > is_visible = tg - > is_visible ;
for ( i = 0 ; i < repeat ; i + + ) {
t = tg - > templates ;
for ( j = 0 ; * t ! = NULL ; j + + ) {
snprintf ( su - > name , sizeof ( su - > name ) ,
( * t ) - > dev_attr . attr . name , tg - > base + i ) ;
if ( ( * t ) - > s2 ) {
a2 = & su - > u . a2 ;
a2 - > dev_attr . attr . name = su - > name ;
a2 - > nr = ( * t ) - > u . s . nr + i ;
a2 - > index = ( * t ) - > u . s . index ;
a2 - > dev_attr . attr . mode =
( * t ) - > dev_attr . attr . mode ;
a2 - > dev_attr . show = ( * t ) - > dev_attr . show ;
a2 - > dev_attr . store = ( * t ) - > dev_attr . store ;
* attrs = & a2 - > dev_attr . attr ;
} else {
a = & su - > u . a1 ;
a - > dev_attr . attr . name = su - > name ;
a - > index = ( * t ) - > u . index + i ;
a - > dev_attr . attr . mode =
( * t ) - > dev_attr . attr . mode ;
a - > dev_attr . show = ( * t ) - > dev_attr . show ;
a - > dev_attr . store = ( * t ) - > dev_attr . store ;
* attrs = & a - > dev_attr . attr ;
}
attrs + + ;
su + + ;
t + + ;
}
}
return group ;
}
/* LSB is 16 mV, except for the following sources, where it is 32 mV */
# define MON_SRC_VCC 0x60
# define MON_SRC_VSB 0x61
# define MON_SRC_AVSB 0x62
# define MON_SRC_VBAT 0x64
static inline long in_from_reg ( u16 reg , u8 src )
{
int scale = 16 ;
if ( src = = MON_SRC_VCC | | src = = MON_SRC_VSB | | src = = MON_SRC_AVSB | |
src = = MON_SRC_VBAT )
scale < < = 1 ;
return reg * scale ;
}
static inline u16 in_to_reg ( u32 val , u8 src )
{
int scale = 16 ;
if ( src = = MON_SRC_VCC | | src = = MON_SRC_VSB | | src = = MON_SRC_AVSB | |
src = = MON_SRC_VBAT )
scale < < = 1 ;
return clamp_val ( DIV_ROUND_CLOSEST ( val , scale ) , 0 , 127 ) ;
}
static u16 nct6683_read ( struct nct6683_data * data , u16 reg )
{
int res ;
outb_p ( 0xff , data - > addr + EC_PAGE_REG ) ; /* unlock */
outb_p ( reg > > 8 , data - > addr + EC_PAGE_REG ) ;
outb_p ( reg & 0xff , data - > addr + EC_INDEX_REG ) ;
res = inb_p ( data - > addr + EC_DATA_REG ) ;
return res ;
}
static u16 nct6683_read16 ( struct nct6683_data * data , u16 reg )
{
return ( nct6683_read ( data , reg ) < < 8 ) | nct6683_read ( data , reg + 1 ) ;
}
static void nct6683_write ( struct nct6683_data * data , u16 reg , u16 value )
{
outb_p ( 0xff , data - > addr + EC_PAGE_REG ) ; /* unlock */
outb_p ( reg > > 8 , data - > addr + EC_PAGE_REG ) ;
outb_p ( reg & 0xff , data - > addr + EC_INDEX_REG ) ;
outb_p ( value & 0xff , data - > addr + EC_DATA_REG ) ;
}
static int get_in_reg ( struct nct6683_data * data , int nr , int index )
{
int ch = data - > in_index [ index ] ;
int reg = - EINVAL ;
switch ( nr ) {
case 0 :
reg = NCT6683_REG_MON ( ch ) ;
break ;
case 1 :
if ( data - > customer_id ! = NCT6683_CUSTOMER_ID_INTEL )
reg = NCT6683_REG_MON_LOW ( ch ) ;
break ;
case 2 :
if ( data - > customer_id ! = NCT6683_CUSTOMER_ID_INTEL )
reg = NCT6683_REG_MON_HIGH ( ch ) ;
break ;
default :
break ;
}
return reg ;
}
static int get_temp_reg ( struct nct6683_data * data , int nr , int index )
{
int ch = data - > temp_index [ index ] ;
int reg = - EINVAL ;
switch ( data - > customer_id ) {
case NCT6683_CUSTOMER_ID_INTEL :
switch ( nr ) {
default :
case 1 : /* max */
reg = NCT6683_REG_INTEL_TEMP_MAX ( ch ) ;
break ;
case 3 : /* crit */
reg = NCT6683_REG_INTEL_TEMP_CRIT ( ch ) ;
break ;
}
break ;
default :
switch ( nr ) {
default :
case 0 : /* min */
reg = NCT6683_REG_MON_LOW ( ch ) ;
break ;
case 1 : /* max */
reg = NCT6683_REG_TEMP_MAX ( ch ) ;
break ;
case 2 : /* hyst */
reg = NCT6683_REG_TEMP_HYST ( ch ) ;
break ;
case 3 : /* crit */
reg = NCT6683_REG_MON_HIGH ( ch ) ;
break ;
}
break ;
}
return reg ;
}
static void nct6683_update_pwm ( struct device * dev )
{
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int i ;
for ( i = 0 ; i < NCT6683_NUM_REG_PWM ; i + + ) {
if ( ! ( data - > have_pwm & ( 1 < < i ) ) )
continue ;
data - > pwm [ i ] = nct6683_read ( data , NCT6683_REG_PWM ( i ) ) ;
}
}
static struct nct6683_data * nct6683_update_device ( struct device * dev )
{
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int i , j ;
mutex_lock ( & data - > update_lock ) ;
if ( time_after ( jiffies , data - > last_updated + HZ ) | | ! data - > valid ) {
/* Measured voltages and limits */
for ( i = 0 ; i < data - > in_num ; i + + ) {
for ( j = 0 ; j < 3 ; j + + ) {
int reg = get_in_reg ( data , j , i ) ;
if ( reg > = 0 )
data - > in [ j ] [ i ] =
nct6683_read ( data , reg ) ;
}
}
/* Measured temperatures and limits */
for ( i = 0 ; i < data - > temp_num ; i + + ) {
u8 ch = data - > temp_index [ i ] ;
data - > temp_in [ i ] = nct6683_read16 ( data ,
NCT6683_REG_MON ( ch ) ) ;
for ( j = 0 ; j < 4 ; j + + ) {
int reg = get_temp_reg ( data , j , i ) ;
if ( reg > = 0 )
data - > temp [ j ] [ i ] =
nct6683_read ( data , reg ) ;
}
}
/* Measured fan speeds and limits */
for ( i = 0 ; i < ARRAY_SIZE ( data - > rpm ) ; i + + ) {
if ( ! ( data - > have_fan & ( 1 < < i ) ) )
continue ;
data - > rpm [ i ] = nct6683_read16 ( data ,
NCT6683_REG_FAN_RPM ( i ) ) ;
data - > fan_min [ i ] = nct6683_read16 ( data ,
NCT6683_REG_FAN_MIN ( i ) ) ;
}
nct6683_update_pwm ( dev ) ;
data - > last_updated = jiffies ;
data - > valid = true ;
}
mutex_unlock ( & data - > update_lock ) ;
return data ;
}
/*
* Sysfs callback functions
*/
static ssize_t
show_in_label ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
int nr = sattr - > index ;
return sprintf ( buf , " %s \n " , nct6683_mon_label [ data - > in_src [ nr ] ] ) ;
}
static ssize_t
show_in_reg ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute_2 * sattr = to_sensor_dev_attr_2 ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
int index = sattr - > index ;
int nr = sattr - > nr ;
return sprintf ( buf , " %ld \n " ,
in_from_reg ( data - > in [ index ] [ nr ] , data - > in_index [ index ] ) ) ;
}
static umode_t nct6683_in_is_visible ( struct kobject * kobj ,
struct attribute * attr , int index )
{
struct device * dev = container_of ( kobj , struct device , kobj ) ;
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int nr = index % 4 ; /* attribute */
/*
* Voltage limits exist for Intel boards ,
* but register location and encoding is unknown
*/
if ( ( nr = = 2 | | nr = = 3 ) & &
data - > customer_id = = NCT6683_CUSTOMER_ID_INTEL )
return 0 ;
return attr - > mode ;
}
SENSOR_TEMPLATE ( in_label , " in%d_label " , S_IRUGO , show_in_label , NULL , 0 ) ;
SENSOR_TEMPLATE_2 ( in_input , " in%d_input " , S_IRUGO , show_in_reg , NULL , 0 , 0 ) ;
SENSOR_TEMPLATE_2 ( in_min , " in%d_min " , S_IRUGO , show_in_reg , NULL , 0 , 1 ) ;
SENSOR_TEMPLATE_2 ( in_max , " in%d_max " , S_IRUGO , show_in_reg , NULL , 0 , 2 ) ;
static struct sensor_device_template * nct6683_attributes_in_template [ ] = {
& sensor_dev_template_in_label ,
& sensor_dev_template_in_input ,
& sensor_dev_template_in_min ,
& sensor_dev_template_in_max ,
NULL
} ;
static struct sensor_template_group nct6683_in_template_group = {
. templates = nct6683_attributes_in_template ,
. is_visible = nct6683_in_is_visible ,
} ;
static ssize_t
show_fan ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
return sprintf ( buf , " %d \n " , data - > rpm [ sattr - > index ] ) ;
}
static ssize_t
show_fan_min ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct nct6683_data * data = nct6683_update_device ( dev ) ;
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
int nr = sattr - > index ;
return sprintf ( buf , " %d \n " , data - > fan_min [ nr ] ) ;
}
static ssize_t
show_fan_pulses ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
return sprintf ( buf , " %d \n " ,
( ( data - > fanin_cfg [ sattr - > index ] > > 5 ) & 0x03 ) + 1 ) ;
}
static umode_t nct6683_fan_is_visible ( struct kobject * kobj ,
struct attribute * attr , int index )
{
struct device * dev = container_of ( kobj , struct device , kobj ) ;
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int fan = index / 3 ; /* fan index */
int nr = index % 3 ; /* attribute index */
if ( ! ( data - > have_fan & ( 1 < < fan ) ) )
return 0 ;
/*
* Intel may have minimum fan speed limits ,
* but register location and encoding are unknown .
*/
if ( nr = = 2 & & data - > customer_id = = NCT6683_CUSTOMER_ID_INTEL )
return 0 ;
return attr - > mode ;
}
SENSOR_TEMPLATE ( fan_input , " fan%d_input " , S_IRUGO , show_fan , NULL , 0 ) ;
SENSOR_TEMPLATE ( fan_pulses , " fan%d_pulses " , S_IRUGO , show_fan_pulses , NULL , 0 ) ;
SENSOR_TEMPLATE ( fan_min , " fan%d_min " , S_IRUGO , show_fan_min , NULL , 0 ) ;
/*
* nct6683_fan_is_visible uses the index into the following array
* to determine if attributes should be created or not .
* Any change in order or content must be matched .
*/
static struct sensor_device_template * nct6683_attributes_fan_template [ ] = {
& sensor_dev_template_fan_input ,
& sensor_dev_template_fan_pulses ,
& sensor_dev_template_fan_min ,
NULL
} ;
static struct sensor_template_group nct6683_fan_template_group = {
. templates = nct6683_attributes_fan_template ,
. is_visible = nct6683_fan_is_visible ,
. base = 1 ,
} ;
static ssize_t
show_temp_label ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
int nr = sattr - > index ;
return sprintf ( buf , " %s \n " , nct6683_mon_label [ data - > temp_src [ nr ] ] ) ;
}
static ssize_t
show_temp8 ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute_2 * sattr = to_sensor_dev_attr_2 ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
int index = sattr - > index ;
int nr = sattr - > nr ;
return sprintf ( buf , " %d \n " , data - > temp [ index ] [ nr ] * 1000 ) ;
}
static ssize_t
show_temp_hyst ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
int nr = sattr - > index ;
int temp = data - > temp [ 1 ] [ nr ] - data - > temp [ 2 ] [ nr ] ;
return sprintf ( buf , " %d \n " , temp * 1000 ) ;
}
static ssize_t
show_temp16 ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
struct nct6683_data * data = nct6683_update_device ( dev ) ;
int index = sattr - > index ;
return sprintf ( buf , " %d \n " , ( data - > temp_in [ index ] / 128 ) * 500 ) ;
}
/*
* Temperature sensor type is determined by temperature source
* and can not be modified .
* 0x02 . .0 x07 : Thermal diode
* 0x08 . .0 x18 : Thermistor
* 0x20 . .0 x2b : Intel PECI
* 0x42 . .0 x49 : AMD TSI
* Others are unspecified ( not visible )
*/
static int get_temp_type ( u8 src )
{
if ( src > = 0x02 & & src < = 0x07 )
return 3 ; /* thermal diode */
else if ( src > = 0x08 & & src < = 0x18 )
return 4 ; /* thermistor */
else if ( src > = 0x20 & & src < = 0x2b )
return 6 ; /* PECI */
else if ( src > = 0x42 & & src < = 0x49 )
return 5 ;
return 0 ;
}
static ssize_t
show_temp_type ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct nct6683_data * data = nct6683_update_device ( dev ) ;
struct sensor_device_attribute * sattr = to_sensor_dev_attr ( attr ) ;
int nr = sattr - > index ;
return sprintf ( buf , " %d \n " , get_temp_type ( data - > temp_src [ nr ] ) ) ;
}
static umode_t nct6683_temp_is_visible ( struct kobject * kobj ,
struct attribute * attr , int index )
{
struct device * dev = container_of ( kobj , struct device , kobj ) ;
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int temp = index / 7 ; /* temp index */
int nr = index % 7 ; /* attribute index */
/*
* Intel does not have low temperature limits or temperature hysteresis
* registers , or at least register location and encoding is unknown .
*/
if ( ( nr = = 2 | | nr = = 4 ) & &
data - > customer_id = = NCT6683_CUSTOMER_ID_INTEL )
return 0 ;
if ( nr = = 6 & & get_temp_type ( data - > temp_src [ temp ] ) = = 0 )
return 0 ; /* type */
return attr - > mode ;
}
SENSOR_TEMPLATE ( temp_input , " temp%d_input " , S_IRUGO , show_temp16 , NULL , 0 ) ;
SENSOR_TEMPLATE ( temp_label , " temp%d_label " , S_IRUGO , show_temp_label , NULL , 0 ) ;
SENSOR_TEMPLATE_2 ( temp_min , " temp%d_min " , S_IRUGO , show_temp8 , NULL , 0 , 0 ) ;
SENSOR_TEMPLATE_2 ( temp_max , " temp%d_max " , S_IRUGO , show_temp8 , NULL , 0 , 1 ) ;
SENSOR_TEMPLATE ( temp_max_hyst , " temp%d_max_hyst " , S_IRUGO , show_temp_hyst , NULL ,
0 ) ;
SENSOR_TEMPLATE_2 ( temp_crit , " temp%d_crit " , S_IRUGO , show_temp8 , NULL , 0 , 3 ) ;
SENSOR_TEMPLATE ( temp_type , " temp%d_type " , S_IRUGO , show_temp_type , NULL , 0 ) ;
/*
* nct6683_temp_is_visible uses the index into the following array
* to determine if attributes should be created or not .
* Any change in order or content must be matched .
*/
static struct sensor_device_template * nct6683_attributes_temp_template [ ] = {
& sensor_dev_template_temp_input ,
& sensor_dev_template_temp_label ,
& sensor_dev_template_temp_min , /* 2 */
& sensor_dev_template_temp_max , /* 3 */
& sensor_dev_template_temp_max_hyst , /* 4 */
& sensor_dev_template_temp_crit , /* 5 */
& sensor_dev_template_temp_type , /* 6 */
NULL
} ;
static struct sensor_template_group nct6683_temp_template_group = {
. templates = nct6683_attributes_temp_template ,
. is_visible = nct6683_temp_is_visible ,
. base = 1 ,
} ;
static ssize_t
show_pwm ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct nct6683_data * data = nct6683_update_device ( dev ) ;
struct sensor_device_attribute_2 * sattr = to_sensor_dev_attr_2 ( attr ) ;
int index = sattr - > index ;
return sprintf ( buf , " %d \n " , data - > pwm [ index ] ) ;
}
SENSOR_TEMPLATE ( pwm , " pwm%d " , S_IRUGO , show_pwm , NULL , 0 ) ;
static umode_t nct6683_pwm_is_visible ( struct kobject * kobj ,
struct attribute * attr , int index )
{
struct device * dev = container_of ( kobj , struct device , kobj ) ;
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int pwm = index ; /* pwm index */
if ( ! ( data - > have_pwm & ( 1 < < pwm ) ) )
return 0 ;
return attr - > mode ;
}
static struct sensor_device_template * nct6683_attributes_pwm_template [ ] = {
& sensor_dev_template_pwm ,
NULL
} ;
static struct sensor_template_group nct6683_pwm_template_group = {
. templates = nct6683_attributes_pwm_template ,
. is_visible = nct6683_pwm_is_visible ,
. base = 1 ,
} ;
static ssize_t
show_global_beep ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int ret ;
u8 reg ;
mutex_lock ( & data - > update_lock ) ;
ret = superio_enter ( data - > sioreg ) ;
if ( ret )
goto error ;
superio_select ( data - > sioreg , NCT6683_LD_HWM ) ;
reg = superio_inb ( data - > sioreg , NCT6683_REG_CR_BEEP ) ;
superio_exit ( data - > sioreg ) ;
mutex_unlock ( & data - > update_lock ) ;
return sprintf ( buf , " %u \n " , ! ! ( reg & NCT6683_CR_BEEP_MASK ) ) ;
error :
mutex_unlock ( & data - > update_lock ) ;
return ret ;
}
static ssize_t
store_global_beep ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
unsigned long val ;
u8 reg ;
int ret ;
if ( kstrtoul ( buf , 10 , & val ) | | ( val ! = 0 & & val ! = 1 ) )
return - EINVAL ;
mutex_lock ( & data - > update_lock ) ;
ret = superio_enter ( data - > sioreg ) ;
if ( ret ) {
count = ret ;
goto error ;
}
superio_select ( data - > sioreg , NCT6683_LD_HWM ) ;
reg = superio_inb ( data - > sioreg , NCT6683_REG_CR_BEEP ) ;
if ( val )
reg | = NCT6683_CR_BEEP_MASK ;
else
reg & = ~ NCT6683_CR_BEEP_MASK ;
superio_outb ( data - > sioreg , NCT6683_REG_CR_BEEP , reg ) ;
superio_exit ( data - > sioreg ) ;
error :
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
/* Case open detection */
static ssize_t
show_caseopen ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
int ret ;
u8 reg ;
mutex_lock ( & data - > update_lock ) ;
ret = superio_enter ( data - > sioreg ) ;
if ( ret )
goto error ;
superio_select ( data - > sioreg , NCT6683_LD_ACPI ) ;
reg = superio_inb ( data - > sioreg , NCT6683_REG_CR_CASEOPEN ) ;
superio_exit ( data - > sioreg ) ;
mutex_unlock ( & data - > update_lock ) ;
return sprintf ( buf , " %u \n " , ! ( reg & NCT6683_CR_CASEOPEN_MASK ) ) ;
error :
mutex_unlock ( & data - > update_lock ) ;
return ret ;
}
static ssize_t
clear_caseopen ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
unsigned long val ;
u8 reg ;
int ret ;
if ( kstrtoul ( buf , 10 , & val ) | | val ! = 0 )
return - EINVAL ;
mutex_lock ( & data - > update_lock ) ;
/*
* Use CR registers to clear caseopen status .
* Caseopen is activ low , clear by writing 1 into the register .
*/
ret = superio_enter ( data - > sioreg ) ;
if ( ret ) {
count = ret ;
goto error ;
}
superio_select ( data - > sioreg , NCT6683_LD_ACPI ) ;
reg = superio_inb ( data - > sioreg , NCT6683_REG_CR_CASEOPEN ) ;
reg | = NCT6683_CR_CASEOPEN_MASK ;
superio_outb ( data - > sioreg , NCT6683_REG_CR_CASEOPEN , reg ) ;
reg & = ~ NCT6683_CR_CASEOPEN_MASK ;
superio_outb ( data - > sioreg , NCT6683_REG_CR_CASEOPEN , reg ) ;
superio_exit ( data - > sioreg ) ;
data - > valid = false ; /* Force cache refresh */
error :
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
static DEVICE_ATTR ( intrusion0_alarm , S_IWUSR | S_IRUGO , show_caseopen ,
clear_caseopen ) ;
static DEVICE_ATTR ( beep_enable , S_IWUSR | S_IRUGO , show_global_beep ,
store_global_beep ) ;
static struct attribute * nct6683_attributes_other [ ] = {
& dev_attr_intrusion0_alarm . attr ,
& dev_attr_beep_enable . attr ,
NULL
} ;
static const struct attribute_group nct6683_group_other = {
. attrs = nct6683_attributes_other ,
} ;
/* Get the monitoring functions started */
static inline void nct6683_init_device ( struct nct6683_data * data )
{
u8 tmp ;
/* Start hardware monitoring if needed */
tmp = nct6683_read ( data , NCT6683_HWM_CFG ) ;
if ( ! ( tmp & 0x80 ) )
nct6683_write ( data , NCT6683_HWM_CFG , tmp | 0x80 ) ;
}
/*
* There are a total of 24 fan inputs . Each can be configured as input
* or as output . A maximum of 16 inputs and 8 outputs is configurable .
*/
static void
nct6683_setup_fans ( struct nct6683_data * data )
{
int i ;
u8 reg ;
for ( i = 0 ; i < NCT6683_NUM_REG_FAN ; i + + ) {
reg = nct6683_read ( data , NCT6683_REG_FANIN_CFG ( i ) ) ;
if ( reg & 0x80 )
data - > have_fan | = 1 < < i ;
data - > fanin_cfg [ i ] = reg ;
}
for ( i = 0 ; i < NCT6683_NUM_REG_PWM ; i + + ) {
reg = nct6683_read ( data , NCT6683_REG_FANOUT_CFG ( i ) ) ;
if ( reg & 0x80 )
data - > have_pwm | = 1 < < i ;
data - > fanout_cfg [ i ] = reg ;
}
}
/*
* Translation from monitoring register to temperature and voltage attributes
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*
* There are a total of 32 monitoring registers . Each can be assigned to either
* a temperature or voltage monitoring source .
* NCT6683_REG_MON_CFG ( x ) defines assignment for each monitoring source .
*
* Temperature and voltage attribute mapping is determined by walking through
* the NCT6683_REG_MON_CFG registers . If the assigned source is
* a temperature , temp_index [ n ] is set to the monitor register index , and
* temp_src [ n ] is set to the temperature source . If the assigned source is
* a voltage , the respective values are stored in in_index [ ] and in_src [ ] ,
* respectively .
*/
static void nct6683_setup_sensors ( struct nct6683_data * data )
{
u8 reg ;
int i ;
data - > temp_num = 0 ;
data - > in_num = 0 ;
for ( i = 0 ; i < NCT6683_NUM_REG_MON ; i + + ) {
reg = nct6683_read ( data , NCT6683_REG_MON_CFG ( i ) ) & 0x7f ;
/* Ignore invalid assignments */
if ( reg > = NUM_MON_LABELS )
continue ;
/* Skip if disabled or reserved */
if ( nct6683_mon_label [ reg ] = = NULL )
continue ;
if ( reg < MON_VOLTAGE_START ) {
data - > temp_index [ data - > temp_num ] = i ;
data - > temp_src [ data - > temp_num ] = reg ;
data - > temp_num + + ;
} else {
data - > in_index [ data - > in_num ] = i ;
data - > in_src [ data - > in_num ] = reg ;
data - > in_num + + ;
}
}
}
static int nct6683_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct nct6683_sio_data * sio_data = dev - > platform_data ;
struct attribute_group * group ;
struct nct6683_data * data ;
struct device * hwmon_dev ;
struct resource * res ;
int groups = 0 ;
res = platform_get_resource ( pdev , IORESOURCE_IO , 0 ) ;
if ( ! devm_request_region ( dev , res - > start , IOREGION_LENGTH , DRVNAME ) )
return - EBUSY ;
data = devm_kzalloc ( dev , sizeof ( struct nct6683_data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > kind = sio_data - > kind ;
data - > sioreg = sio_data - > sioreg ;
data - > addr = res - > start ;
mutex_init ( & data - > update_lock ) ;
platform_set_drvdata ( pdev , data ) ;
data - > customer_id = nct6683_read16 ( data , NCT6683_REG_CUSTOMER_ID ) ;
nct6683_init_device ( data ) ;
nct6683_setup_fans ( data ) ;
nct6683_setup_sensors ( data ) ;
/* Register sysfs hooks */
if ( data - > have_pwm ) {
group = nct6683_create_attr_group ( dev ,
& nct6683_pwm_template_group ,
fls ( data - > have_pwm ) ) ;
if ( IS_ERR ( group ) )
return PTR_ERR ( group ) ;
data - > groups [ groups + + ] = group ;
}
if ( data - > in_num ) {
group = nct6683_create_attr_group ( dev ,
& nct6683_in_template_group ,
data - > in_num ) ;
if ( IS_ERR ( group ) )
return PTR_ERR ( group ) ;
data - > groups [ groups + + ] = group ;
}
if ( data - > have_fan ) {
group = nct6683_create_attr_group ( dev ,
& nct6683_fan_template_group ,
fls ( data - > have_fan ) ) ;
if ( IS_ERR ( group ) )
return PTR_ERR ( group ) ;
data - > groups [ groups + + ] = group ;
}
if ( data - > temp_num ) {
group = nct6683_create_attr_group ( dev ,
& nct6683_temp_template_group ,
data - > temp_num ) ;
if ( IS_ERR ( group ) )
return PTR_ERR ( group ) ;
data - > groups [ groups + + ] = group ;
}
data - > groups [ groups + + ] = & nct6683_group_other ;
dev_info ( dev , " %s EC firmware version %d.%d build %02x/%02x/%02x \n " ,
nct6683_chip_names [ data - > kind ] ,
nct6683_read ( data , NCT6683_REG_VERSION_HI ) ,
nct6683_read ( data , NCT6683_REG_VERSION_LO ) ,
nct6683_read ( data , NCT6683_REG_BUILD_MONTH ) ,
nct6683_read ( data , NCT6683_REG_BUILD_DAY ) ,
nct6683_read ( data , NCT6683_REG_BUILD_YEAR ) ) ;
hwmon_dev = devm_hwmon_device_register_with_groups ( dev ,
nct6683_device_names [ data - > kind ] , data , data - > groups ) ;
return PTR_ERR_OR_ZERO ( hwmon_dev ) ;
}
# ifdef CONFIG_PM
static int nct6683_suspend ( struct device * dev )
{
struct nct6683_data * data = nct6683_update_device ( dev ) ;
mutex_lock ( & data - > update_lock ) ;
data - > hwm_cfg = nct6683_read ( data , NCT6683_HWM_CFG ) ;
mutex_unlock ( & data - > update_lock ) ;
return 0 ;
}
static int nct6683_resume ( struct device * dev )
{
struct nct6683_data * data = dev_get_drvdata ( dev ) ;
mutex_lock ( & data - > update_lock ) ;
nct6683_write ( data , NCT6683_HWM_CFG , data - > hwm_cfg ) ;
/* Force re-reading all values */
data - > valid = false ;
mutex_unlock ( & data - > update_lock ) ;
return 0 ;
}
static const struct dev_pm_ops nct6683_dev_pm_ops = {
. suspend = nct6683_suspend ,
. resume = nct6683_resume ,
. freeze = nct6683_suspend ,
. restore = nct6683_resume ,
} ;
# define NCT6683_DEV_PM_OPS (&nct6683_dev_pm_ops)
# else
# define NCT6683_DEV_PM_OPS NULL
# endif /* CONFIG_PM */
static struct platform_driver nct6683_driver = {
. driver = {
. name = DRVNAME ,
. pm = NCT6683_DEV_PM_OPS ,
} ,
. probe = nct6683_probe ,
} ;
static int __init nct6683_find ( int sioaddr , struct nct6683_sio_data * sio_data )
{
const char * board_vendor ;
int addr ;
u16 val ;
int err ;
/*
* Only run on Intel boards unless the ' force ' module parameter is set
*/
if ( ! force ) {
board_vendor = dmi_get_system_info ( DMI_BOARD_VENDOR ) ;
if ( ! board_vendor | | strcmp ( board_vendor , " Intel Corporation " ) )
return - ENODEV ;
}
err = superio_enter ( sioaddr ) ;
if ( err )
return err ;
val = ( superio_inb ( sioaddr , SIO_REG_DEVID ) < < 8 )
| superio_inb ( sioaddr , SIO_REG_DEVID + 1 ) ;
switch ( val & SIO_ID_MASK ) {
case SIO_NCT6683_ID :
sio_data - > kind = nct6683 ;
break ;
default :
if ( val ! = 0xffff )
pr_debug ( " unsupported chip ID: 0x%04x \n " , val ) ;
goto fail ;
}
/* We have a known chip, find the HWM I/O address */
superio_select ( sioaddr , NCT6683_LD_HWM ) ;
val = ( superio_inb ( sioaddr , SIO_REG_ADDR ) < < 8 )
| superio_inb ( sioaddr , SIO_REG_ADDR + 1 ) ;
addr = val & IOREGION_ALIGNMENT ;
if ( addr = = 0 ) {
pr_err ( " EC base I/O port unconfigured \n " ) ;
goto fail ;
}
/* Activate logical device if needed */
val = superio_inb ( sioaddr , SIO_REG_ENABLE ) ;
if ( ! ( val & 0x01 ) ) {
pr_err ( " EC is disabled \n " ) ;
goto fail ;
}
superio_exit ( sioaddr ) ;
pr_info ( " Found %s or compatible chip at %#x:%#x \n " ,
nct6683_chip_names [ sio_data - > kind ] , sioaddr , addr ) ;
sio_data - > sioreg = sioaddr ;
return addr ;
fail :
superio_exit ( sioaddr ) ;
return - ENODEV ;
}
/*
* when Super - I / O functions move to a separate file , the Super - I / O
* bus will manage the lifetime of the device and this module will only keep
* track of the nct6683 driver . But since we use platform_device_alloc ( ) , we
* must keep track of the device
*/
static struct platform_device * pdev [ 2 ] ;
static int __init sensors_nct6683_init ( void )
{
struct nct6683_sio_data sio_data ;
int sioaddr [ 2 ] = { 0x2e , 0x4e } ;
struct resource res ;
bool found = false ;
int address ;
int i , err ;
err = platform_driver_register ( & nct6683_driver ) ;
if ( err )
return err ;
/*
* initialize sio_data - > kind and sio_data - > sioreg .
*
* when Super - I / O functions move to a separate file , the Super - I / O
* driver will probe 0x2e and 0x4e and auto - detect the presence of a
* nct6683 hardware monitor , and call probe ( )
*/
for ( i = 0 ; i < ARRAY_SIZE ( pdev ) ; i + + ) {
address = nct6683_find ( sioaddr [ i ] , & sio_data ) ;
if ( address < = 0 )
continue ;
found = true ;
pdev [ i ] = platform_device_alloc ( DRVNAME , address ) ;
if ( ! pdev [ i ] ) {
err = - ENOMEM ;
2014-05-24 19:04:22 +04:00
goto exit_device_unregister ;
2014-04-06 19:57:20 +04:00
}
err = platform_device_add_data ( pdev [ i ] , & sio_data ,
sizeof ( struct nct6683_sio_data ) ) ;
if ( err )
goto exit_device_put ;
memset ( & res , 0 , sizeof ( res ) ) ;
res . name = DRVNAME ;
res . start = address + IOREGION_OFFSET ;
res . end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1 ;
res . flags = IORESOURCE_IO ;
err = acpi_check_resource_conflict ( & res ) ;
if ( err ) {
platform_device_put ( pdev [ i ] ) ;
pdev [ i ] = NULL ;
continue ;
}
err = platform_device_add_resources ( pdev [ i ] , & res , 1 ) ;
if ( err )
goto exit_device_put ;
/* platform_device_add calls probe() */
err = platform_device_add ( pdev [ i ] ) ;
if ( err )
goto exit_device_put ;
}
if ( ! found ) {
err = - ENODEV ;
goto exit_unregister ;
}
return 0 ;
exit_device_put :
2014-05-24 19:04:22 +04:00
platform_device_put ( pdev [ i ] ) ;
exit_device_unregister :
while ( - - i > = 0 ) {
2014-04-06 19:57:20 +04:00
if ( pdev [ i ] )
2014-05-24 19:04:22 +04:00
platform_device_unregister ( pdev [ i ] ) ;
2014-04-06 19:57:20 +04:00
}
exit_unregister :
platform_driver_unregister ( & nct6683_driver ) ;
return err ;
}
static void __exit sensors_nct6683_exit ( void )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( pdev ) ; i + + ) {
if ( pdev [ i ] )
platform_device_unregister ( pdev [ i ] ) ;
}
platform_driver_unregister ( & nct6683_driver ) ;
}
MODULE_AUTHOR ( " Guenter Roeck <linux@roeck-us.net> " ) ;
MODULE_DESCRIPTION ( " NCT6683D driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( sensors_nct6683_init ) ;
module_exit ( sensors_nct6683_exit ) ;