2007-10-19 00:22:43 +04:00
/*
* A hwmon driver for the Intel 5000 series chipset FB - DIMM AMB
* temperature sensors
* Copyright ( C ) 2007 IBM
*
* Author : Darrick J . Wong < djwong @ us . ibm . com >
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/module.h>
# include <linux/jiffies.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/err.h>
# include <linux/mutex.h>
# include <linux/delay.h>
# include <linux/log2.h>
# include <linux/pci.h>
# include <linux/platform_device.h>
# define DRVNAME "i5k_amb"
# define I5K_REG_AMB_BASE_ADDR 0x48
# define I5K_REG_AMB_LEN_ADDR 0x50
# define I5K_REG_CHAN0_PRESENCE_ADDR 0x64
# define I5K_REG_CHAN1_PRESENCE_ADDR 0x66
# define AMB_REG_TEMP_MIN_ADDR 0x80
# define AMB_REG_TEMP_MID_ADDR 0x81
# define AMB_REG_TEMP_MAX_ADDR 0x82
# define AMB_REG_TEMP_STATUS_ADDR 0x84
# define AMB_REG_TEMP_ADDR 0x85
# define AMB_CONFIG_SIZE 2048
# define AMB_FUNC_3_OFFSET 768
2007-10-26 22:55:11 +04:00
static unsigned long amb_reg_temp_status ( unsigned int amb )
{
return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_STATUS_ADDR +
AMB_CONFIG_SIZE * amb ;
}
static unsigned long amb_reg_temp_min ( unsigned int amb )
{
return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MIN_ADDR +
AMB_CONFIG_SIZE * amb ;
}
static unsigned long amb_reg_temp_mid ( unsigned int amb )
{
return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MID_ADDR +
AMB_CONFIG_SIZE * amb ;
}
static unsigned long amb_reg_temp_max ( unsigned int amb )
{
return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MAX_ADDR +
AMB_CONFIG_SIZE * amb ;
}
static unsigned long amb_reg_temp ( unsigned int amb )
{
return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_ADDR +
AMB_CONFIG_SIZE * amb ;
}
2007-10-19 00:22:43 +04:00
# define MAX_MEM_CHANNELS 4
# define MAX_AMBS_PER_CHANNEL 16
# define MAX_AMBS (MAX_MEM_CHANNELS * \
MAX_AMBS_PER_CHANNEL )
/*
* Ugly hack : For some reason the highest bit is set if there
* are _any_ DIMMs in the channel . Attempting to read from
* this " high-order " AMB results in a memory bus error , so
* for now we ' ll just ignore that top bit , even though that
* might prevent us from seeing the 16 th DIMM in the channel .
*/
# define REAL_MAX_AMBS_PER_CHANNEL 15
# define KNOBS_PER_AMB 5
2007-10-26 22:55:11 +04:00
static unsigned long amb_num_from_reg ( unsigned int byte_num , unsigned int bit )
{
return byte_num * MAX_AMBS_PER_CHANNEL + bit ;
}
2007-10-19 00:22:43 +04:00
# define AMB_SYSFS_NAME_LEN 16
struct i5k_device_attribute {
struct sensor_device_attribute s_attr ;
char name [ AMB_SYSFS_NAME_LEN ] ;
} ;
struct i5k_amb_data {
struct device * hwmon_dev ;
unsigned long amb_base ;
unsigned long amb_len ;
u16 amb_present [ MAX_MEM_CHANNELS ] ;
void __iomem * amb_mmio ;
struct i5k_device_attribute * attrs ;
unsigned int num_attrs ;
2008-05-24 00:04:25 +04:00
unsigned long chipset_id ;
2007-10-19 00:22:43 +04:00
} ;
static ssize_t show_name ( struct device * dev , struct device_attribute * devattr ,
char * buf )
{
return sprintf ( buf , " %s \n " , DRVNAME ) ;
}
static DEVICE_ATTR ( name , S_IRUGO , show_name , NULL ) ;
static struct platform_device * amb_pdev ;
static u8 amb_read_byte ( struct i5k_amb_data * data , unsigned long offset )
{
return ioread8 ( data - > amb_mmio + offset ) ;
}
static void amb_write_byte ( struct i5k_amb_data * data , unsigned long offset ,
u8 val )
{
iowrite8 ( val , data - > amb_mmio + offset ) ;
}
static ssize_t show_amb_alarm ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
2007-10-26 22:55:11 +04:00
if ( ! ( amb_read_byte ( data , amb_reg_temp_status ( attr - > index ) ) & 0x20 ) & &
( amb_read_byte ( data , amb_reg_temp_status ( attr - > index ) ) & 0x8 ) )
2007-10-19 00:22:43 +04:00
return sprintf ( buf , " 1 \n " ) ;
else
return sprintf ( buf , " 0 \n " ) ;
}
static ssize_t store_amb_min ( struct device * dev ,
struct device_attribute * devattr ,
const char * buf ,
size_t count )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
unsigned long temp = simple_strtoul ( buf , NULL , 10 ) / 500 ;
if ( temp > 255 )
temp = 255 ;
2007-10-26 22:55:11 +04:00
amb_write_byte ( data , amb_reg_temp_min ( attr - > index ) , temp ) ;
2007-10-19 00:22:43 +04:00
return count ;
}
static ssize_t store_amb_mid ( struct device * dev ,
struct device_attribute * devattr ,
const char * buf ,
size_t count )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
unsigned long temp = simple_strtoul ( buf , NULL , 10 ) / 500 ;
if ( temp > 255 )
temp = 255 ;
2007-10-26 22:55:11 +04:00
amb_write_byte ( data , amb_reg_temp_mid ( attr - > index ) , temp ) ;
2007-10-19 00:22:43 +04:00
return count ;
}
static ssize_t store_amb_max ( struct device * dev ,
struct device_attribute * devattr ,
const char * buf ,
size_t count )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
unsigned long temp = simple_strtoul ( buf , NULL , 10 ) / 500 ;
if ( temp > 255 )
temp = 255 ;
2007-10-26 22:55:11 +04:00
amb_write_byte ( data , amb_reg_temp_max ( attr - > index ) , temp ) ;
2007-10-19 00:22:43 +04:00
return count ;
}
static ssize_t show_amb_min ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %d \n " ,
2007-10-26 22:55:11 +04:00
500 * amb_read_byte ( data , amb_reg_temp_min ( attr - > index ) ) ) ;
2007-10-19 00:22:43 +04:00
}
static ssize_t show_amb_mid ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %d \n " ,
2007-10-26 22:55:11 +04:00
500 * amb_read_byte ( data , amb_reg_temp_mid ( attr - > index ) ) ) ;
2007-10-19 00:22:43 +04:00
}
static ssize_t show_amb_max ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %d \n " ,
2007-10-26 22:55:11 +04:00
500 * amb_read_byte ( data , amb_reg_temp_max ( attr - > index ) ) ) ;
2007-10-19 00:22:43 +04:00
}
static ssize_t show_amb_temp ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
struct i5k_amb_data * data = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %d \n " ,
2007-10-26 22:55:11 +04:00
500 * amb_read_byte ( data , amb_reg_temp ( attr - > index ) ) ) ;
2007-10-19 00:22:43 +04:00
}
static int __devinit i5k_amb_hwmon_init ( struct platform_device * pdev )
{
int i , j , k , d = 0 ;
u16 c ;
int res = 0 ;
int num_ambs = 0 ;
struct i5k_amb_data * data = platform_get_drvdata ( pdev ) ;
/* Count the number of AMBs found */
/* ignore the high-order bit, see "Ugly hack" comment above */
for ( i = 0 ; i < MAX_MEM_CHANNELS ; i + + )
num_ambs + = hweight16 ( data - > amb_present [ i ] & 0x7fff ) ;
/* Set up sysfs stuff */
data - > attrs = kzalloc ( sizeof ( * data - > attrs ) * num_ambs * KNOBS_PER_AMB ,
GFP_KERNEL ) ;
if ( ! data - > attrs )
return - ENOMEM ;
data - > num_attrs = 0 ;
for ( i = 0 ; i < MAX_MEM_CHANNELS ; i + + ) {
c = data - > amb_present [ i ] ;
for ( j = 0 ; j < REAL_MAX_AMBS_PER_CHANNEL ; j + + , c > > = 1 ) {
struct i5k_device_attribute * iattr ;
2007-10-26 22:55:11 +04:00
k = amb_num_from_reg ( i , j ) ;
2007-10-19 00:22:43 +04:00
if ( ! ( c & 0x1 ) )
continue ;
d + + ;
/* Temperature sysfs knob */
iattr = data - > attrs + data - > num_attrs ;
snprintf ( iattr - > name , AMB_SYSFS_NAME_LEN ,
" temp%d_input " , d ) ;
iattr - > s_attr . dev_attr . attr . name = iattr - > name ;
iattr - > s_attr . dev_attr . attr . mode = S_IRUGO ;
iattr - > s_attr . dev_attr . show = show_amb_temp ;
iattr - > s_attr . index = k ;
res = device_create_file ( & pdev - > dev ,
& iattr - > s_attr . dev_attr ) ;
if ( res )
goto exit_remove ;
data - > num_attrs + + ;
/* Temperature min sysfs knob */
iattr = data - > attrs + data - > num_attrs ;
snprintf ( iattr - > name , AMB_SYSFS_NAME_LEN ,
" temp%d_min " , d ) ;
iattr - > s_attr . dev_attr . attr . name = iattr - > name ;
iattr - > s_attr . dev_attr . attr . mode = S_IWUSR | S_IRUGO ;
iattr - > s_attr . dev_attr . show = show_amb_min ;
iattr - > s_attr . dev_attr . store = store_amb_min ;
iattr - > s_attr . index = k ;
res = device_create_file ( & pdev - > dev ,
& iattr - > s_attr . dev_attr ) ;
if ( res )
goto exit_remove ;
data - > num_attrs + + ;
/* Temperature mid sysfs knob */
iattr = data - > attrs + data - > num_attrs ;
snprintf ( iattr - > name , AMB_SYSFS_NAME_LEN ,
" temp%d_mid " , d ) ;
iattr - > s_attr . dev_attr . attr . name = iattr - > name ;
iattr - > s_attr . dev_attr . attr . mode = S_IWUSR | S_IRUGO ;
iattr - > s_attr . dev_attr . show = show_amb_mid ;
iattr - > s_attr . dev_attr . store = store_amb_mid ;
iattr - > s_attr . index = k ;
res = device_create_file ( & pdev - > dev ,
& iattr - > s_attr . dev_attr ) ;
if ( res )
goto exit_remove ;
data - > num_attrs + + ;
/* Temperature max sysfs knob */
iattr = data - > attrs + data - > num_attrs ;
snprintf ( iattr - > name , AMB_SYSFS_NAME_LEN ,
" temp%d_max " , d ) ;
iattr - > s_attr . dev_attr . attr . name = iattr - > name ;
iattr - > s_attr . dev_attr . attr . mode = S_IWUSR | S_IRUGO ;
iattr - > s_attr . dev_attr . show = show_amb_max ;
iattr - > s_attr . dev_attr . store = store_amb_max ;
iattr - > s_attr . index = k ;
res = device_create_file ( & pdev - > dev ,
& iattr - > s_attr . dev_attr ) ;
if ( res )
goto exit_remove ;
data - > num_attrs + + ;
/* Temperature alarm sysfs knob */
iattr = data - > attrs + data - > num_attrs ;
snprintf ( iattr - > name , AMB_SYSFS_NAME_LEN ,
" temp%d_alarm " , d ) ;
iattr - > s_attr . dev_attr . attr . name = iattr - > name ;
iattr - > s_attr . dev_attr . attr . mode = S_IRUGO ;
iattr - > s_attr . dev_attr . show = show_amb_alarm ;
iattr - > s_attr . index = k ;
res = device_create_file ( & pdev - > dev ,
& iattr - > s_attr . dev_attr ) ;
if ( res )
goto exit_remove ;
data - > num_attrs + + ;
}
}
res = device_create_file ( & pdev - > dev , & dev_attr_name ) ;
if ( res )
goto exit_remove ;
data - > hwmon_dev = hwmon_device_register ( & pdev - > dev ) ;
if ( IS_ERR ( data - > hwmon_dev ) ) {
res = PTR_ERR ( data - > hwmon_dev ) ;
goto exit_remove ;
}
return res ;
exit_remove :
device_remove_file ( & pdev - > dev , & dev_attr_name ) ;
for ( i = 0 ; i < data - > num_attrs ; i + + )
device_remove_file ( & pdev - > dev , & data - > attrs [ i ] . s_attr . dev_attr ) ;
kfree ( data - > attrs ) ;
return res ;
}
static int __devinit i5k_amb_add ( void )
{
int res = - ENODEV ;
/* only ever going to be one of these */
amb_pdev = platform_device_alloc ( DRVNAME , 0 ) ;
if ( ! amb_pdev )
return - ENOMEM ;
res = platform_device_add ( amb_pdev ) ;
if ( res )
goto err ;
return 0 ;
err :
platform_device_put ( amb_pdev ) ;
return res ;
}
2008-05-24 00:04:25 +04:00
static int __devinit i5k_find_amb_registers ( struct i5k_amb_data * data ,
unsigned long devid )
2007-10-19 00:22:43 +04:00
{
struct pci_dev * pcidev ;
u32 val32 ;
int res = - ENODEV ;
/* Find AMB register memory space */
pcidev = pci_get_device ( PCI_VENDOR_ID_INTEL ,
2008-05-24 00:04:25 +04:00
devid ,
2007-10-19 00:22:43 +04:00
NULL ) ;
if ( ! pcidev )
return - ENODEV ;
if ( pci_read_config_dword ( pcidev , I5K_REG_AMB_BASE_ADDR , & val32 ) )
goto out ;
data - > amb_base = val32 ;
if ( pci_read_config_dword ( pcidev , I5K_REG_AMB_LEN_ADDR , & val32 ) )
goto out ;
data - > amb_len = val32 ;
/* Is it big enough? */
if ( data - > amb_len < AMB_CONFIG_SIZE * MAX_AMBS ) {
dev_err ( & pcidev - > dev , " AMB region too small! \n " ) ;
goto out ;
}
2008-05-24 00:04:25 +04:00
data - > chipset_id = devid ;
2007-10-19 00:22:43 +04:00
res = 0 ;
out :
pci_dev_put ( pcidev ) ;
return res ;
}
static int __devinit i5k_channel_probe ( u16 * amb_present , unsigned long dev_id )
{
struct pci_dev * pcidev ;
u16 val16 ;
int res = - ENODEV ;
/* Copy the DIMM presence map for these two channels */
pcidev = pci_get_device ( PCI_VENDOR_ID_INTEL , dev_id , NULL ) ;
if ( ! pcidev )
return - ENODEV ;
if ( pci_read_config_word ( pcidev , I5K_REG_CHAN0_PRESENCE_ADDR , & val16 ) )
goto out ;
amb_present [ 0 ] = val16 ;
if ( pci_read_config_word ( pcidev , I5K_REG_CHAN1_PRESENCE_ADDR , & val16 ) )
goto out ;
amb_present [ 1 ] = val16 ;
res = 0 ;
out :
pci_dev_put ( pcidev ) ;
return res ;
}
2008-05-24 00:04:25 +04:00
static unsigned long i5k_channel_pci_id ( struct i5k_amb_data * data ,
unsigned long channel )
{
switch ( data - > chipset_id ) {
case PCI_DEVICE_ID_INTEL_5000_ERR :
return PCI_DEVICE_ID_INTEL_5000_FBD0 + channel ;
case PCI_DEVICE_ID_INTEL_5400_ERR :
return PCI_DEVICE_ID_INTEL_5400_FBD0 + channel ;
default :
BUG ( ) ;
}
}
static unsigned long chipset_ids [ ] = {
PCI_DEVICE_ID_INTEL_5000_ERR ,
PCI_DEVICE_ID_INTEL_5400_ERR ,
0
} ;
2007-10-19 00:22:43 +04:00
static int __devinit i5k_amb_probe ( struct platform_device * pdev )
{
struct i5k_amb_data * data ;
struct resource * reso ;
2008-05-24 00:04:25 +04:00
int i ;
2007-10-19 00:22:43 +04:00
int res = - ENODEV ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
/* Figure out where the AMB registers live */
2008-05-24 00:04:25 +04:00
i = 0 ;
do {
res = i5k_find_amb_registers ( data , chipset_ids [ i ] ) ;
i + + ;
} while ( res & & chipset_ids [ i ] ) ;
2007-10-19 00:22:43 +04:00
if ( res )
goto err ;
/* Copy the DIMM presence map for the first two channels */
res = i5k_channel_probe ( & data - > amb_present [ 0 ] ,
2008-05-24 00:04:25 +04:00
i5k_channel_pci_id ( data , 0 ) ) ;
2007-10-19 00:22:43 +04:00
if ( res )
goto err ;
/* Copy the DIMM presence map for the optional second two channels */
i5k_channel_probe ( & data - > amb_present [ 2 ] ,
2008-05-24 00:04:25 +04:00
i5k_channel_pci_id ( data , 1 ) ) ;
2007-10-19 00:22:43 +04:00
/* Set up resource regions */
reso = request_mem_region ( data - > amb_base , data - > amb_len , DRVNAME ) ;
if ( ! reso ) {
res = - EBUSY ;
goto err ;
}
data - > amb_mmio = ioremap_nocache ( data - > amb_base , data - > amb_len ) ;
if ( ! data - > amb_mmio ) {
res = - EBUSY ;
goto err_map_failed ;
}
platform_set_drvdata ( pdev , data ) ;
res = i5k_amb_hwmon_init ( pdev ) ;
if ( res )
goto err_init_failed ;
return res ;
err_init_failed :
iounmap ( data - > amb_mmio ) ;
platform_set_drvdata ( pdev , NULL ) ;
err_map_failed :
release_mem_region ( data - > amb_base , data - > amb_len ) ;
err :
kfree ( data ) ;
return res ;
}
static int __devexit i5k_amb_remove ( struct platform_device * pdev )
{
int i ;
struct i5k_amb_data * data = platform_get_drvdata ( pdev ) ;
hwmon_device_unregister ( data - > hwmon_dev ) ;
device_remove_file ( & pdev - > dev , & dev_attr_name ) ;
for ( i = 0 ; i < data - > num_attrs ; i + + )
device_remove_file ( & pdev - > dev , & data - > attrs [ i ] . s_attr . dev_attr ) ;
kfree ( data - > attrs ) ;
iounmap ( data - > amb_mmio ) ;
release_mem_region ( data - > amb_base , data - > amb_len ) ;
platform_set_drvdata ( pdev , NULL ) ;
kfree ( data ) ;
return 0 ;
}
static struct platform_driver i5k_amb_driver = {
. driver = {
. owner = THIS_MODULE ,
. name = DRVNAME ,
} ,
. probe = i5k_amb_probe ,
. remove = __devexit_p ( i5k_amb_remove ) ,
} ;
static int __init i5k_amb_init ( void )
{
int res ;
res = platform_driver_register ( & i5k_amb_driver ) ;
if ( res )
return res ;
res = i5k_amb_add ( ) ;
if ( res )
platform_driver_unregister ( & i5k_amb_driver ) ;
return res ;
}
static void __exit i5k_amb_exit ( void )
{
platform_device_unregister ( amb_pdev ) ;
platform_driver_unregister ( & i5k_amb_driver ) ;
}
MODULE_AUTHOR ( " Darrick J. Wong <djwong@us.ibm.com> " ) ;
MODULE_DESCRIPTION ( " Intel 5000 chipset FB-DIMM AMB temperature sensor " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( i5k_amb_init ) ;
module_exit ( i5k_amb_exit ) ;