2012-04-19 02:16:52 +04:00
/*
* Windfarm PowerMac thermal control . AD7417 sensors
*
* Copyright 2012 Benjamin Herrenschmidt , IBM Corp .
*
* Released under the term of the GNU GPL v2 .
*/
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/delay.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/wait.h>
# include <linux/i2c.h>
# include <asm/prom.h>
# include <asm/machdep.h>
# include <asm/io.h>
# include <asm/sections.h>
# include "windfarm.h"
# include "windfarm_mpu.h"
# define VERSION "1.0"
struct wf_ad7417_priv {
struct kref ref ;
struct i2c_client * i2c ;
u8 config ;
u8 cpu ;
const struct mpu_data * mpu ;
struct wf_sensor sensors [ 5 ] ;
struct mutex lock ;
} ;
static int wf_ad7417_temp_get ( struct wf_sensor * sr , s32 * value )
{
struct wf_ad7417_priv * pv = sr - > priv ;
u8 buf [ 2 ] ;
s16 raw ;
int rc ;
* value = 0 ;
mutex_lock ( & pv - > lock ) ;
/* Read temp register */
buf [ 0 ] = 0 ;
rc = i2c_master_send ( pv - > i2c , buf , 1 ) ;
if ( rc < 0 )
goto error ;
rc = i2c_master_recv ( pv - > i2c , buf , 2 ) ;
if ( rc < 0 )
goto error ;
/* Read a a 16-bit signed value */
raw = be16_to_cpup ( ( __le16 * ) buf ) ;
/* Convert 8.8-bit to 16.16 fixed point */
* value = ( ( s32 ) raw ) < < 8 ;
mutex_unlock ( & pv - > lock ) ;
return 0 ;
error :
mutex_unlock ( & pv - > lock ) ;
return - 1 ;
}
/*
* Scaling factors for the AD7417 ADC converters ( except
* for the CPU diode which is obtained from the EEPROM ) .
* Those values are obtained from the property list of
* the darwin driver
*/
# define ADC_12V_CURRENT_SCALE 0x0320 /* _AD2 */
# define ADC_CPU_VOLTAGE_SCALE 0x00a0 /* _AD3 */
# define ADC_CPU_CURRENT_SCALE 0x1f40 /* _AD4 */
static void wf_ad7417_adc_convert ( struct wf_ad7417_priv * pv ,
int chan , s32 raw , s32 * value )
{
switch ( chan ) {
case 1 : /* Diode */
* value = ( raw * ( s32 ) pv - > mpu - > mdiode +
( ( s32 ) pv - > mpu - > bdiode < < 12 ) ) > > 2 ;
break ;
case 2 : /* 12v current */
* value = raw * ADC_12V_CURRENT_SCALE ;
break ;
case 3 : /* core voltage */
* value = raw * ADC_CPU_VOLTAGE_SCALE ;
break ;
case 4 : /* core current */
* value = raw * ADC_CPU_CURRENT_SCALE ;
break ;
}
}
static int wf_ad7417_adc_get ( struct wf_sensor * sr , s32 * value )
{
struct wf_ad7417_priv * pv = sr - > priv ;
int chan = sr - pv - > sensors ;
int i , rc ;
u8 buf [ 2 ] ;
u16 raw ;
* value = 0 ;
mutex_lock ( & pv - > lock ) ;
for ( i = 0 ; i < 10 ; i + + ) {
/* Set channel */
buf [ 0 ] = 1 ;
buf [ 1 ] = ( pv - > config & 0x1f ) | ( chan < < 5 ) ;
rc = i2c_master_send ( pv - > i2c , buf , 2 ) ;
if ( rc < 0 )
goto error ;
/* Wait for conversion */
msleep ( 1 ) ;
/* Switch to data register */
buf [ 0 ] = 4 ;
rc = i2c_master_send ( pv - > i2c , buf , 1 ) ;
if ( rc < 0 )
goto error ;
/* Read result */
rc = i2c_master_recv ( pv - > i2c , buf , 2 ) ;
if ( rc < 0 )
goto error ;
/* Read a a 16-bit signed value */
raw = be16_to_cpup ( ( __le16 * ) buf ) > > 6 ;
wf_ad7417_adc_convert ( pv , chan , raw , value ) ;
dev_vdbg ( & pv - > i2c - > dev , " ADC chan %d [%s] "
" raw value: 0x%x, conv to: 0x%08x \n " ,
chan , sr - > name , raw , * value ) ;
mutex_unlock ( & pv - > lock ) ;
return 0 ;
error :
dev_dbg ( & pv - > i2c - > dev ,
" Error reading ADC, try %d... \n " , i ) ;
if ( i < 9 )
msleep ( 10 ) ;
}
mutex_unlock ( & pv - > lock ) ;
return - 1 ;
}
static void wf_ad7417_release ( struct kref * ref )
{
struct wf_ad7417_priv * pv = container_of ( ref ,
struct wf_ad7417_priv , ref ) ;
kfree ( pv ) ;
}
static void wf_ad7417_sensor_release ( struct wf_sensor * sr )
{
struct wf_ad7417_priv * pv = sr - > priv ;
kfree ( sr - > name ) ;
kref_put ( & pv - > ref , wf_ad7417_release ) ;
}
static const struct wf_sensor_ops wf_ad7417_temp_ops = {
. get_value = wf_ad7417_temp_get ,
. release = wf_ad7417_sensor_release ,
. owner = THIS_MODULE ,
} ;
static const struct wf_sensor_ops wf_ad7417_adc_ops = {
. get_value = wf_ad7417_adc_get ,
. release = wf_ad7417_sensor_release ,
. owner = THIS_MODULE ,
} ;
2012-12-22 03:03:50 +04:00
static void wf_ad7417_add_sensor ( struct wf_ad7417_priv * pv ,
int index , const char * name ,
const struct wf_sensor_ops * ops )
2012-04-19 02:16:52 +04:00
{
pv - > sensors [ index ] . name = kasprintf ( GFP_KERNEL , " %s-%d " , name , pv - > cpu ) ;
pv - > sensors [ index ] . priv = pv ;
pv - > sensors [ index ] . ops = ops ;
if ( ! wf_register_sensor ( & pv - > sensors [ index ] ) )
kref_get ( & pv - > ref ) ;
}
2012-12-22 03:03:50 +04:00
static void wf_ad7417_init_chip ( struct wf_ad7417_priv * pv )
2012-04-19 02:16:52 +04:00
{
int rc ;
u8 buf [ 2 ] ;
u8 config = 0 ;
/*
* Read ADC the configuration register and cache it . We
* also make sure Config2 contains proper values , I ' ve seen
* cases where we got stale grabage in there , thus preventing
* proper reading of conv . values
*/
/* Clear Config2 */
buf [ 0 ] = 5 ;
buf [ 1 ] = 0 ;
i2c_master_send ( pv - > i2c , buf , 2 ) ;
/* Read & cache Config1 */
buf [ 0 ] = 1 ;
rc = i2c_master_send ( pv - > i2c , buf , 1 ) ;
if ( rc > 0 ) {
rc = i2c_master_recv ( pv - > i2c , buf , 1 ) ;
if ( rc > 0 ) {
config = buf [ 0 ] ;
dev_dbg ( & pv - > i2c - > dev , " ADC config reg: %02x \n " ,
config ) ;
/* Disable shutdown mode */
config & = 0xfe ;
buf [ 0 ] = 1 ;
buf [ 1 ] = config ;
rc = i2c_master_send ( pv - > i2c , buf , 2 ) ;
}
}
if ( rc < = 0 )
dev_err ( & pv - > i2c - > dev , " Error reading ADC config \n " ) ;
pv - > config = config ;
}
2012-12-22 03:03:50 +04:00
static int wf_ad7417_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
2012-04-19 02:16:52 +04:00
{
struct wf_ad7417_priv * pv ;
const struct mpu_data * mpu ;
const char * loc ;
int cpu_nr ;
loc = of_get_property ( client - > dev . of_node , " hwsensor-location " , NULL ) ;
if ( ! loc ) {
dev_warn ( & client - > dev , " Missing hwsensor-location property! \n " ) ;
return - ENXIO ;
}
/*
* Identify which CPU we belong to by looking at the first entry
* in the hwsensor - location list
*/
if ( ! strncmp ( loc , " CPU A " , 5 ) )
cpu_nr = 0 ;
else if ( ! strncmp ( loc , " CPU B " , 5 ) )
cpu_nr = 1 ;
else {
pr_err ( " wf_ad7417: Can't identify location %s \n " , loc ) ;
return - ENXIO ;
}
mpu = wf_get_mpu ( cpu_nr ) ;
if ( ! mpu ) {
dev_err ( & client - > dev , " Failed to retrieve MPU data \n " ) ;
return - ENXIO ;
}
pv = kzalloc ( sizeof ( struct wf_ad7417_priv ) , GFP_KERNEL ) ;
if ( pv = = NULL )
return - ENODEV ;
kref_init ( & pv - > ref ) ;
mutex_init ( & pv - > lock ) ;
pv - > i2c = client ;
pv - > cpu = cpu_nr ;
pv - > mpu = mpu ;
dev_set_drvdata ( & client - > dev , pv ) ;
/* Initialize the chip */
wf_ad7417_init_chip ( pv ) ;
/*
* We cannot rely on Apple device - tree giving us child
* node with the names of the individual sensors so we
* just hard code what we know about them
*/
wf_ad7417_add_sensor ( pv , 0 , " cpu-amb-temp " , & wf_ad7417_temp_ops ) ;
wf_ad7417_add_sensor ( pv , 1 , " cpu-diode-temp " , & wf_ad7417_adc_ops ) ;
wf_ad7417_add_sensor ( pv , 2 , " cpu-12v-current " , & wf_ad7417_adc_ops ) ;
wf_ad7417_add_sensor ( pv , 3 , " cpu-voltage " , & wf_ad7417_adc_ops ) ;
wf_ad7417_add_sensor ( pv , 4 , " cpu-current " , & wf_ad7417_adc_ops ) ;
return 0 ;
}
2012-12-22 03:03:50 +04:00
static int wf_ad7417_remove ( struct i2c_client * client )
2012-04-19 02:16:52 +04:00
{
struct wf_ad7417_priv * pv = dev_get_drvdata ( & client - > dev ) ;
int i ;
/* Mark client detached */
pv - > i2c = NULL ;
/* Release sensor */
for ( i = 0 ; i < 5 ; i + + )
wf_unregister_sensor ( & pv - > sensors [ i ] ) ;
kref_put ( & pv - > ref , wf_ad7417_release ) ;
return 0 ;
}
static const struct i2c_device_id wf_ad7417_id [ ] = {
{ " MAC,ad7417 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , wf_ad7417_id ) ;
static struct i2c_driver wf_ad7417_driver = {
. driver = {
. name = " wf_ad7417 " ,
} ,
. probe = wf_ad7417_probe ,
. remove = wf_ad7417_remove ,
. id_table = wf_ad7417_id ,
} ;
2012-12-22 03:03:50 +04:00
static int wf_ad7417_init ( void )
2012-04-19 02:16:52 +04:00
{
/* This is only supported on these machines */
if ( ! of_machine_is_compatible ( " PowerMac7,2 " ) & &
! of_machine_is_compatible ( " PowerMac7,3 " ) & &
! of_machine_is_compatible ( " RackMac3,1 " ) )
return - ENODEV ;
return i2c_add_driver ( & wf_ad7417_driver ) ;
}
2012-12-22 03:03:50 +04:00
static void wf_ad7417_exit ( void )
2012-04-19 02:16:52 +04:00
{
i2c_del_driver ( & wf_ad7417_driver ) ;
}
module_init ( wf_ad7417_init ) ;
module_exit ( wf_ad7417_exit ) ;
MODULE_AUTHOR ( " Benjamin Herrenschmidt <benh@kernel.crashing.org> " ) ;
MODULE_DESCRIPTION ( " ad7417 sensor driver for PowerMacs " ) ;
MODULE_LICENSE ( " GPL " ) ;