2005-04-17 02:20:36 +04:00
/*
* PowerMac G5 SMU driver
*
* Copyright 2004 J . Mayer < l_indien @ magic . fr >
* Copyright 2005 Benjamin Herrenschmidt , IBM Corp .
*
* Released under the term of the GNU GPL v2 .
*/
/*
* TODO :
2005-09-23 08:44:06 +04:00
* - maybe add timeout to commands ?
* - blocking version of time functions
* - polling version of i2c commands ( including timer that works with
* interrutps off )
* - maybe avoid some data copies with i2c by directly using the smu cmd
* buffer and a lower level internal interface
* - understand SMU - > CPU events and implement reception of them via
* the userland interface
2005-04-17 02:20:36 +04:00
*/
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/device.h>
# include <linux/dmapool.h>
# include <linux/bootmem.h>
# include <linux/vmalloc.h>
# include <linux/highmem.h>
# include <linux/jiffies.h>
# include <linux/interrupt.h>
# include <linux/rtc.h>
2005-09-23 08:44:06 +04:00
# include <linux/completion.h>
# include <linux/miscdevice.h>
# include <linux/delay.h>
# include <linux/sysdev.h>
# include <linux/poll.h>
2006-03-26 13:37:14 +04:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
# include <asm/byteorder.h>
# include <asm/io.h>
# include <asm/prom.h>
# include <asm/machdep.h>
# include <asm/pmac_feature.h>
# include <asm/smu.h>
# include <asm/sections.h>
# include <asm/abs_addr.h>
2005-09-23 08:44:06 +04:00
# include <asm/uaccess.h>
# include <asm/of_device.h>
2005-11-07 06:29:02 +03:00
# define VERSION "0.7"
2005-09-23 08:44:06 +04:00
# define AUTHOR "(c) 2005 Benjamin Herrenschmidt, IBM Corp."
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
# undef DEBUG_SMU
2005-04-17 02:20:36 +04:00
# ifdef DEBUG_SMU
2005-12-14 05:10:10 +03:00
# define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0)
2005-04-17 02:20:36 +04:00
# else
# define DPRINTK(fmt, args...) do { } while (0)
# endif
/*
* This is the command buffer passed to the SMU hardware
*/
2005-09-23 08:44:06 +04:00
# define SMU_MAX_DATA 254
2005-04-17 02:20:36 +04:00
struct smu_cmd_buf {
u8 cmd ;
u8 length ;
2005-09-23 08:44:06 +04:00
u8 data [ SMU_MAX_DATA ] ;
2005-04-17 02:20:36 +04:00
} ;
struct smu_device {
spinlock_t lock ;
struct device_node * of_node ;
2005-09-23 08:44:06 +04:00
struct of_device * of_dev ;
int doorbell ; /* doorbell gpio */
2005-04-17 02:20:36 +04:00
u32 __iomem * db_buf ; /* doorbell buffer */
2006-07-10 15:44:44 +04:00
struct device_node * db_node ;
unsigned int db_irq ;
2005-09-23 08:44:06 +04:00
int msg ;
2006-07-10 15:44:44 +04:00
struct device_node * msg_node ;
unsigned int msg_irq ;
2005-04-17 02:20:36 +04:00
struct smu_cmd_buf * cmd_buf ; /* command buffer virtual */
u32 cmd_buf_abs ; /* command buffer absolute */
2005-09-23 08:44:06 +04:00
struct list_head cmd_list ;
struct smu_cmd * cmd_cur ; /* pending command */
struct list_head cmd_i2c_list ;
struct smu_i2c_cmd * cmd_i2c_cur ; /* pending i2c command */
struct timer_list i2c_timer ;
2005-04-17 02:20:36 +04:00
} ;
/*
* I don ' t think there will ever be more than one SMU , so
* for now , just hard code that
*/
static struct smu_device * smu ;
2006-03-26 13:37:14 +04:00
static DEFINE_MUTEX ( smu_part_access ) ;
2006-07-10 15:44:44 +04:00
static int smu_irq_inited ;
2005-09-23 08:44:06 +04:00
2006-01-07 03:30:44 +03:00
static void smu_i2c_retry ( unsigned long data ) ;
2005-04-17 02:20:36 +04:00
/*
2005-09-23 08:44:06 +04:00
* SMU driver low level stuff
2005-04-17 02:20:36 +04:00
*/
2005-09-23 08:44:06 +04:00
static void smu_start_cmd ( void )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
unsigned long faddr , fend ;
struct smu_cmd * cmd ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
if ( list_empty ( & smu - > cmd_list ) )
return ;
/* Fetch first command in queue */
cmd = list_entry ( smu - > cmd_list . next , struct smu_cmd , link ) ;
smu - > cmd_cur = cmd ;
list_del ( & cmd - > link ) ;
DPRINTK ( " SMU: starting cmd %x, %d bytes data \n " , cmd - > cmd ,
cmd - > data_len ) ;
2005-11-07 06:29:02 +03:00
DPRINTK ( " SMU: data buffer: %02x %02x %02x %02x %02x %02x %02x %02x \n " ,
2005-09-23 08:44:06 +04:00
( ( u8 * ) cmd - > data_buf ) [ 0 ] , ( ( u8 * ) cmd - > data_buf ) [ 1 ] ,
2005-11-07 06:29:02 +03:00
( ( u8 * ) cmd - > data_buf ) [ 2 ] , ( ( u8 * ) cmd - > data_buf ) [ 3 ] ,
( ( u8 * ) cmd - > data_buf ) [ 4 ] , ( ( u8 * ) cmd - > data_buf ) [ 5 ] ,
( ( u8 * ) cmd - > data_buf ) [ 6 ] , ( ( u8 * ) cmd - > data_buf ) [ 7 ] ) ;
2005-09-23 08:44:06 +04:00
/* Fill the SMU command buffer */
smu - > cmd_buf - > cmd = cmd - > cmd ;
smu - > cmd_buf - > length = cmd - > data_len ;
memcpy ( smu - > cmd_buf - > data , cmd - > data_buf , cmd - > data_len ) ;
/* Flush command and data to RAM */
faddr = ( unsigned long ) smu - > cmd_buf ;
fend = faddr + smu - > cmd_buf - > length + 2 ;
flush_inval_dcache_range ( faddr , fend ) ;
/* This isn't exactly a DMA mapping here, I suspect
2005-04-17 02:20:36 +04:00
* the SMU is actually communicating with us via i2c to the
* northbridge or the CPU to access RAM .
*/
2005-09-23 08:44:06 +04:00
writel ( smu - > cmd_buf_abs , smu - > db_buf ) ;
2005-04-17 02:20:36 +04:00
/* Ring the SMU doorbell */
2005-09-23 08:44:06 +04:00
pmac_do_feature_call ( PMAC_FTR_WRITE_GPIO , NULL , smu - > doorbell , 4 ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
static irqreturn_t smu_db_intr ( int irq , void * arg , struct pt_regs * regs )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
unsigned long flags ;
struct smu_cmd * cmd ;
void ( * done ) ( struct smu_cmd * cmd , void * misc ) = NULL ;
void * misc = NULL ;
u8 gpio ;
int rc = 0 ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
/* SMU completed the command, well, we hope, let's make sure
* of it
*/
spin_lock_irqsave ( & smu - > lock , flags ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
gpio = pmac_do_feature_call ( PMAC_FTR_READ_GPIO , NULL , smu - > doorbell ) ;
2005-09-30 02:25:17 +04:00
if ( ( gpio & 7 ) ! = 7 ) {
spin_unlock_irqrestore ( & smu - > lock , flags ) ;
2005-09-23 08:44:06 +04:00
return IRQ_HANDLED ;
2005-09-30 02:25:17 +04:00
}
2005-09-23 08:44:06 +04:00
cmd = smu - > cmd_cur ;
smu - > cmd_cur = NULL ;
if ( cmd = = NULL )
goto bail ;
if ( rc = = 0 ) {
unsigned long faddr ;
int reply_len ;
u8 ack ;
/* CPU might have brought back the cache line, so we need
* to flush again before peeking at the SMU response . We
* flush the entire buffer for now as we haven ' t read the
* reply lenght ( it ' s only 2 cache lines anyway )
*/
faddr = ( unsigned long ) smu - > cmd_buf ;
flush_inval_dcache_range ( faddr , faddr + 256 ) ;
/* Now check ack */
ack = ( ~ cmd - > cmd ) & 0xff ;
if ( ack ! = smu - > cmd_buf - > cmd ) {
DPRINTK ( " SMU: incorrect ack, want %x got %x \n " ,
ack , smu - > cmd_buf - > cmd ) ;
rc = - EIO ;
}
reply_len = rc = = 0 ? smu - > cmd_buf - > length : 0 ;
DPRINTK ( " SMU: reply len: %d \n " , reply_len ) ;
if ( reply_len > cmd - > reply_len ) {
printk ( KERN_WARNING " SMU: reply buffer too small, "
" got %d bytes for a %d bytes buffer \n " ,
reply_len , cmd - > reply_len ) ;
reply_len = cmd - > reply_len ;
}
cmd - > reply_len = reply_len ;
if ( cmd - > reply_buf & & reply_len )
memcpy ( cmd - > reply_buf , smu - > cmd_buf - > data , reply_len ) ;
}
/* Now complete the command. Write status last in order as we lost
* ownership of the command structure as soon as it ' s no longer - 1
*/
done = cmd - > done ;
misc = cmd - > misc ;
mb ( ) ;
cmd - > status = rc ;
bail :
/* Start next command if any */
smu_start_cmd ( ) ;
spin_unlock_irqrestore ( & smu - > lock , flags ) ;
/* Call command completion handler if any */
if ( done )
done ( cmd , misc ) ;
/* It's an edge interrupt, nothing to do */
return IRQ_HANDLED ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
static irqreturn_t smu_msg_intr ( int irq , void * arg , struct pt_regs * regs )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
/* I don't quite know what to do with this one, we seem to never
* receive it , so I suspect we have to arm it someway in the SMU
* to start getting events that way .
*/
printk ( KERN_INFO " SMU: message interrupt ! \n " ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
/* It's an edge interrupt, nothing to do */
return IRQ_HANDLED ;
}
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
/*
* Queued command management .
*
*/
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
int smu_queue_cmd ( struct smu_cmd * cmd )
{
unsigned long flags ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
if ( smu = = NULL )
return - ENODEV ;
if ( cmd - > data_len > SMU_MAX_DATA | |
cmd - > reply_len > SMU_MAX_DATA )
return - EINVAL ;
cmd - > status = 1 ;
spin_lock_irqsave ( & smu - > lock , flags ) ;
list_add_tail ( & cmd - > link , & smu - > cmd_list ) ;
if ( smu - > cmd_cur = = NULL )
smu_start_cmd ( ) ;
spin_unlock_irqrestore ( & smu - > lock , flags ) ;
2006-07-10 15:44:44 +04:00
/* Workaround for early calls when irq isn't available */
if ( ! smu_irq_inited | | smu - > db_irq = = NO_IRQ )
smu_spinwait_cmd ( cmd ) ;
2005-09-23 08:44:06 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
EXPORT_SYMBOL ( smu_queue_cmd ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
int smu_queue_simple ( struct smu_simple_cmd * scmd , u8 command ,
unsigned int data_len ,
void ( * done ) ( struct smu_cmd * cmd , void * misc ) ,
void * misc , . . . )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
struct smu_cmd * cmd = & scmd - > cmd ;
va_list list ;
int i ;
if ( data_len > sizeof ( scmd - > buffer ) )
return - EINVAL ;
memset ( scmd , 0 , sizeof ( * scmd ) ) ;
cmd - > cmd = command ;
cmd - > data_len = data_len ;
cmd - > data_buf = scmd - > buffer ;
cmd - > reply_len = sizeof ( scmd - > buffer ) ;
cmd - > reply_buf = scmd - > buffer ;
cmd - > done = done ;
cmd - > misc = misc ;
va_start ( list , misc ) ;
for ( i = 0 ; i < data_len ; + + i )
scmd - > buffer [ i ] = ( u8 ) va_arg ( list , int ) ;
va_end ( list ) ;
return smu_queue_cmd ( cmd ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
EXPORT_SYMBOL ( smu_queue_simple ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
void smu_poll ( void )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
u8 gpio ;
if ( smu = = NULL )
return ;
gpio = pmac_do_feature_call ( PMAC_FTR_READ_GPIO , NULL , smu - > doorbell ) ;
if ( ( gpio & 7 ) = = 7 )
smu_db_intr ( smu - > db_irq , smu , NULL ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
EXPORT_SYMBOL ( smu_poll ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
void smu_done_complete ( struct smu_cmd * cmd , void * misc )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
struct completion * comp = misc ;
complete ( comp ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
EXPORT_SYMBOL ( smu_done_complete ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
void smu_spinwait_cmd ( struct smu_cmd * cmd )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
while ( cmd - > status = = 1 )
smu_poll ( ) ;
}
EXPORT_SYMBOL ( smu_spinwait_cmd ) ;
/* RTC low level commands */
static inline int bcd2hex ( int n )
{
return ( ( ( n & 0xf0 ) > > 4 ) * 10 ) + ( n & 0xf ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
static inline int hex2bcd ( int n )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
return ( ( n / 10 ) < < 4 ) + ( n % 10 ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
2005-04-17 02:20:36 +04:00
static inline void smu_fill_set_rtc_cmd ( struct smu_cmd_buf * cmd_buf ,
struct rtc_time * time )
{
cmd_buf - > cmd = 0x8e ;
cmd_buf - > length = 8 ;
cmd_buf - > data [ 0 ] = 0x80 ;
cmd_buf - > data [ 1 ] = hex2bcd ( time - > tm_sec ) ;
cmd_buf - > data [ 2 ] = hex2bcd ( time - > tm_min ) ;
cmd_buf - > data [ 3 ] = hex2bcd ( time - > tm_hour ) ;
cmd_buf - > data [ 4 ] = time - > tm_wday ;
cmd_buf - > data [ 5 ] = hex2bcd ( time - > tm_mday ) ;
cmd_buf - > data [ 6 ] = hex2bcd ( time - > tm_mon ) + 1 ;
cmd_buf - > data [ 7 ] = hex2bcd ( time - > tm_year - 100 ) ;
}
2005-09-23 08:44:06 +04:00
int smu_get_rtc_time ( struct rtc_time * time , int spinwait )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
struct smu_simple_cmd cmd ;
2005-04-17 02:20:36 +04:00
int rc ;
if ( smu = = NULL )
return - ENODEV ;
memset ( time , 0 , sizeof ( struct rtc_time ) ) ;
2005-09-23 08:44:06 +04:00
rc = smu_queue_simple ( & cmd , SMU_CMD_RTC_COMMAND , 1 , NULL , NULL ,
SMU_CMD_RTC_GET_DATETIME ) ;
if ( rc )
return rc ;
smu_spinwait_simple ( & cmd ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
time - > tm_sec = bcd2hex ( cmd . buffer [ 0 ] ) ;
time - > tm_min = bcd2hex ( cmd . buffer [ 1 ] ) ;
time - > tm_hour = bcd2hex ( cmd . buffer [ 2 ] ) ;
time - > tm_wday = bcd2hex ( cmd . buffer [ 3 ] ) ;
time - > tm_mday = bcd2hex ( cmd . buffer [ 4 ] ) ;
time - > tm_mon = bcd2hex ( cmd . buffer [ 5 ] ) - 1 ;
time - > tm_year = bcd2hex ( cmd . buffer [ 6 ] ) + 100 ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
int smu_set_rtc_time ( struct rtc_time * time , int spinwait )
2005-04-17 02:20:36 +04:00
{
2005-09-23 08:44:06 +04:00
struct smu_simple_cmd cmd ;
2005-04-17 02:20:36 +04:00
int rc ;
if ( smu = = NULL )
return - ENODEV ;
2005-09-23 08:44:06 +04:00
rc = smu_queue_simple ( & cmd , SMU_CMD_RTC_COMMAND , 8 , NULL , NULL ,
SMU_CMD_RTC_SET_DATETIME ,
hex2bcd ( time - > tm_sec ) ,
hex2bcd ( time - > tm_min ) ,
hex2bcd ( time - > tm_hour ) ,
time - > tm_wday ,
hex2bcd ( time - > tm_mday ) ,
hex2bcd ( time - > tm_mon ) + 1 ,
hex2bcd ( time - > tm_year - 100 ) ) ;
if ( rc )
return rc ;
smu_spinwait_simple ( & cmd ) ;
2005-04-17 02:20:36 +04:00
2005-09-23 08:44:06 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-09-23 08:44:06 +04:00
2005-04-17 02:20:36 +04:00
void smu_shutdown ( void )
{
2005-09-23 08:44:06 +04:00
struct smu_simple_cmd cmd ;
2005-04-17 02:20:36 +04:00
if ( smu = = NULL )
return ;
2005-09-23 08:44:06 +04:00
if ( smu_queue_simple ( & cmd , SMU_CMD_POWER_COMMAND , 9 , NULL , NULL ,
' S ' , ' H ' , ' U ' , ' T ' , ' D ' , ' O ' , ' W ' , ' N ' , 0 ) )
return ;
smu_spinwait_simple ( & cmd ) ;
2005-04-17 02:20:36 +04:00
for ( ; ; )
;
}
2005-09-23 08:44:06 +04:00
2005-04-17 02:20:36 +04:00
void smu_restart ( void )
{
2005-09-23 08:44:06 +04:00
struct smu_simple_cmd cmd ;
2005-04-17 02:20:36 +04:00
if ( smu = = NULL )
return ;
2005-09-23 08:44:06 +04:00
if ( smu_queue_simple ( & cmd , SMU_CMD_POWER_COMMAND , 8 , NULL , NULL ,
' R ' , ' E ' , ' S ' , ' T ' , ' A ' , ' R ' , ' T ' , 0 ) )
return ;
smu_spinwait_simple ( & cmd ) ;
2005-04-17 02:20:36 +04:00
for ( ; ; )
;
}
2005-09-23 08:44:06 +04:00
2005-04-17 02:20:36 +04:00
int smu_present ( void )
{
return smu ! = NULL ;
}
2005-09-23 08:44:06 +04:00
EXPORT_SYMBOL ( smu_present ) ;
2005-04-17 02:20:36 +04:00
2005-11-07 06:29:02 +03:00
int __init smu_init ( void )
2005-04-17 02:20:36 +04:00
{
struct device_node * np ;
2006-07-12 09:40:29 +04:00
const u32 * data ;
2005-04-17 02:20:36 +04:00
np = of_find_node_by_type ( NULL , " smu " ) ;
if ( np = = NULL )
return - ENODEV ;
2005-09-23 08:44:06 +04:00
printk ( KERN_INFO " SMU driver %s %s \n " , VERSION , AUTHOR ) ;
2005-04-17 02:20:36 +04:00
if ( smu_cmdbuf_abs = = 0 ) {
printk ( KERN_ERR " SMU: Command buffer not allocated ! \n " ) ;
return - EINVAL ;
}
smu = alloc_bootmem ( sizeof ( struct smu_device ) ) ;
if ( smu = = NULL )
return - ENOMEM ;
memset ( smu , 0 , sizeof ( * smu ) ) ;
spin_lock_init ( & smu - > lock ) ;
2005-09-23 08:44:06 +04:00
INIT_LIST_HEAD ( & smu - > cmd_list ) ;
INIT_LIST_HEAD ( & smu - > cmd_i2c_list ) ;
2005-04-17 02:20:36 +04:00
smu - > of_node = np ;
2005-09-23 08:44:06 +04:00
smu - > db_irq = NO_IRQ ;
smu - > msg_irq = NO_IRQ ;
2005-04-17 02:20:36 +04:00
/* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a
* 32 bits value safely
*/
smu - > cmd_buf_abs = ( u32 ) smu_cmdbuf_abs ;
smu - > cmd_buf = ( struct smu_cmd_buf * ) abs_to_virt ( smu_cmdbuf_abs ) ;
2006-07-10 15:44:44 +04:00
smu - > db_node = of_find_node_by_name ( NULL , " smu-doorbell " ) ;
if ( smu - > db_node = = NULL ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR " SMU: Can't find doorbell GPIO ! \n " ) ;
goto fail ;
}
2006-08-01 04:37:25 +04:00
data = get_property ( smu - > db_node , " reg " , NULL ) ;
2005-04-17 02:20:36 +04:00
if ( data = = NULL ) {
2006-07-10 15:44:44 +04:00
of_node_put ( smu - > db_node ) ;
smu - > db_node = NULL ;
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR " SMU: Can't find doorbell GPIO address ! \n " ) ;
goto fail ;
}
/* Current setup has one doorbell GPIO that does both doorbell
* and ack . GPIOs are at 0x50 , best would be to find that out
* in the device - tree though .
*/
2005-09-23 08:44:06 +04:00
smu - > doorbell = * data ;
if ( smu - > doorbell < 0x50 )
smu - > doorbell + = 0x50 ;
/* Now look for the smu-interrupt GPIO */
do {
2006-07-10 15:44:44 +04:00
smu - > msg_node = of_find_node_by_name ( NULL , " smu-interrupt " ) ;
if ( smu - > msg_node = = NULL )
2005-09-23 08:44:06 +04:00
break ;
2006-08-01 04:37:25 +04:00
data = get_property ( smu - > msg_node , " reg " , NULL ) ;
2005-09-23 08:44:06 +04:00
if ( data = = NULL ) {
2006-07-10 15:44:44 +04:00
of_node_put ( smu - > msg_node ) ;
smu - > msg_node = NULL ;
2005-09-23 08:44:06 +04:00
break ;
}
smu - > msg = * data ;
if ( smu - > msg < 0x50 )
smu - > msg + = 0x50 ;
} while ( 0 ) ;
2005-04-17 02:20:36 +04:00
/* Doorbell buffer is currently hard-coded, I didn't find a proper
* device - tree entry giving the address . Best would probably to use
* an offset for K2 base though , but let ' s do it that way for now .
*/
smu - > db_buf = ioremap ( 0x8000860c , 0x1000 ) ;
if ( smu - > db_buf = = NULL ) {
printk ( KERN_ERR " SMU: Can't map doorbell buffer pointer ! \n " ) ;
goto fail ;
}
sys_ctrler = SYS_CTRLER_SMU ;
return 0 ;
fail :
smu = NULL ;
return - ENXIO ;
}
2005-09-23 08:44:06 +04:00
static int smu_late_init ( void )
{
if ( ! smu )
return 0 ;
2006-01-07 03:30:44 +03:00
init_timer ( & smu - > i2c_timer ) ;
smu - > i2c_timer . function = smu_i2c_retry ;
smu - > i2c_timer . data = ( unsigned long ) smu ;
2006-07-10 15:44:44 +04:00
if ( smu - > db_node ) {
smu - > db_irq = irq_of_parse_and_map ( smu - > db_node , 0 ) ;
if ( smu - > db_irq = = NO_IRQ )
printk ( KERN_ERR " smu: failed to map irq for node %s \n " ,
smu - > db_node - > full_name ) ;
}
if ( smu - > msg_node ) {
smu - > msg_irq = irq_of_parse_and_map ( smu - > msg_node , 0 ) ;
if ( smu - > msg_irq = = NO_IRQ )
printk ( KERN_ERR " smu: failed to map irq for node %s \n " ,
smu - > msg_node - > full_name ) ;
}
2005-09-23 08:44:06 +04:00
/*
* Try to request the interrupts
*/
if ( smu - > db_irq ! = NO_IRQ ) {
if ( request_irq ( smu - > db_irq , smu_db_intr ,
2006-07-02 06:29:38 +04:00
IRQF_SHARED , " SMU doorbell " , smu ) < 0 ) {
2005-09-23 08:44:06 +04:00
printk ( KERN_WARNING " SMU: can't "
" request interrupt %d \n " ,
smu - > db_irq ) ;
smu - > db_irq = NO_IRQ ;
}
}
if ( smu - > msg_irq ! = NO_IRQ ) {
if ( request_irq ( smu - > msg_irq , smu_msg_intr ,
2006-07-02 06:29:38 +04:00
IRQF_SHARED , " SMU message " , smu ) < 0 ) {
2005-09-23 08:44:06 +04:00
printk ( KERN_WARNING " SMU: can't "
" request interrupt %d \n " ,
smu - > msg_irq ) ;
smu - > msg_irq = NO_IRQ ;
}
}
2006-07-10 15:44:44 +04:00
smu_irq_inited = 1 ;
2005-09-23 08:44:06 +04:00
return 0 ;
}
2006-01-07 03:30:44 +03:00
/* This has to be before arch_initcall as the low i2c stuff relies on the
* above having been done before we reach arch_initcalls
*/
core_initcall ( smu_late_init ) ;
2005-09-23 08:44:06 +04:00
/*
* sysfs visibility
*/
static void smu_expose_childs ( void * unused )
{
2006-01-07 03:35:26 +03:00
struct device_node * np ;
for ( np = NULL ; ( np = of_get_next_child ( smu - > of_node , np ) ) ! = NULL ; )
2005-11-07 08:08:17 +03:00
if ( device_is_compatible ( np , " smu-sensors " ) )
2006-01-07 03:30:44 +03:00
of_platform_device_create ( np , " smu-sensors " ,
& smu - > of_dev - > dev ) ;
2005-09-23 08:44:06 +04:00
}
static DECLARE_WORK ( smu_expose_childs_work , smu_expose_childs , NULL ) ;
static int smu_platform_probe ( struct of_device * dev ,
const struct of_device_id * match )
{
if ( ! smu )
return - ENODEV ;
smu - > of_dev = dev ;
/*
* Ok , we are matched , now expose all i2c busses . We have to defer
* that unfortunately or it would deadlock inside the device model
*/
schedule_work ( & smu_expose_childs_work ) ;
return 0 ;
}
static struct of_device_id smu_platform_match [ ] =
{
{
. type = " smu " ,
} ,
{ } ,
} ;
static struct of_platform_driver smu_of_platform_driver =
{
. name = " smu " ,
. match_table = smu_platform_match ,
. probe = smu_platform_probe ,
} ;
static int __init smu_init_sysfs ( void )
{
/*
* Due to sysfs bogosity , a sysdev is not a real device , so
* we should in fact create both if we want sysdev semantics
* for power management .
* For now , we don ' t power manage machines with an SMU chip ,
* I ' m a bit too far from figuring out how that works with those
* new chipsets , but that will come back and bite us
*/
2006-03-22 10:20:27 +03:00
of_register_driver ( & smu_of_platform_driver ) ;
2005-09-23 08:44:06 +04:00
return 0 ;
}
device_initcall ( smu_init_sysfs ) ;
struct of_device * smu_get_ofdev ( void )
{
if ( ! smu )
return NULL ;
return smu - > of_dev ;
}
EXPORT_SYMBOL_GPL ( smu_get_ofdev ) ;
/*
* i2c interface
*/
static void smu_i2c_complete_command ( struct smu_i2c_cmd * cmd , int fail )
{
void ( * done ) ( struct smu_i2c_cmd * cmd , void * misc ) = cmd - > done ;
void * misc = cmd - > misc ;
unsigned long flags ;
/* Check for read case */
if ( ! fail & & cmd - > read ) {
if ( cmd - > pdata [ 0 ] < 1 )
fail = 1 ;
else
memcpy ( cmd - > info . data , & cmd - > pdata [ 1 ] ,
cmd - > info . datalen ) ;
}
DPRINTK ( " SMU: completing, success: %d \n " , ! fail ) ;
/* Update status and mark no pending i2c command with lock
* held so nobody comes in while we dequeue an eventual
* pending next i2c command
*/
spin_lock_irqsave ( & smu - > lock , flags ) ;
smu - > cmd_i2c_cur = NULL ;
wmb ( ) ;
cmd - > status = fail ? - EIO : 0 ;
/* Is there another i2c command waiting ? */
if ( ! list_empty ( & smu - > cmd_i2c_list ) ) {
struct smu_i2c_cmd * newcmd ;
/* Fetch it, new current, remove from list */
newcmd = list_entry ( smu - > cmd_i2c_list . next ,
struct smu_i2c_cmd , link ) ;
smu - > cmd_i2c_cur = newcmd ;
list_del ( & cmd - > link ) ;
/* Queue with low level smu */
list_add_tail ( & cmd - > scmd . link , & smu - > cmd_list ) ;
if ( smu - > cmd_cur = = NULL )
smu_start_cmd ( ) ;
}
spin_unlock_irqrestore ( & smu - > lock , flags ) ;
/* Call command completion handler if any */
if ( done )
done ( cmd , misc ) ;
}
static void smu_i2c_retry ( unsigned long data )
{
2006-01-07 03:30:44 +03:00
struct smu_i2c_cmd * cmd = smu - > cmd_i2c_cur ;
2005-09-23 08:44:06 +04:00
DPRINTK ( " SMU: i2c failure, requeuing... \n " ) ;
/* requeue command simply by resetting reply_len */
cmd - > pdata [ 0 ] = 0xff ;
2006-01-07 03:30:44 +03:00
cmd - > scmd . reply_len = sizeof ( cmd - > pdata ) ;
2005-09-23 08:44:06 +04:00
smu_queue_cmd ( & cmd - > scmd ) ;
}
static void smu_i2c_low_completion ( struct smu_cmd * scmd , void * misc )
{
struct smu_i2c_cmd * cmd = misc ;
int fail = 0 ;
DPRINTK ( " SMU: i2c compl. stage=%d status=%x pdata[0]=%x rlen: %x \n " ,
cmd - > stage , scmd - > status , cmd - > pdata [ 0 ] , scmd - > reply_len ) ;
/* Check for possible status */
if ( scmd - > status < 0 )
fail = 1 ;
else if ( cmd - > read ) {
if ( cmd - > stage = = 0 )
fail = cmd - > pdata [ 0 ] ! = 0 ;
else
fail = cmd - > pdata [ 0 ] > = 0x80 ;
} else {
fail = cmd - > pdata [ 0 ] ! = 0 ;
}
/* Handle failures by requeuing command, after 5ms interval
*/
if ( fail & & - - cmd - > retries > 0 ) {
DPRINTK ( " SMU: i2c failure, starting timer... \n " ) ;
2006-01-07 03:30:44 +03:00
BUG_ON ( cmd ! = smu - > cmd_i2c_cur ) ;
2006-07-10 15:44:44 +04:00
if ( ! smu_irq_inited ) {
mdelay ( 5 ) ;
smu_i2c_retry ( 0 ) ;
return ;
}
2006-01-07 03:30:44 +03:00
mod_timer ( & smu - > i2c_timer , jiffies + msecs_to_jiffies ( 5 ) ) ;
2005-09-23 08:44:06 +04:00
return ;
}
/* If failure or stage 1, command is complete */
if ( fail | | cmd - > stage ! = 0 ) {
smu_i2c_complete_command ( cmd , fail ) ;
return ;
}
DPRINTK ( " SMU: going to stage 1 \n " ) ;
/* Ok, initial command complete, now poll status */
scmd - > reply_buf = cmd - > pdata ;
2006-01-07 03:30:44 +03:00
scmd - > reply_len = sizeof ( cmd - > pdata ) ;
2005-09-23 08:44:06 +04:00
scmd - > data_buf = cmd - > pdata ;
scmd - > data_len = 1 ;
cmd - > pdata [ 0 ] = 0 ;
cmd - > stage = 1 ;
cmd - > retries = 20 ;
smu_queue_cmd ( scmd ) ;
}
int smu_queue_i2c ( struct smu_i2c_cmd * cmd )
{
unsigned long flags ;
if ( smu = = NULL )
return - ENODEV ;
/* Fill most fields of scmd */
cmd - > scmd . cmd = SMU_CMD_I2C_COMMAND ;
cmd - > scmd . done = smu_i2c_low_completion ;
cmd - > scmd . misc = cmd ;
cmd - > scmd . reply_buf = cmd - > pdata ;
2006-01-07 03:30:44 +03:00
cmd - > scmd . reply_len = sizeof ( cmd - > pdata ) ;
2005-09-23 08:44:06 +04:00
cmd - > scmd . data_buf = ( u8 * ) ( char * ) & cmd - > info ;
cmd - > scmd . status = 1 ;
cmd - > stage = 0 ;
cmd - > pdata [ 0 ] = 0xff ;
cmd - > retries = 20 ;
cmd - > status = 1 ;
/* Check transfer type, sanitize some "info" fields
* based on transfer type and do more checking
*/
cmd - > info . caddr = cmd - > info . devaddr ;
cmd - > read = cmd - > info . devaddr & 0x01 ;
switch ( cmd - > info . type ) {
case SMU_I2C_TRANSFER_SIMPLE :
memset ( & cmd - > info . sublen , 0 , 4 ) ;
break ;
case SMU_I2C_TRANSFER_COMBINED :
cmd - > info . devaddr & = 0xfe ;
case SMU_I2C_TRANSFER_STDSUB :
if ( cmd - > info . sublen > 3 )
return - EINVAL ;
break ;
default :
return - EINVAL ;
}
/* Finish setting up command based on transfer direction
*/
if ( cmd - > read ) {
if ( cmd - > info . datalen > SMU_I2C_READ_MAX )
return - EINVAL ;
memset ( cmd - > info . data , 0xff , cmd - > info . datalen ) ;
cmd - > scmd . data_len = 9 ;
} else {
if ( cmd - > info . datalen > SMU_I2C_WRITE_MAX )
return - EINVAL ;
cmd - > scmd . data_len = 9 + cmd - > info . datalen ;
}
DPRINTK ( " SMU: i2c enqueuing command \n " ) ;
DPRINTK ( " SMU: %s, len=%d bus=%x addr=%x sub0=%x type=%x \n " ,
cmd - > read ? " read " : " write " , cmd - > info . datalen ,
cmd - > info . bus , cmd - > info . caddr ,
cmd - > info . subaddr [ 0 ] , cmd - > info . type ) ;
/* Enqueue command in i2c list, and if empty, enqueue also in
* main command list
*/
spin_lock_irqsave ( & smu - > lock , flags ) ;
if ( smu - > cmd_i2c_cur = = NULL ) {
smu - > cmd_i2c_cur = cmd ;
list_add_tail ( & cmd - > scmd . link , & smu - > cmd_list ) ;
if ( smu - > cmd_cur = = NULL )
smu_start_cmd ( ) ;
} else
list_add_tail ( & cmd - > link , & smu - > cmd_i2c_list ) ;
spin_unlock_irqrestore ( & smu - > lock , flags ) ;
return 0 ;
}
2005-11-07 06:29:02 +03:00
/*
* Handling of " partitions "
*/
static int smu_read_datablock ( u8 * dest , unsigned int addr , unsigned int len )
{
2006-10-01 10:28:10 +04:00
DECLARE_COMPLETION_ONSTACK ( comp ) ;
2005-11-07 06:29:02 +03:00
unsigned int chunk ;
struct smu_cmd cmd ;
int rc ;
u8 params [ 8 ] ;
/* We currently use a chunk size of 0xe. We could check the
* SMU firmware version and use bigger sizes though
*/
chunk = 0xe ;
while ( len ) {
unsigned int clen = min ( len , chunk ) ;
cmd . cmd = SMU_CMD_MISC_ee_COMMAND ;
cmd . data_len = 7 ;
cmd . data_buf = params ;
cmd . reply_len = chunk ;
cmd . reply_buf = dest ;
cmd . done = smu_done_complete ;
cmd . misc = & comp ;
params [ 0 ] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC ;
params [ 1 ] = 0x4 ;
* ( ( u32 * ) & params [ 2 ] ) = addr ;
params [ 6 ] = clen ;
rc = smu_queue_cmd ( & cmd ) ;
if ( rc )
return rc ;
wait_for_completion ( & comp ) ;
if ( cmd . status ! = 0 )
return rc ;
if ( cmd . reply_len ! = clen ) {
printk ( KERN_DEBUG " SMU: short read in "
" smu_read_datablock, got: %d, want: %d \n " ,
cmd . reply_len , clen ) ;
return - EIO ;
}
len - = clen ;
addr + = clen ;
dest + = clen ;
}
return 0 ;
}
static struct smu_sdbp_header * smu_create_sdb_partition ( int id )
{
2006-10-01 10:28:10 +04:00
DECLARE_COMPLETION_ONSTACK ( comp ) ;
2005-11-07 06:29:02 +03:00
struct smu_simple_cmd cmd ;
unsigned int addr , len , tlen ;
struct smu_sdbp_header * hdr ;
struct property * prop ;
/* First query the partition info */
2005-12-14 05:10:10 +03:00
DPRINTK ( " SMU: Query partition infos ... (irq=%d) \n " , smu - > db_irq ) ;
2005-11-07 06:29:02 +03:00
smu_queue_simple ( & cmd , SMU_CMD_PARTITION_COMMAND , 2 ,
smu_done_complete , & comp ,
SMU_CMD_PARTITION_LATEST , id ) ;
wait_for_completion ( & comp ) ;
2005-12-14 05:10:10 +03:00
DPRINTK ( " SMU: done, status: %d, reply_len: %d \n " ,
cmd . cmd . status , cmd . cmd . reply_len ) ;
2005-11-07 06:29:02 +03:00
/* Partition doesn't exist (or other error) */
if ( cmd . cmd . status ! = 0 | | cmd . cmd . reply_len ! = 6 )
return NULL ;
/* Fetch address and length from reply */
addr = * ( ( u16 * ) cmd . buffer ) ;
len = cmd . buffer [ 3 ] < < 2 ;
/* Calucluate total length to allocate, including the 17 bytes
* for " sdb-partition-XX " that we append at the end of the buffer
*/
tlen = sizeof ( struct property ) + len + 18 ;
prop = kcalloc ( tlen , 1 , GFP_KERNEL ) ;
if ( prop = = NULL )
return NULL ;
hdr = ( struct smu_sdbp_header * ) ( prop + 1 ) ;
prop - > name = ( ( char * ) prop ) + tlen - 18 ;
sprintf ( prop - > name , " sdb-partition-%02x " , id ) ;
prop - > length = len ;
prop - > value = ( unsigned char * ) hdr ;
prop - > next = NULL ;
/* Read the datablock */
if ( smu_read_datablock ( ( u8 * ) hdr , addr , len ) ) {
printk ( KERN_DEBUG " SMU: datablock read failed while reading "
" partition %02x ! \n " , id ) ;
goto failure ;
}
/* Got it, check a few things and create the property */
if ( hdr - > id ! = id ) {
printk ( KERN_DEBUG " SMU: Reading partition %02x and got "
" %02x ! \n " , id , hdr - > id ) ;
goto failure ;
}
if ( prom_add_property ( smu - > of_node , prop ) ) {
printk ( KERN_DEBUG " SMU: Failed creating sdb-partition-%02x "
" property ! \n " , id ) ;
goto failure ;
}
return hdr ;
failure :
kfree ( prop ) ;
return NULL ;
}
/* Note: Only allowed to return error code in pointers (using ERR_PTR)
* when interruptible is 1
*/
2006-07-12 09:40:29 +04:00
const struct smu_sdbp_header * __smu_get_sdb_partition ( int id ,
unsigned int * size , int interruptible )
2005-11-07 06:27:33 +03:00
{
char pname [ 32 ] ;
2006-07-12 09:40:29 +04:00
const struct smu_sdbp_header * part ;
2005-11-07 06:27:33 +03:00
if ( ! smu )
return NULL ;
sprintf ( pname , " sdb-partition-%02x " , id ) ;
2005-11-07 06:29:02 +03:00
2005-12-14 05:10:10 +03:00
DPRINTK ( " smu_get_sdb_partition(%02x) \n " , id ) ;
2005-11-07 06:29:02 +03:00
if ( interruptible ) {
int rc ;
2006-03-26 13:37:14 +04:00
rc = mutex_lock_interruptible ( & smu_part_access ) ;
2005-11-07 06:29:02 +03:00
if ( rc )
return ERR_PTR ( rc ) ;
} else
2006-03-26 13:37:14 +04:00
mutex_lock ( & smu_part_access ) ;
2005-11-07 06:29:02 +03:00
2006-07-12 09:40:29 +04:00
part = get_property ( smu - > of_node , pname , size ) ;
2005-11-07 06:29:02 +03:00
if ( part = = NULL ) {
2005-12-14 05:10:10 +03:00
DPRINTK ( " trying to extract from SMU ... \n " ) ;
2005-11-07 06:29:02 +03:00
part = smu_create_sdb_partition ( id ) ;
if ( part ! = NULL & & size )
* size = part - > len < < 2 ;
}
2006-03-26 13:37:14 +04:00
mutex_unlock ( & smu_part_access ) ;
2005-11-07 06:29:02 +03:00
return part ;
}
2006-07-12 09:40:29 +04:00
const struct smu_sdbp_header * smu_get_sdb_partition ( int id , unsigned int * size )
2005-11-07 06:29:02 +03:00
{
return __smu_get_sdb_partition ( id , size , 0 ) ;
2005-11-07 06:27:33 +03:00
}
EXPORT_SYMBOL ( smu_get_sdb_partition ) ;
2005-09-23 08:44:06 +04:00
/*
* Userland driver interface
*/
static LIST_HEAD ( smu_clist ) ;
static DEFINE_SPINLOCK ( smu_clist_lock ) ;
enum smu_file_mode {
smu_file_commands ,
smu_file_events ,
smu_file_closing
} ;
struct smu_private
{
struct list_head list ;
enum smu_file_mode mode ;
int busy ;
struct smu_cmd cmd ;
spinlock_t lock ;
wait_queue_head_t wait ;
u8 buffer [ SMU_MAX_DATA ] ;
} ;
static int smu_open ( struct inode * inode , struct file * file )
{
struct smu_private * pp ;
unsigned long flags ;
pp = kmalloc ( sizeof ( struct smu_private ) , GFP_KERNEL ) ;
if ( pp = = 0 )
return - ENOMEM ;
memset ( pp , 0 , sizeof ( struct smu_private ) ) ;
spin_lock_init ( & pp - > lock ) ;
pp - > mode = smu_file_commands ;
init_waitqueue_head ( & pp - > wait ) ;
spin_lock_irqsave ( & smu_clist_lock , flags ) ;
list_add ( & pp - > list , & smu_clist ) ;
spin_unlock_irqrestore ( & smu_clist_lock , flags ) ;
file - > private_data = pp ;
return 0 ;
}
static void smu_user_cmd_done ( struct smu_cmd * cmd , void * misc )
{
struct smu_private * pp = misc ;
wake_up_all ( & pp - > wait ) ;
}
static ssize_t smu_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
struct smu_private * pp = file - > private_data ;
unsigned long flags ;
struct smu_user_cmd_hdr hdr ;
int rc = 0 ;
if ( pp - > busy )
return - EBUSY ;
else if ( copy_from_user ( & hdr , buf , sizeof ( hdr ) ) )
return - EFAULT ;
else if ( hdr . cmdtype = = SMU_CMDTYPE_WANTS_EVENTS ) {
pp - > mode = smu_file_events ;
return 0 ;
2005-11-07 06:29:02 +03:00
} else if ( hdr . cmdtype = = SMU_CMDTYPE_GET_PARTITION ) {
2006-07-12 09:40:29 +04:00
const struct smu_sdbp_header * part ;
2005-11-07 06:29:02 +03:00
part = __smu_get_sdb_partition ( hdr . cmd , NULL , 1 ) ;
if ( part = = NULL )
return - EINVAL ;
else if ( IS_ERR ( part ) )
return PTR_ERR ( part ) ;
return 0 ;
2005-09-23 08:44:06 +04:00
} else if ( hdr . cmdtype ! = SMU_CMDTYPE_SMU )
return - EINVAL ;
else if ( pp - > mode ! = smu_file_commands )
return - EBADFD ;
else if ( hdr . data_len > SMU_MAX_DATA )
return - EINVAL ;
spin_lock_irqsave ( & pp - > lock , flags ) ;
if ( pp - > busy ) {
spin_unlock_irqrestore ( & pp - > lock , flags ) ;
return - EBUSY ;
}
pp - > busy = 1 ;
pp - > cmd . status = 1 ;
spin_unlock_irqrestore ( & pp - > lock , flags ) ;
if ( copy_from_user ( pp - > buffer , buf + sizeof ( hdr ) , hdr . data_len ) ) {
pp - > busy = 0 ;
return - EFAULT ;
}
pp - > cmd . cmd = hdr . cmd ;
pp - > cmd . data_len = hdr . data_len ;
pp - > cmd . reply_len = SMU_MAX_DATA ;
pp - > cmd . data_buf = pp - > buffer ;
pp - > cmd . reply_buf = pp - > buffer ;
pp - > cmd . done = smu_user_cmd_done ;
pp - > cmd . misc = pp ;
rc = smu_queue_cmd ( & pp - > cmd ) ;
if ( rc < 0 )
return rc ;
return count ;
}
static ssize_t smu_read_command ( struct file * file , struct smu_private * pp ,
char __user * buf , size_t count )
{
DECLARE_WAITQUEUE ( wait , current ) ;
struct smu_user_reply_hdr hdr ;
unsigned long flags ;
int size , rc = 0 ;
if ( ! pp - > busy )
return 0 ;
if ( count < sizeof ( struct smu_user_reply_hdr ) )
return - EOVERFLOW ;
spin_lock_irqsave ( & pp - > lock , flags ) ;
if ( pp - > cmd . status = = 1 ) {
if ( file - > f_flags & O_NONBLOCK )
return - EAGAIN ;
add_wait_queue ( & pp - > wait , & wait ) ;
for ( ; ; ) {
set_current_state ( TASK_INTERRUPTIBLE ) ;
rc = 0 ;
if ( pp - > cmd . status ! = 1 )
break ;
rc = - ERESTARTSYS ;
if ( signal_pending ( current ) )
break ;
spin_unlock_irqrestore ( & pp - > lock , flags ) ;
schedule ( ) ;
spin_lock_irqsave ( & pp - > lock , flags ) ;
}
set_current_state ( TASK_RUNNING ) ;
remove_wait_queue ( & pp - > wait , & wait ) ;
}
spin_unlock_irqrestore ( & pp - > lock , flags ) ;
if ( rc )
return rc ;
if ( pp - > cmd . status ! = 0 )
pp - > cmd . reply_len = 0 ;
size = sizeof ( hdr ) + pp - > cmd . reply_len ;
if ( count < size )
size = count ;
rc = size ;
hdr . status = pp - > cmd . status ;
hdr . reply_len = pp - > cmd . reply_len ;
if ( copy_to_user ( buf , & hdr , sizeof ( hdr ) ) )
return - EFAULT ;
size - = sizeof ( hdr ) ;
if ( size & & copy_to_user ( buf + sizeof ( hdr ) , pp - > buffer , size ) )
return - EFAULT ;
pp - > busy = 0 ;
return rc ;
}
static ssize_t smu_read_events ( struct file * file , struct smu_private * pp ,
char __user * buf , size_t count )
{
/* Not implemented */
msleep_interruptible ( 1000 ) ;
return 0 ;
}
static ssize_t smu_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
{
struct smu_private * pp = file - > private_data ;
if ( pp - > mode = = smu_file_commands )
return smu_read_command ( file , pp , buf , count ) ;
if ( pp - > mode = = smu_file_events )
return smu_read_events ( file , pp , buf , count ) ;
return - EBADFD ;
}
static unsigned int smu_fpoll ( struct file * file , poll_table * wait )
{
struct smu_private * pp = file - > private_data ;
unsigned int mask = 0 ;
unsigned long flags ;
if ( pp = = 0 )
return 0 ;
if ( pp - > mode = = smu_file_commands ) {
poll_wait ( file , & pp - > wait , wait ) ;
spin_lock_irqsave ( & pp - > lock , flags ) ;
if ( pp - > busy & & pp - > cmd . status ! = 1 )
mask | = POLLIN ;
spin_unlock_irqrestore ( & pp - > lock , flags ) ;
} if ( pp - > mode = = smu_file_events ) {
/* Not yet implemented */
}
return mask ;
}
static int smu_release ( struct inode * inode , struct file * file )
{
struct smu_private * pp = file - > private_data ;
unsigned long flags ;
unsigned int busy ;
if ( pp = = 0 )
return 0 ;
file - > private_data = NULL ;
/* Mark file as closing to avoid races with new request */
spin_lock_irqsave ( & pp - > lock , flags ) ;
pp - > mode = smu_file_closing ;
busy = pp - > busy ;
/* Wait for any pending request to complete */
if ( busy & & pp - > cmd . status = = 1 ) {
DECLARE_WAITQUEUE ( wait , current ) ;
add_wait_queue ( & pp - > wait , & wait ) ;
for ( ; ; ) {
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
if ( pp - > cmd . status ! = 1 )
break ;
spin_lock_irqsave ( & pp - > lock , flags ) ;
schedule ( ) ;
spin_unlock_irqrestore ( & pp - > lock , flags ) ;
}
set_current_state ( TASK_RUNNING ) ;
remove_wait_queue ( & pp - > wait , & wait ) ;
}
spin_unlock_irqrestore ( & pp - > lock , flags ) ;
spin_lock_irqsave ( & smu_clist_lock , flags ) ;
list_del ( & pp - > list ) ;
spin_unlock_irqrestore ( & smu_clist_lock , flags ) ;
kfree ( pp ) ;
return 0 ;
}
2005-09-27 08:09:39 +04:00
static struct file_operations smu_device_fops = {
2005-09-23 08:44:06 +04:00
. llseek = no_llseek ,
. read = smu_read ,
. write = smu_write ,
. poll = smu_fpoll ,
. open = smu_open ,
. release = smu_release ,
} ;
2005-09-27 08:09:39 +04:00
static struct miscdevice pmu_device = {
2005-09-23 08:44:06 +04:00
MISC_DYNAMIC_MINOR , " smu " , & smu_device_fops
} ;
static int smu_device_init ( void )
{
if ( ! smu )
return - ENODEV ;
if ( misc_register ( & pmu_device ) < 0 )
printk ( KERN_ERR " via-pmu: cannot register misc device. \n " ) ;
return 0 ;
}
device_initcall ( smu_device_init ) ;