2019-06-03 08:45:02 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-11-07 08:08:17 +03:00
/*
* Windfarm PowerMac thermal control . SMU based controls
*
* ( c ) Copyright 2005 Benjamin Herrenschmidt , IBM Corp .
* < benh @ kernel . crashing . org >
*/
# 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>
2006-01-08 12:02:05 +03:00
# include <linux/completion.h>
2005-11-07 08:08:17 +03:00
# include <asm/prom.h>
# include <asm/machdep.h>
# include <asm/io.h>
# include <asm/sections.h>
# include <asm/smu.h>
# include "windfarm.h"
2006-02-08 08:42:51 +03:00
# define VERSION "0.4"
2005-11-07 08:08:17 +03:00
# undef DEBUG
# ifdef DEBUG
# define DBG(args...) printk(args)
# else
# define DBG(args...) do { } while(0)
# endif
2006-02-08 08:42:51 +03:00
static int smu_supports_new_fans_ops = 1 ;
2005-11-07 08:08:17 +03:00
/*
* SMU fans control object
*/
static LIST_HEAD ( smu_fans ) ;
struct smu_fan_control {
struct list_head link ;
int fan_type ; /* 0 = rpm, 1 = pwm */
u32 reg ; /* index in SMU */
s32 value ; /* current value */
s32 min , max ; /* min/max values */
struct wf_control ctrl ;
} ;
# define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
static int smu_set_fan ( int pwm , u8 id , u16 value )
{
struct smu_cmd cmd ;
u8 buffer [ 16 ] ;
2006-10-01 10:28:10 +04:00
DECLARE_COMPLETION_ONSTACK ( comp ) ;
2005-11-07 08:08:17 +03:00
int rc ;
/* Fill SMU command structure */
cmd . cmd = SMU_CMD_FAN_COMMAND ;
2006-02-08 08:42:51 +03:00
/* The SMU has an "old" and a "new" way of setting the fan speed
* Unfortunately , I found no reliable way to know which one works
* on a given machine model . After some investigations it appears
* that MacOS X just tries the new one , and if it fails fallbacks
* to the old ones . . . Ugh .
*/
retry :
if ( smu_supports_new_fans_ops ) {
buffer [ 0 ] = 0x30 ;
buffer [ 1 ] = id ;
* ( ( u16 * ) ( & buffer [ 2 ] ) ) = value ;
cmd . data_len = 4 ;
} else {
if ( id > 7 )
return - EINVAL ;
/* Fill argument buffer */
memset ( buffer , 0 , 16 ) ;
buffer [ 0 ] = pwm ? 0x10 : 0x00 ;
buffer [ 1 ] = 0x01 < < id ;
* ( ( u16 * ) & buffer [ 2 + id * 2 ] ) = value ;
cmd . data_len = 14 ;
}
2005-11-07 08:08:17 +03:00
cmd . reply_len = 16 ;
cmd . data_buf = cmd . reply_buf = buffer ;
cmd . status = 0 ;
cmd . done = smu_done_complete ;
cmd . misc = & comp ;
rc = smu_queue_cmd ( & cmd ) ;
if ( rc )
return rc ;
wait_for_completion ( & comp ) ;
2006-02-08 08:42:51 +03:00
2021-01-20 17:20:21 +03:00
/* Handle fallback (see comment above) */
2006-02-08 08:42:51 +03:00
if ( cmd . status ! = 0 & & smu_supports_new_fans_ops ) {
printk ( KERN_WARNING " windfarm: SMU failed new fan command "
" falling back to old method \n " ) ;
smu_supports_new_fans_ops = 0 ;
goto retry ;
}
2005-11-07 08:08:17 +03:00
return cmd . status ;
}
static void smu_fan_release ( struct wf_control * ct )
{
struct smu_fan_control * fct = to_smu_fan ( ct ) ;
kfree ( fct ) ;
}
static int smu_fan_set ( struct wf_control * ct , s32 value )
{
struct smu_fan_control * fct = to_smu_fan ( ct ) ;
if ( value < fct - > min )
value = fct - > min ;
if ( value > fct - > max )
value = fct - > max ;
fct - > value = value ;
return smu_set_fan ( fct - > fan_type , fct - > reg , value ) ;
}
static int smu_fan_get ( struct wf_control * ct , s32 * value )
{
struct smu_fan_control * fct = to_smu_fan ( ct ) ;
* value = fct - > value ; /* todo: read from SMU */
return 0 ;
}
static s32 smu_fan_min ( struct wf_control * ct )
{
struct smu_fan_control * fct = to_smu_fan ( ct ) ;
return fct - > min ;
}
static s32 smu_fan_max ( struct wf_control * ct )
{
struct smu_fan_control * fct = to_smu_fan ( ct ) ;
return fct - > max ;
}
2017-08-11 20:38:45 +03:00
static const struct wf_control_ops smu_fan_ops = {
2005-11-07 08:08:17 +03:00
. set_value = smu_fan_set ,
. get_value = smu_fan_get ,
. get_min = smu_fan_min ,
. get_max = smu_fan_max ,
. release = smu_fan_release ,
. owner = THIS_MODULE ,
} ;
static struct smu_fan_control * smu_fan_create ( struct device_node * node ,
int pwm_fan )
{
struct smu_fan_control * fct ;
2006-07-12 09:40:29 +04:00
const s32 * v ;
const u32 * reg ;
const char * l ;
2005-11-07 08:08:17 +03:00
fct = kmalloc ( sizeof ( struct smu_fan_control ) , GFP_KERNEL ) ;
if ( fct = = NULL )
return NULL ;
fct - > ctrl . ops = & smu_fan_ops ;
2007-04-27 07:41:15 +04:00
l = of_get_property ( node , " location " , NULL ) ;
2005-11-07 08:08:17 +03:00
if ( l = = NULL )
goto fail ;
fct - > fan_type = pwm_fan ;
fct - > ctrl . type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN ;
/* We use the name & location here the same way we do for SMU sensors,
* see the comment in windfarm_smu_sensors . c . The locations are a bit
* less consistent here between the iMac and the desktop models , but
* that is good enough for our needs for now at least .
*
* One problem though is that Apple seem to be inconsistent with case
* and the kernel doesn ' t have strcasecmp = P
*/
fct - > ctrl . name = NULL ;
/* Names used on desktop models */
if ( ! strcmp ( l , " Rear Fan 0 " ) | | ! strcmp ( l , " Rear Fan " ) | |
2006-02-08 08:42:51 +03:00
! strcmp ( l , " Rear fan 0 " ) | | ! strcmp ( l , " Rear fan " ) | |
! strcmp ( l , " CPU A EXHAUST " ) )
2005-11-07 08:08:17 +03:00
fct - > ctrl . name = " cpu-rear-fan-0 " ;
2006-02-08 08:42:51 +03:00
else if ( ! strcmp ( l , " Rear Fan 1 " ) | | ! strcmp ( l , " Rear fan 1 " ) | |
! strcmp ( l , " CPU B EXHAUST " ) )
2005-11-07 08:08:17 +03:00
fct - > ctrl . name = " cpu-rear-fan-1 " ;
else if ( ! strcmp ( l , " Front Fan 0 " ) | | ! strcmp ( l , " Front Fan " ) | |
2006-02-08 08:42:51 +03:00
! strcmp ( l , " Front fan 0 " ) | | ! strcmp ( l , " Front fan " ) | |
! strcmp ( l , " CPU A INTAKE " ) )
2005-11-07 08:08:17 +03:00
fct - > ctrl . name = " cpu-front-fan-0 " ;
2006-02-08 08:42:51 +03:00
else if ( ! strcmp ( l , " Front Fan 1 " ) | | ! strcmp ( l , " Front fan 1 " ) | |
! strcmp ( l , " CPU B INTAKE " ) )
2005-11-07 08:08:17 +03:00
fct - > ctrl . name = " cpu-front-fan-1 " ;
2006-02-08 08:42:51 +03:00
else if ( ! strcmp ( l , " CPU A PUMP " ) )
fct - > ctrl . name = " cpu-pump-0 " ;
2009-11-27 08:44:33 +03:00
else if ( ! strcmp ( l , " CPU B PUMP " ) )
fct - > ctrl . name = " cpu-pump-1 " ;
2006-02-08 08:42:51 +03:00
else if ( ! strcmp ( l , " Slots Fan " ) | | ! strcmp ( l , " Slots fan " ) | |
! strcmp ( l , " EXPANSION SLOTS INTAKE " ) )
2005-11-07 08:08:17 +03:00
fct - > ctrl . name = " slots-fan " ;
2006-02-08 08:42:51 +03:00
else if ( ! strcmp ( l , " Drive Bay " ) | | ! strcmp ( l , " Drive bay " ) | |
! strcmp ( l , " DRIVE BAY A INTAKE " ) )
2005-11-07 08:08:17 +03:00
fct - > ctrl . name = " drive-bay-fan " ;
2006-02-08 08:42:51 +03:00
else if ( ! strcmp ( l , " BACKSIDE " ) )
fct - > ctrl . name = " backside-fan " ;
2005-11-07 08:08:17 +03:00
/* Names used on iMac models */
if ( ! strcmp ( l , " System Fan " ) | | ! strcmp ( l , " System fan " ) )
fct - > ctrl . name = " system-fan " ;
else if ( ! strcmp ( l , " CPU Fan " ) | | ! strcmp ( l , " CPU fan " ) )
fct - > ctrl . name = " cpu-fan " ;
else if ( ! strcmp ( l , " Hard Drive " ) | | ! strcmp ( l , " Hard drive " ) )
fct - > ctrl . name = " drive-bay-fan " ;
2008-04-29 09:39:55 +04:00
else if ( ! strcmp ( l , " HDD Fan " ) ) /* seen on iMac G5 iSight */
fct - > ctrl . name = " hard-drive-fan " ;
else if ( ! strcmp ( l , " ODD Fan " ) ) /* same */
fct - > ctrl . name = " optical-drive-fan " ;
2005-11-07 08:08:17 +03:00
/* Unrecognized fan, bail out */
if ( fct - > ctrl . name = = NULL )
goto fail ;
/* Get min & max values*/
2007-04-27 07:41:15 +04:00
v = of_get_property ( node , " min-value " , NULL ) ;
2005-11-07 08:08:17 +03:00
if ( v = = NULL )
goto fail ;
fct - > min = * v ;
2007-04-27 07:41:15 +04:00
v = of_get_property ( node , " max-value " , NULL ) ;
2005-11-07 08:08:17 +03:00
if ( v = = NULL )
goto fail ;
fct - > max = * v ;
/* Get "reg" value */
2007-04-27 07:41:15 +04:00
reg = of_get_property ( node , " reg " , NULL ) ;
2005-11-07 08:08:17 +03:00
if ( reg = = NULL )
goto fail ;
fct - > reg = * reg ;
if ( wf_register_control ( & fct - > ctrl ) )
goto fail ;
return fct ;
fail :
kfree ( fct ) ;
return NULL ;
}
static int __init smu_controls_init ( void )
{
struct device_node * smu , * fans , * fan ;
if ( ! smu_present ( ) )
return - ENODEV ;
smu = of_find_node_by_type ( NULL , " smu " ) ;
if ( smu = = NULL )
return - ENODEV ;
/* Look for RPM fans */
for ( fans = NULL ; ( fans = of_get_next_child ( smu , fans ) ) ! = NULL ; )
2018-12-05 22:50:28 +03:00
if ( of_node_name_eq ( fans , " rpm-fans " ) | |
2007-05-03 11:26:52 +04:00
of_device_is_compatible ( fans , " smu-rpm-fans " ) )
2005-11-07 08:08:17 +03:00
break ;
for ( fan = NULL ;
fans & & ( fan = of_get_next_child ( fans , fan ) ) ! = NULL ; ) {
struct smu_fan_control * fct ;
fct = smu_fan_create ( fan , 0 ) ;
if ( fct = = NULL ) {
printk ( KERN_WARNING " windfarm: Failed to create SMU "
2018-09-05 00:27:44 +03:00
" RPM fan %pOFn \n " , fan ) ;
2005-11-07 08:08:17 +03:00
continue ;
}
list_add ( & fct - > link , & smu_fans ) ;
}
of_node_put ( fans ) ;
/* Look for PWM fans */
for ( fans = NULL ; ( fans = of_get_next_child ( smu , fans ) ) ! = NULL ; )
2018-12-05 22:50:28 +03:00
if ( of_node_name_eq ( fans , " pwm-fans " ) )
2005-11-07 08:08:17 +03:00
break ;
for ( fan = NULL ;
fans & & ( fan = of_get_next_child ( fans , fan ) ) ! = NULL ; ) {
struct smu_fan_control * fct ;
fct = smu_fan_create ( fan , 1 ) ;
if ( fct = = NULL ) {
printk ( KERN_WARNING " windfarm: Failed to create SMU "
2018-09-05 00:27:44 +03:00
" PWM fan %pOFn \n " , fan ) ;
2005-11-07 08:08:17 +03:00
continue ;
}
list_add ( & fct - > link , & smu_fans ) ;
}
of_node_put ( fans ) ;
of_node_put ( smu ) ;
return 0 ;
}
static void __exit smu_controls_exit ( void )
{
struct smu_fan_control * fct ;
while ( ! list_empty ( & smu_fans ) ) {
fct = list_entry ( smu_fans . next , struct smu_fan_control , link ) ;
list_del ( & fct - > link ) ;
wf_unregister_control ( & fct - > ctrl ) ;
}
}
module_init ( smu_controls_init ) ;
module_exit ( smu_controls_exit ) ;
MODULE_AUTHOR ( " Benjamin Herrenschmidt <benh@kernel.crashing.org> " ) ;
MODULE_DESCRIPTION ( " SMU control objects for PowerMacs thermal control " ) ;
MODULE_LICENSE ( " GPL " ) ;