2022-04-27 04:01:53 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* nct6775 - Platform driver for the hardware monitoring
* functionality of Nuvoton NCT677x Super - I / O chips
*
* Copyright ( C ) 2012 Guenter Roeck < linux @ roeck - us . net >
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/acpi.h>
# include <linux/dmi.h>
# include <linux/hwmon-sysfs.h>
# include <linux/hwmon-vid.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/wmi.h>
# include "nct6775.h"
enum sensor_access { access_direct , access_asuswmi } ;
static const char * const nct6775_sio_names [ ] __initconst = {
" NCT6106D " ,
" NCT6116D " ,
" NCT6775F " ,
" NCT6776D/F " ,
" NCT6779D " ,
" NCT6791D " ,
" NCT6792D " ,
" NCT6793D " ,
" NCT6795D " ,
" NCT6796D " ,
" NCT6797D " ,
" NCT6798D " ,
} ;
static unsigned short force_id ;
module_param ( force_id , ushort , 0 ) ;
MODULE_PARM_DESC ( force_id , " Override the detected device ID " ) ;
static unsigned short fan_debounce ;
module_param ( fan_debounce , ushort , 0 ) ;
MODULE_PARM_DESC ( fan_debounce , " Enable debouncing for fan RPM signal " ) ;
# define DRVNAME "nct6775"
# define NCT6775_PORT_CHIPID 0x58
/*
* ISA constants
*/
# define IOREGION_ALIGNMENT (~7)
# define IOREGION_OFFSET 5
# define IOREGION_LENGTH 2
# define ADDR_REG_OFFSET 0
# define DATA_REG_OFFSET 1
/*
* Super - I / O constants and functions
*/
# define NCT6775_LD_ACPI 0x0a
# define NCT6775_LD_HWM 0x0b
# define NCT6775_LD_VID 0x0d
# define NCT6775_LD_12 0x12
# 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_NCT6106_ID 0xc450
# define SIO_NCT6116_ID 0xd280
# define SIO_NCT6775_ID 0xb470
# define SIO_NCT6776_ID 0xc330
# define SIO_NCT6779_ID 0xc560
# define SIO_NCT6791_ID 0xc800
# define SIO_NCT6792_ID 0xc910
# define SIO_NCT6793_ID 0xd120
# define SIO_NCT6795_ID 0xd350
# define SIO_NCT6796_ID 0xd420
# define SIO_NCT6797_ID 0xd450
# define SIO_NCT6798_ID 0xd428
# define SIO_ID_MASK 0xFFF8
/*
* Control registers
*/
# define NCT6775_REG_CR_FAN_DEBOUNCE 0xf0
struct nct6775_sio_data {
int sioreg ;
int ld ;
enum kinds kind ;
enum sensor_access access ;
/* superio_() callbacks */
void ( * sio_outb ) ( struct nct6775_sio_data * sio_data , int reg , int val ) ;
int ( * sio_inb ) ( struct nct6775_sio_data * sio_data , int reg ) ;
void ( * sio_select ) ( struct nct6775_sio_data * sio_data , int ld ) ;
int ( * sio_enter ) ( struct nct6775_sio_data * sio_data ) ;
void ( * sio_exit ) ( struct nct6775_sio_data * sio_data ) ;
} ;
# define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66"
# define ASUSWMI_METHODID_RSIO 0x5253494F
# define ASUSWMI_METHODID_WSIO 0x5753494F
# define ASUSWMI_METHODID_RHWM 0x5248574D
# define ASUSWMI_METHODID_WHWM 0x5748574D
# define ASUSWMI_UNSUPPORTED_METHOD 0xFFFFFFFE
static int nct6775_asuswmi_evaluate_method ( u32 method_id , u8 bank , u8 reg , u8 val , u32 * retval )
{
# if IS_ENABLED(CONFIG_ACPI_WMI)
u32 args = bank | ( reg < < 8 ) | ( val < < 16 ) ;
struct acpi_buffer input = { ( acpi_size ) sizeof ( args ) , & args } ;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER , NULL } ;
acpi_status status ;
union acpi_object * obj ;
u32 tmp = ASUSWMI_UNSUPPORTED_METHOD ;
status = wmi_evaluate_method ( ASUSWMI_MONITORING_GUID , 0 ,
method_id , & input , & output ) ;
if ( ACPI_FAILURE ( status ) )
return - EIO ;
obj = output . pointer ;
if ( obj & & obj - > type = = ACPI_TYPE_INTEGER )
tmp = obj - > integer . value ;
if ( retval )
* retval = tmp ;
kfree ( obj ) ;
if ( tmp = = ASUSWMI_UNSUPPORTED_METHOD )
return - ENODEV ;
return 0 ;
# else
return - EOPNOTSUPP ;
# endif
}
static inline int nct6775_asuswmi_write ( u8 bank , u8 reg , u8 val )
{
return nct6775_asuswmi_evaluate_method ( ASUSWMI_METHODID_WHWM , bank ,
reg , val , NULL ) ;
}
static inline int nct6775_asuswmi_read ( u8 bank , u8 reg , u8 * val )
{
u32 ret , tmp = 0 ;
ret = nct6775_asuswmi_evaluate_method ( ASUSWMI_METHODID_RHWM , bank ,
reg , 0 , & tmp ) ;
* val = tmp ;
return ret ;
}
static int superio_wmi_inb ( struct nct6775_sio_data * sio_data , int reg )
{
int tmp = 0 ;
nct6775_asuswmi_evaluate_method ( ASUSWMI_METHODID_RSIO , sio_data - > ld ,
reg , 0 , & tmp ) ;
return tmp ;
}
static void superio_wmi_outb ( struct nct6775_sio_data * sio_data , int reg , int val )
{
nct6775_asuswmi_evaluate_method ( ASUSWMI_METHODID_WSIO , sio_data - > ld ,
reg , val , NULL ) ;
}
static void superio_wmi_select ( struct nct6775_sio_data * sio_data , int ld )
{
sio_data - > ld = ld ;
}
static int superio_wmi_enter ( struct nct6775_sio_data * sio_data )
{
return 0 ;
}
static void superio_wmi_exit ( struct nct6775_sio_data * sio_data )
{
}
static void superio_outb ( struct nct6775_sio_data * sio_data , int reg , int val )
{
int ioreg = sio_data - > sioreg ;
outb ( reg , ioreg ) ;
outb ( val , ioreg + 1 ) ;
}
static int superio_inb ( struct nct6775_sio_data * sio_data , int reg )
{
int ioreg = sio_data - > sioreg ;
outb ( reg , ioreg ) ;
return inb ( ioreg + 1 ) ;
}
static void superio_select ( struct nct6775_sio_data * sio_data , int ld )
{
int ioreg = sio_data - > sioreg ;
outb ( SIO_REG_LDSEL , ioreg ) ;
outb ( ld , ioreg + 1 ) ;
}
static int superio_enter ( struct nct6775_sio_data * sio_data )
{
int ioreg = sio_data - > sioreg ;
/*
* 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 void superio_exit ( struct nct6775_sio_data * sio_data )
{
int ioreg = sio_data - > sioreg ;
outb ( 0xaa , ioreg ) ;
outb ( 0x02 , ioreg ) ;
outb ( 0x02 , ioreg + 1 ) ;
release_region ( ioreg , 2 ) ;
}
static inline void nct6775_wmi_set_bank ( struct nct6775_data * data , u16 reg )
{
u8 bank = reg > > 8 ;
data - > bank = bank ;
}
static int nct6775_wmi_reg_read ( void * ctx , unsigned int reg , unsigned int * val )
{
struct nct6775_data * data = ctx ;
int err , word_sized = nct6775_reg_is_word_sized ( data , reg ) ;
u8 tmp = 0 ;
u16 res ;
nct6775_wmi_set_bank ( data , reg ) ;
err = nct6775_asuswmi_read ( data - > bank , reg & 0xff , & tmp ) ;
if ( err )
return err ;
res = tmp ;
if ( word_sized ) {
err = nct6775_asuswmi_read ( data - > bank , ( reg & 0xff ) + 1 , & tmp ) ;
if ( err )
return err ;
res = ( res < < 8 ) + tmp ;
}
* val = res ;
return 0 ;
}
static int nct6775_wmi_reg_write ( void * ctx , unsigned int reg , unsigned int value )
{
struct nct6775_data * data = ctx ;
int res , word_sized = nct6775_reg_is_word_sized ( data , reg ) ;
nct6775_wmi_set_bank ( data , reg ) ;
if ( word_sized ) {
res = nct6775_asuswmi_write ( data - > bank , reg & 0xff , value > > 8 ) ;
if ( res )
return res ;
res = nct6775_asuswmi_write ( data - > bank , ( reg & 0xff ) + 1 , value ) ;
} else {
res = nct6775_asuswmi_write ( data - > bank , reg & 0xff , value ) ;
}
return res ;
}
/*
* On older chips , only registers 0x50 - 0x5f are banked .
* On more recent chips , all registers are banked .
* Assume that is the case and set the bank number for each access .
* Cache the bank number so it only needs to be set if it changes .
*/
static inline void nct6775_set_bank ( struct nct6775_data * data , u16 reg )
{
u8 bank = reg > > 8 ;
if ( data - > bank ! = bank ) {
outb_p ( NCT6775_REG_BANK , data - > addr + ADDR_REG_OFFSET ) ;
outb_p ( bank , data - > addr + DATA_REG_OFFSET ) ;
data - > bank = bank ;
}
}
static int nct6775_reg_read ( void * ctx , unsigned int reg , unsigned int * val )
{
struct nct6775_data * data = ctx ;
int word_sized = nct6775_reg_is_word_sized ( data , reg ) ;
nct6775_set_bank ( data , reg ) ;
outb_p ( reg & 0xff , data - > addr + ADDR_REG_OFFSET ) ;
* val = inb_p ( data - > addr + DATA_REG_OFFSET ) ;
if ( word_sized ) {
outb_p ( ( reg & 0xff ) + 1 ,
data - > addr + ADDR_REG_OFFSET ) ;
* val = ( * val < < 8 ) + inb_p ( data - > addr + DATA_REG_OFFSET ) ;
}
return 0 ;
}
static int nct6775_reg_write ( void * ctx , unsigned int reg , unsigned int value )
{
struct nct6775_data * data = ctx ;
int word_sized = nct6775_reg_is_word_sized ( data , reg ) ;
nct6775_set_bank ( data , reg ) ;
outb_p ( reg & 0xff , data - > addr + ADDR_REG_OFFSET ) ;
if ( word_sized ) {
outb_p ( value > > 8 , data - > addr + DATA_REG_OFFSET ) ;
outb_p ( ( reg & 0xff ) + 1 ,
data - > addr + ADDR_REG_OFFSET ) ;
}
outb_p ( value & 0xff , data - > addr + DATA_REG_OFFSET ) ;
return 0 ;
}
static void nct6791_enable_io_mapping ( struct nct6775_sio_data * sio_data )
{
int val ;
val = sio_data - > sio_inb ( sio_data , NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE ) ;
if ( val & 0x10 ) {
pr_info ( " Enabling hardware monitor logical device mappings. \n " ) ;
sio_data - > sio_outb ( sio_data , NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE ,
val & ~ 0x10 ) ;
}
}
static int __maybe_unused nct6775_suspend ( struct device * dev )
{
int err ;
u16 tmp ;
2022-08-10 08:26:46 +03:00
struct nct6775_data * data = nct6775_update_device ( dev ) ;
2022-04-27 04:01:53 +03:00
if ( IS_ERR ( data ) )
return PTR_ERR ( data ) ;
mutex_lock ( & data - > update_lock ) ;
err = nct6775_read_value ( data , data - > REG_VBAT , & tmp ) ;
if ( err )
goto out ;
data - > vbat = tmp ;
if ( data - > kind = = nct6775 ) {
err = nct6775_read_value ( data , NCT6775_REG_FANDIV1 , & tmp ) ;
if ( err )
goto out ;
data - > fandiv1 = tmp ;
err = nct6775_read_value ( data , NCT6775_REG_FANDIV2 , & tmp ) ;
if ( err )
goto out ;
data - > fandiv2 = tmp ;
}
out :
mutex_unlock ( & data - > update_lock ) ;
return err ;
}
static int __maybe_unused nct6775_resume ( struct device * dev )
{
struct nct6775_data * data = dev_get_drvdata ( dev ) ;
struct nct6775_sio_data * sio_data = dev_get_platdata ( dev ) ;
int i , j , err = 0 ;
u8 reg ;
mutex_lock ( & data - > update_lock ) ;
data - > bank = 0xff ; /* Force initial bank selection */
err = sio_data - > sio_enter ( sio_data ) ;
if ( err )
goto abort ;
sio_data - > sio_select ( sio_data , NCT6775_LD_HWM ) ;
reg = sio_data - > sio_inb ( sio_data , SIO_REG_ENABLE ) ;
if ( reg ! = data - > sio_reg_enable )
sio_data - > sio_outb ( sio_data , SIO_REG_ENABLE , data - > sio_reg_enable ) ;
if ( data - > kind = = nct6791 | | data - > kind = = nct6792 | |
data - > kind = = nct6793 | | data - > kind = = nct6795 | |
data - > kind = = nct6796 | | data - > kind = = nct6797 | |
data - > kind = = nct6798 )
nct6791_enable_io_mapping ( sio_data ) ;
sio_data - > sio_exit ( sio_data ) ;
/* Restore limits */
for ( i = 0 ; i < data - > in_num ; i + + ) {
if ( ! ( data - > have_in & BIT ( i ) ) )
continue ;
err = nct6775_write_value ( data , data - > REG_IN_MINMAX [ 0 ] [ i ] , data - > in [ i ] [ 1 ] ) ;
if ( err )
goto abort ;
err = nct6775_write_value ( data , data - > REG_IN_MINMAX [ 1 ] [ i ] , data - > in [ i ] [ 2 ] ) ;
if ( err )
goto abort ;
}
for ( i = 0 ; i < ARRAY_SIZE ( data - > fan_min ) ; i + + ) {
if ( ! ( data - > has_fan_min & BIT ( i ) ) )
continue ;
err = nct6775_write_value ( data , data - > REG_FAN_MIN [ i ] , data - > fan_min [ i ] ) ;
if ( err )
goto abort ;
}
for ( i = 0 ; i < NUM_TEMP ; i + + ) {
if ( ! ( data - > have_temp & BIT ( i ) ) )
continue ;
for ( j = 1 ; j < ARRAY_SIZE ( data - > reg_temp ) ; j + + )
if ( data - > reg_temp [ j ] [ i ] ) {
err = nct6775_write_temp ( data , data - > reg_temp [ j ] [ i ] ,
data - > temp [ j ] [ i ] ) ;
if ( err )
goto abort ;
}
}
/* Restore other settings */
err = nct6775_write_value ( data , data - > REG_VBAT , data - > vbat ) ;
if ( err )
goto abort ;
if ( data - > kind = = nct6775 ) {
err = nct6775_write_value ( data , NCT6775_REG_FANDIV1 , data - > fandiv1 ) ;
if ( err )
goto abort ;
err = nct6775_write_value ( data , NCT6775_REG_FANDIV2 , data - > fandiv2 ) ;
}
abort :
/* Force re-reading all values */
data - > valid = false ;
mutex_unlock ( & data - > update_lock ) ;
return err ;
}
static SIMPLE_DEV_PM_OPS ( nct6775_dev_pm_ops , nct6775_suspend , nct6775_resume ) ;
static void
nct6775_check_fan_inputs ( struct nct6775_data * data , struct nct6775_sio_data * sio_data )
{
bool fan3pin = false , fan4pin = false , fan4min = false ;
bool fan5pin = false , fan6pin = false , fan7pin = false ;
bool pwm3pin = false , pwm4pin = false , pwm5pin = false ;
bool pwm6pin = false , pwm7pin = false ;
/* Store SIO_REG_ENABLE for use during resume */
sio_data - > sio_select ( sio_data , NCT6775_LD_HWM ) ;
data - > sio_reg_enable = sio_data - > sio_inb ( sio_data , SIO_REG_ENABLE ) ;
/* fan4 and fan5 share some pins with the GPIO and serial flash */
if ( data - > kind = = nct6775 ) {
int cr2c = sio_data - > sio_inb ( sio_data , 0x2c ) ;
fan3pin = cr2c & BIT ( 6 ) ;
pwm3pin = cr2c & BIT ( 7 ) ;
/* On NCT6775, fan4 shares pins with the fdc interface */
fan4pin = ! ( sio_data - > sio_inb ( sio_data , 0x2A ) & 0x80 ) ;
} else if ( data - > kind = = nct6776 ) {
bool gpok = sio_data - > sio_inb ( sio_data , 0x27 ) & 0x80 ;
const char * board_vendor , * board_name ;
board_vendor = dmi_get_system_info ( DMI_BOARD_VENDOR ) ;
board_name = dmi_get_system_info ( DMI_BOARD_NAME ) ;
if ( board_name & & board_vendor & &
! strcmp ( board_vendor , " ASRock " ) ) {
/*
* Auxiliary fan monitoring is not enabled on ASRock
* Z77 Pro4 - M if booted in UEFI Ultra - FastBoot mode .
* Observed with BIOS version 2.00 .
*/
if ( ! strcmp ( board_name , " Z77 Pro4-M " ) ) {
if ( ( data - > sio_reg_enable & 0xe0 ) ! = 0xe0 ) {
data - > sio_reg_enable | = 0xe0 ;
sio_data - > sio_outb ( sio_data , SIO_REG_ENABLE ,
data - > sio_reg_enable ) ;
}
}
}
if ( data - > sio_reg_enable & 0x80 )
fan3pin = gpok ;
else
fan3pin = ! ( sio_data - > sio_inb ( sio_data , 0x24 ) & 0x40 ) ;
if ( data - > sio_reg_enable & 0x40 )
fan4pin = gpok ;
else
fan4pin = sio_data - > sio_inb ( sio_data , 0x1C ) & 0x01 ;
if ( data - > sio_reg_enable & 0x20 )
fan5pin = gpok ;
else
fan5pin = sio_data - > sio_inb ( sio_data , 0x1C ) & 0x02 ;
fan4min = fan4pin ;
pwm3pin = fan3pin ;
} else if ( data - > kind = = nct6106 ) {
int cr24 = sio_data - > sio_inb ( sio_data , 0x24 ) ;
fan3pin = ! ( cr24 & 0x80 ) ;
pwm3pin = cr24 & 0x08 ;
} else if ( data - > kind = = nct6116 ) {
int cr1a = sio_data - > sio_inb ( sio_data , 0x1a ) ;
int cr1b = sio_data - > sio_inb ( sio_data , 0x1b ) ;
int cr24 = sio_data - > sio_inb ( sio_data , 0x24 ) ;
int cr2a = sio_data - > sio_inb ( sio_data , 0x2a ) ;
int cr2b = sio_data - > sio_inb ( sio_data , 0x2b ) ;
int cr2f = sio_data - > sio_inb ( sio_data , 0x2f ) ;
fan3pin = ! ( cr2b & 0x10 ) ;
fan4pin = ( cr2b & 0x80 ) | | // pin 1(2)
( ! ( cr2f & 0x10 ) & & ( cr1a & 0x04 ) ) ; // pin 65(66)
fan5pin = ( cr2b & 0x80 ) | | // pin 126(127)
( ! ( cr1b & 0x03 ) & & ( cr2a & 0x02 ) ) ; // pin 94(96)
pwm3pin = fan3pin & & ( cr24 & 0x08 ) ;
pwm4pin = fan4pin ;
pwm5pin = fan5pin ;
} else {
/*
* NCT6779D , NCT6791D , NCT6792D , NCT6793D , NCT6795D , NCT6796D ,
* NCT6797D , NCT6798D
*/
int cr1a = sio_data - > sio_inb ( sio_data , 0x1a ) ;
int cr1b = sio_data - > sio_inb ( sio_data , 0x1b ) ;
int cr1c = sio_data - > sio_inb ( sio_data , 0x1c ) ;
int cr1d = sio_data - > sio_inb ( sio_data , 0x1d ) ;
int cr2a = sio_data - > sio_inb ( sio_data , 0x2a ) ;
int cr2b = sio_data - > sio_inb ( sio_data , 0x2b ) ;
int cr2d = sio_data - > sio_inb ( sio_data , 0x2d ) ;
int cr2f = sio_data - > sio_inb ( sio_data , 0x2f ) ;
bool dsw_en = cr2f & BIT ( 3 ) ;
bool ddr4_en = cr2f & BIT ( 4 ) ;
int cre0 ;
int creb ;
int cred ;
sio_data - > sio_select ( sio_data , NCT6775_LD_12 ) ;
cre0 = sio_data - > sio_inb ( sio_data , 0xe0 ) ;
creb = sio_data - > sio_inb ( sio_data , 0xeb ) ;
cred = sio_data - > sio_inb ( sio_data , 0xed ) ;
fan3pin = ! ( cr1c & BIT ( 5 ) ) ;
fan4pin = ! ( cr1c & BIT ( 6 ) ) ;
fan5pin = ! ( cr1c & BIT ( 7 ) ) ;
pwm3pin = ! ( cr1c & BIT ( 0 ) ) ;
pwm4pin = ! ( cr1c & BIT ( 1 ) ) ;
pwm5pin = ! ( cr1c & BIT ( 2 ) ) ;
switch ( data - > kind ) {
case nct6791 :
fan6pin = cr2d & BIT ( 1 ) ;
pwm6pin = cr2d & BIT ( 0 ) ;
break ;
case nct6792 :
fan6pin = ! dsw_en & & ( cr2d & BIT ( 1 ) ) ;
pwm6pin = ! dsw_en & & ( cr2d & BIT ( 0 ) ) ;
break ;
case nct6793 :
fan5pin | = cr1b & BIT ( 5 ) ;
fan5pin | = creb & BIT ( 5 ) ;
fan6pin = ! dsw_en & & ( cr2d & BIT ( 1 ) ) ;
fan6pin | = creb & BIT ( 3 ) ;
pwm5pin | = cr2d & BIT ( 7 ) ;
pwm5pin | = ( creb & BIT ( 4 ) ) & & ! ( cr2a & BIT ( 0 ) ) ;
pwm6pin = ! dsw_en & & ( cr2d & BIT ( 0 ) ) ;
pwm6pin | = creb & BIT ( 2 ) ;
break ;
case nct6795 :
fan5pin | = cr1b & BIT ( 5 ) ;
fan5pin | = creb & BIT ( 5 ) ;
fan6pin = ( cr2a & BIT ( 4 ) ) & &
( ! dsw_en | | ( cred & BIT ( 4 ) ) ) ;
fan6pin | = creb & BIT ( 3 ) ;
pwm5pin | = cr2d & BIT ( 7 ) ;
pwm5pin | = ( creb & BIT ( 4 ) ) & & ! ( cr2a & BIT ( 0 ) ) ;
pwm6pin = ( cr2a & BIT ( 3 ) ) & & ( cred & BIT ( 2 ) ) ;
pwm6pin | = creb & BIT ( 2 ) ;
break ;
case nct6796 :
fan5pin | = cr1b & BIT ( 5 ) ;
fan5pin | = ( cre0 & BIT ( 3 ) ) & & ! ( cr1b & BIT ( 0 ) ) ;
fan5pin | = creb & BIT ( 5 ) ;
fan6pin = ( cr2a & BIT ( 4 ) ) & &
( ! dsw_en | | ( cred & BIT ( 4 ) ) ) ;
fan6pin | = creb & BIT ( 3 ) ;
fan7pin = ! ( cr2b & BIT ( 2 ) ) ;
pwm5pin | = cr2d & BIT ( 7 ) ;
pwm5pin | = ( cre0 & BIT ( 4 ) ) & & ! ( cr1b & BIT ( 0 ) ) ;
pwm5pin | = ( creb & BIT ( 4 ) ) & & ! ( cr2a & BIT ( 0 ) ) ;
pwm6pin = ( cr2a & BIT ( 3 ) ) & & ( cred & BIT ( 2 ) ) ;
pwm6pin | = creb & BIT ( 2 ) ;
pwm7pin = ! ( cr1d & ( BIT ( 2 ) | BIT ( 3 ) ) ) ;
break ;
case nct6797 :
fan5pin | = ! ddr4_en & & ( cr1b & BIT ( 5 ) ) ;
fan5pin | = creb & BIT ( 5 ) ;
fan6pin = cr2a & BIT ( 4 ) ;
fan6pin | = creb & BIT ( 3 ) ;
fan7pin = cr1a & BIT ( 1 ) ;
pwm5pin | = ( creb & BIT ( 4 ) ) & & ! ( cr2a & BIT ( 0 ) ) ;
pwm5pin | = ! ddr4_en & & ( cr2d & BIT ( 7 ) ) ;
pwm6pin = creb & BIT ( 2 ) ;
pwm6pin | = cred & BIT ( 2 ) ;
pwm7pin = cr1d & BIT ( 4 ) ;
break ;
case nct6798 :
fan6pin = ! ( cr1b & BIT ( 0 ) ) & & ( cre0 & BIT ( 3 ) ) ;
fan6pin | = cr2a & BIT ( 4 ) ;
fan6pin | = creb & BIT ( 5 ) ;
fan7pin = cr1b & BIT ( 5 ) ;
fan7pin | = ! ( cr2b & BIT ( 2 ) ) ;
fan7pin | = creb & BIT ( 3 ) ;
pwm6pin = ! ( cr1b & BIT ( 0 ) ) & & ( cre0 & BIT ( 4 ) ) ;
pwm6pin | = ! ( cred & BIT ( 2 ) ) & & ( cr2a & BIT ( 3 ) ) ;
pwm6pin | = ( creb & BIT ( 4 ) ) & & ! ( cr2a & BIT ( 0 ) ) ;
pwm7pin = ! ( cr1d & ( BIT ( 2 ) | BIT ( 3 ) ) ) ;
pwm7pin | = cr2d & BIT ( 7 ) ;
pwm7pin | = creb & BIT ( 2 ) ;
break ;
default : /* NCT6779D */
break ;
}
fan4min = fan4pin ;
}
/* fan 1 and 2 (0x03) are always present */
data - > has_fan = 0x03 | ( fan3pin < < 2 ) | ( fan4pin < < 3 ) |
( fan5pin < < 4 ) | ( fan6pin < < 5 ) | ( fan7pin < < 6 ) ;
data - > has_fan_min = 0x03 | ( fan3pin < < 2 ) | ( fan4min < < 3 ) |
( fan5pin < < 4 ) | ( fan6pin < < 5 ) | ( fan7pin < < 6 ) ;
data - > has_pwm = 0x03 | ( pwm3pin < < 2 ) | ( pwm4pin < < 3 ) |
( pwm5pin < < 4 ) | ( pwm6pin < < 5 ) | ( pwm7pin < < 6 ) ;
}
static ssize_t
cpu0_vid_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct nct6775_data * data = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %d \n " , vid_from_reg ( data - > vid , data - > vrm ) ) ;
}
static DEVICE_ATTR_RO ( cpu0_vid ) ;
/* Case open detection */
static const u8 NCT6775_REG_CR_CASEOPEN_CLR [ ] = { 0xe6 , 0xee } ;
static const u8 NCT6775_CR_CASEOPEN_CLR_MASK [ ] = { 0x20 , 0x01 } ;
static ssize_t
clear_caseopen ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct nct6775_data * data = dev_get_drvdata ( dev ) ;
struct nct6775_sio_data * sio_data = data - > driver_data ;
int nr = to_sensor_dev_attr ( attr ) - > index - INTRUSION_ALARM_BASE ;
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 .
* The CR registers are the same for all chips , and not all chips
* support clearing the caseopen status through " regular " registers .
*/
ret = sio_data - > sio_enter ( sio_data ) ;
if ( ret ) {
count = ret ;
goto error ;
}
sio_data - > sio_select ( sio_data , NCT6775_LD_ACPI ) ;
reg = sio_data - > sio_inb ( sio_data , NCT6775_REG_CR_CASEOPEN_CLR [ nr ] ) ;
reg | = NCT6775_CR_CASEOPEN_CLR_MASK [ nr ] ;
sio_data - > sio_outb ( sio_data , NCT6775_REG_CR_CASEOPEN_CLR [ nr ] , reg ) ;
reg & = ~ NCT6775_CR_CASEOPEN_CLR_MASK [ nr ] ;
sio_data - > sio_outb ( sio_data , NCT6775_REG_CR_CASEOPEN_CLR [ nr ] , reg ) ;
sio_data - > sio_exit ( sio_data ) ;
data - > valid = false ; /* Force cache refresh */
error :
mutex_unlock ( & data - > update_lock ) ;
return count ;
}
static SENSOR_DEVICE_ATTR ( intrusion0_alarm , 0644 , nct6775_show_alarm ,
clear_caseopen , INTRUSION_ALARM_BASE ) ;
static SENSOR_DEVICE_ATTR ( intrusion1_alarm , 0644 , nct6775_show_alarm ,
clear_caseopen , INTRUSION_ALARM_BASE + 1 ) ;
static SENSOR_DEVICE_ATTR ( intrusion0_beep , 0644 , nct6775_show_beep ,
nct6775_store_beep , INTRUSION_ALARM_BASE ) ;
static SENSOR_DEVICE_ATTR ( intrusion1_beep , 0644 , nct6775_show_beep ,
nct6775_store_beep , INTRUSION_ALARM_BASE + 1 ) ;
static SENSOR_DEVICE_ATTR ( beep_enable , 0644 , nct6775_show_beep ,
nct6775_store_beep , BEEP_ENABLE_BASE ) ;
static umode_t nct6775_other_is_visible ( struct kobject * kobj ,
struct attribute * attr , int index )
{
struct device * dev = kobj_to_dev ( kobj ) ;
struct nct6775_data * data = dev_get_drvdata ( dev ) ;
if ( index = = 0 & & ! data - > have_vid )
return 0 ;
if ( index = = 1 | | index = = 2 ) {
if ( data - > ALARM_BITS [ INTRUSION_ALARM_BASE + index - 1 ] < 0 )
return 0 ;
}
if ( index = = 3 | | index = = 4 ) {
if ( data - > BEEP_BITS [ INTRUSION_ALARM_BASE + index - 3 ] < 0 )
return 0 ;
}
return nct6775_attr_mode ( data , attr ) ;
}
/*
* nct6775_other_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 attribute * nct6775_attributes_other [ ] = {
& dev_attr_cpu0_vid . attr , /* 0 */
& sensor_dev_attr_intrusion0_alarm . dev_attr . attr , /* 1 */
& sensor_dev_attr_intrusion1_alarm . dev_attr . attr , /* 2 */
& sensor_dev_attr_intrusion0_beep . dev_attr . attr , /* 3 */
& sensor_dev_attr_intrusion1_beep . dev_attr . attr , /* 4 */
& sensor_dev_attr_beep_enable . dev_attr . attr , /* 5 */
NULL
} ;
static const struct attribute_group nct6775_group_other = {
. attrs = nct6775_attributes_other ,
. is_visible = nct6775_other_is_visible ,
} ;
static int nct6775_platform_probe_init ( struct nct6775_data * data )
{
int err ;
u8 cr2a ;
struct nct6775_sio_data * sio_data = data - > driver_data ;
err = sio_data - > sio_enter ( sio_data ) ;
if ( err )
return err ;
cr2a = sio_data - > sio_inb ( sio_data , 0x2a ) ;
switch ( data - > kind ) {
case nct6775 :
data - > have_vid = ( cr2a & 0x40 ) ;
break ;
case nct6776 :
data - > have_vid = ( cr2a & 0x60 ) = = 0x40 ;
break ;
case nct6106 :
case nct6116 :
case nct6779 :
case nct6791 :
case nct6792 :
case nct6793 :
case nct6795 :
case nct6796 :
case nct6797 :
case nct6798 :
break ;
}
/*
* Read VID value
* We can get the VID input values directly at logical device D 0xe3 .
*/
if ( data - > have_vid ) {
sio_data - > sio_select ( sio_data , NCT6775_LD_VID ) ;
data - > vid = sio_data - > sio_inb ( sio_data , 0xe3 ) ;
data - > vrm = vid_which_vrm ( ) ;
}
if ( fan_debounce ) {
u8 tmp ;
sio_data - > sio_select ( sio_data , NCT6775_LD_HWM ) ;
tmp = sio_data - > sio_inb ( sio_data ,
NCT6775_REG_CR_FAN_DEBOUNCE ) ;
switch ( data - > kind ) {
case nct6106 :
case nct6116 :
tmp | = 0xe0 ;
break ;
case nct6775 :
tmp | = 0x1e ;
break ;
case nct6776 :
case nct6779 :
tmp | = 0x3e ;
break ;
case nct6791 :
case nct6792 :
case nct6793 :
case nct6795 :
case nct6796 :
case nct6797 :
case nct6798 :
tmp | = 0x7e ;
break ;
}
sio_data - > sio_outb ( sio_data , NCT6775_REG_CR_FAN_DEBOUNCE ,
tmp ) ;
pr_info ( " Enabled fan debounce for chip %s \n " , data - > name ) ;
}
nct6775_check_fan_inputs ( data , sio_data ) ;
sio_data - > sio_exit ( sio_data ) ;
return nct6775_add_attr_group ( data , & nct6775_group_other ) ;
}
static const struct regmap_config nct6775_regmap_config = {
. reg_bits = 16 ,
. val_bits = 16 ,
. reg_read = nct6775_reg_read ,
. reg_write = nct6775_reg_write ,
} ;
static const struct regmap_config nct6775_wmi_regmap_config = {
. reg_bits = 16 ,
. val_bits = 16 ,
. reg_read = nct6775_wmi_reg_read ,
. reg_write = nct6775_wmi_reg_write ,
} ;
static int nct6775_platform_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct nct6775_sio_data * sio_data = dev_get_platdata ( dev ) ;
struct nct6775_data * data ;
struct resource * res ;
const struct regmap_config * regmapcfg ;
if ( sio_data - > access = = access_direct ) {
res = platform_get_resource ( pdev , IORESOURCE_IO , 0 ) ;
if ( ! devm_request_region ( & pdev - > dev , res - > start , IOREGION_LENGTH , DRVNAME ) )
return - EBUSY ;
}
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > kind = sio_data - > kind ;
data - > sioreg = sio_data - > sioreg ;
if ( sio_data - > access = = access_direct ) {
data - > addr = res - > start ;
regmapcfg = & nct6775_regmap_config ;
} else {
regmapcfg = & nct6775_wmi_regmap_config ;
}
platform_set_drvdata ( pdev , data ) ;
data - > driver_data = sio_data ;
data - > driver_init = nct6775_platform_probe_init ;
return nct6775_probe ( & pdev - > dev , data , regmapcfg ) ;
}
static struct platform_driver nct6775_driver = {
. driver = {
. name = DRVNAME ,
. pm = & nct6775_dev_pm_ops ,
} ,
. probe = nct6775_platform_probe ,
} ;
/* nct6775_find() looks for a '627 in the Super-I/O config space */
static int __init nct6775_find ( int sioaddr , struct nct6775_sio_data * sio_data )
{
u16 val ;
int err ;
int addr ;
sio_data - > access = access_direct ;
sio_data - > sioreg = sioaddr ;
err = sio_data - > sio_enter ( sio_data ) ;
if ( err )
return err ;
val = ( sio_data - > sio_inb ( sio_data , SIO_REG_DEVID ) < < 8 ) |
sio_data - > sio_inb ( sio_data , SIO_REG_DEVID + 1 ) ;
if ( force_id & & val ! = 0xffff )
val = force_id ;
switch ( val & SIO_ID_MASK ) {
case SIO_NCT6106_ID :
sio_data - > kind = nct6106 ;
break ;
case SIO_NCT6116_ID :
sio_data - > kind = nct6116 ;
break ;
case SIO_NCT6775_ID :
sio_data - > kind = nct6775 ;
break ;
case SIO_NCT6776_ID :
sio_data - > kind = nct6776 ;
break ;
case SIO_NCT6779_ID :
sio_data - > kind = nct6779 ;
break ;
case SIO_NCT6791_ID :
sio_data - > kind = nct6791 ;
break ;
case SIO_NCT6792_ID :
sio_data - > kind = nct6792 ;
break ;
case SIO_NCT6793_ID :
sio_data - > kind = nct6793 ;
break ;
case SIO_NCT6795_ID :
sio_data - > kind = nct6795 ;
break ;
case SIO_NCT6796_ID :
sio_data - > kind = nct6796 ;
break ;
case SIO_NCT6797_ID :
sio_data - > kind = nct6797 ;
break ;
case SIO_NCT6798_ID :
sio_data - > kind = nct6798 ;
break ;
default :
if ( val ! = 0xffff )
pr_debug ( " unsupported chip ID: 0x%04x \n " , val ) ;
sio_data - > sio_exit ( sio_data ) ;
return - ENODEV ;
}
/* We have a known chip, find the HWM I/O address */
sio_data - > sio_select ( sio_data , NCT6775_LD_HWM ) ;
val = ( sio_data - > sio_inb ( sio_data , SIO_REG_ADDR ) < < 8 )
| sio_data - > sio_inb ( sio_data , SIO_REG_ADDR + 1 ) ;
addr = val & IOREGION_ALIGNMENT ;
if ( addr = = 0 ) {
pr_err ( " Refusing to enable a Super-I/O device with a base I/O port 0 \n " ) ;
sio_data - > sio_exit ( sio_data ) ;
return - ENODEV ;
}
/* Activate logical device if needed */
val = sio_data - > sio_inb ( sio_data , SIO_REG_ENABLE ) ;
if ( ! ( val & 0x01 ) ) {
pr_warn ( " Forcibly enabling Super-I/O. Sensor is probably unusable. \n " ) ;
sio_data - > sio_outb ( sio_data , SIO_REG_ENABLE , val | 0x01 ) ;
}
if ( sio_data - > kind = = nct6791 | | sio_data - > kind = = nct6792 | |
sio_data - > kind = = nct6793 | | sio_data - > kind = = nct6795 | |
sio_data - > kind = = nct6796 | | sio_data - > kind = = nct6797 | |
sio_data - > kind = = nct6798 )
nct6791_enable_io_mapping ( sio_data ) ;
sio_data - > sio_exit ( sio_data ) ;
pr_info ( " Found %s or compatible chip at %#x:%#x \n " ,
nct6775_sio_names [ sio_data - > kind ] , sioaddr , addr ) ;
return addr ;
}
/*
* 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 nct6775 driver . But since we use platform_device_alloc ( ) , we
* must keep track of the device
*/
static struct platform_device * pdev [ 2 ] ;
static const char * const asus_wmi_boards [ ] = {
2022-05-07 10:29:33 +03:00
" PRO H410T " ,
2022-04-27 04:01:53 +03:00
" ProArt X570-CREATOR WIFI " ,
" Pro B550M-C " ,
" Pro WS X570-ACE " ,
" PRIME B360-PLUS " ,
" PRIME B460-PLUS " ,
" PRIME B550-PLUS " ,
" PRIME B550M-A " ,
" PRIME B550M-A (WI-FI) " ,
2022-05-07 10:29:33 +03:00
" PRIME H410M-R " ,
2022-04-27 04:01:53 +03:00
" PRIME X570-P " ,
" PRIME X570-PRO " ,
" ROG CROSSHAIR VIII DARK HERO " ,
" ROG CROSSHAIR VIII FORMULA " ,
" ROG CROSSHAIR VIII HERO " ,
" ROG CROSSHAIR VIII IMPACT " ,
" ROG STRIX B550-A GAMING " ,
" ROG STRIX B550-E GAMING " ,
" ROG STRIX B550-F GAMING " ,
" ROG STRIX B550-F GAMING (WI-FI) " ,
" ROG STRIX B550-F GAMING WIFI II " ,
" ROG STRIX B550-I GAMING " ,
" ROG STRIX B550-XE GAMING (WI-FI) " ,
" ROG STRIX X570-E GAMING " ,
2022-05-07 10:29:33 +03:00
" ROG STRIX X570-E GAMING WIFI II " ,
2022-04-27 04:01:53 +03:00
" ROG STRIX X570-F GAMING " ,
" ROG STRIX X570-I GAMING " ,
" ROG STRIX Z390-E GAMING " ,
" ROG STRIX Z390-F GAMING " ,
" ROG STRIX Z390-H GAMING " ,
" ROG STRIX Z390-I GAMING " ,
" ROG STRIX Z490-A GAMING " ,
" ROG STRIX Z490-E GAMING " ,
" ROG STRIX Z490-F GAMING " ,
" ROG STRIX Z490-G GAMING " ,
" ROG STRIX Z490-G GAMING (WI-FI) " ,
" ROG STRIX Z490-H GAMING " ,
" ROG STRIX Z490-I GAMING " ,
" TUF GAMING B550M-PLUS " ,
" TUF GAMING B550M-PLUS (WI-FI) " ,
" TUF GAMING B550-PLUS " ,
2022-07-27 00:16:17 +03:00
" TUF GAMING B550-PLUS WIFI II " ,
2022-04-27 04:01:53 +03:00
" TUF GAMING B550-PRO " ,
" TUF GAMING X570-PLUS " ,
" TUF GAMING X570-PLUS (WI-FI) " ,
" TUF GAMING X570-PRO (WI-FI) " ,
" TUF GAMING Z490-PLUS " ,
" TUF GAMING Z490-PLUS (WI-FI) " ,
} ;
static int __init sensors_nct6775_platform_init ( void )
{
int i , err ;
bool found = false ;
int address ;
struct resource res ;
struct nct6775_sio_data sio_data ;
int sioaddr [ 2 ] = { 0x2e , 0x4e } ;
enum sensor_access access = access_direct ;
const char * board_vendor , * board_name ;
u8 tmp ;
err = platform_driver_register ( & nct6775_driver ) ;
if ( err )
return err ;
board_vendor = dmi_get_system_info ( DMI_BOARD_VENDOR ) ;
board_name = dmi_get_system_info ( DMI_BOARD_NAME ) ;
if ( board_name & & board_vendor & &
! strcmp ( board_vendor , " ASUSTeK COMPUTER INC. " ) ) {
err = match_string ( asus_wmi_boards , ARRAY_SIZE ( asus_wmi_boards ) ,
board_name ) ;
if ( err > = 0 ) {
/* if reading chip id via WMI succeeds, use WMI */
if ( ! nct6775_asuswmi_read ( 0 , NCT6775_PORT_CHIPID , & tmp ) & & tmp ) {
pr_info ( " Using Asus WMI to access %#x chip. \n " , tmp ) ;
access = access_asuswmi ;
} else {
pr_err ( " Can't read ChipID by Asus WMI. \n " ) ;
}
}
}
/*
* 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
* nct6775 hardware monitor , and call probe ( )
*/
for ( i = 0 ; i < ARRAY_SIZE ( pdev ) ; i + + ) {
sio_data . sio_outb = superio_outb ;
sio_data . sio_inb = superio_inb ;
sio_data . sio_select = superio_select ;
sio_data . sio_enter = superio_enter ;
sio_data . sio_exit = superio_exit ;
address = nct6775_find ( sioaddr [ i ] , & sio_data ) ;
if ( address < = 0 )
continue ;
found = true ;
sio_data . access = access ;
if ( access = = access_asuswmi ) {
sio_data . sio_outb = superio_wmi_outb ;
sio_data . sio_inb = superio_wmi_inb ;
sio_data . sio_select = superio_wmi_select ;
sio_data . sio_enter = superio_wmi_enter ;
sio_data . sio_exit = superio_wmi_exit ;
}
pdev [ i ] = platform_device_alloc ( DRVNAME , address ) ;
if ( ! pdev [ i ] ) {
err = - ENOMEM ;
goto exit_device_unregister ;
}
err = platform_device_add_data ( pdev [ i ] , & sio_data ,
sizeof ( struct nct6775_sio_data ) ) ;
if ( err )
goto exit_device_put ;
if ( sio_data . access = = access_direct ) {
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 :
platform_device_put ( pdev [ i ] ) ;
exit_device_unregister :
2022-06-10 13:33:24 +03:00
while ( i - - )
platform_device_unregister ( pdev [ i ] ) ;
2022-04-27 04:01:53 +03:00
exit_unregister :
platform_driver_unregister ( & nct6775_driver ) ;
return err ;
}
static void __exit sensors_nct6775_platform_exit ( void )
{
int i ;
2022-06-10 13:33:24 +03:00
for ( i = 0 ; i < ARRAY_SIZE ( pdev ) ; i + + )
platform_device_unregister ( pdev [ i ] ) ;
2022-04-27 04:01:53 +03:00
platform_driver_unregister ( & nct6775_driver ) ;
}
MODULE_AUTHOR ( " Guenter Roeck <linux@roeck-us.net> " ) ;
MODULE_DESCRIPTION ( " Platform driver for NCT6775F and compatible chips " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_IMPORT_NS ( HWMON_NCT6775 ) ;
module_init ( sensors_nct6775_platform_init ) ;
module_exit ( sensors_nct6775_platform_exit ) ;