2009-04-07 15:32:59 +02:00
/*
* Copyright ( C ) 2007 - 2009 Luca Tettamanti < kronos . it @ gmail . com >
*
* This file is released under the GPLv2
* See COPYING in the top level directory of the kernel tree .
*/
2010-01-10 20:52:33 +01:00
# include <linux/debugfs.h>
2009-04-07 15:32:59 +02:00
# include <linux/kernel.h>
# include <linux/hwmon.h>
# include <linux/list.h>
# include <linux/module.h>
# include <acpi/acpi.h>
# include <acpi/acpixf.h>
# include <acpi/acpi_drivers.h>
# include <acpi/acpi_bus.h>
# define ATK_HID "ATK0110"
/* Minimum time between readings, enforced in order to avoid
* hogging the CPU .
*/
# define CACHE_TIME HZ
# define BOARD_ID "MBIF"
# define METHOD_ENUMERATE "GGRP"
# define METHOD_READ "GITM"
# define METHOD_WRITE "SITM"
# define METHOD_OLD_READ_TMP "RTMP"
# define METHOD_OLD_READ_VLT "RVLT"
# define METHOD_OLD_READ_FAN "RFAN"
# define METHOD_OLD_ENUM_TMP "TSIF"
# define METHOD_OLD_ENUM_VLT "VSIF"
# define METHOD_OLD_ENUM_FAN "FSIF"
# define ATK_MUX_HWMON 0x00000006ULL
2009-10-09 20:35:18 +02:00
# define ATK_MUX_MGMT 0x00000011ULL
2009-04-07 15:32:59 +02:00
# define ATK_CLASS_MASK 0xff000000ULL
# define ATK_CLASS_FREQ_CTL 0x03000000ULL
# define ATK_CLASS_FAN_CTL 0x04000000ULL
# define ATK_CLASS_HWMON 0x06000000ULL
2009-10-09 20:35:18 +02:00
# define ATK_CLASS_MGMT 0x11000000ULL
2009-04-07 15:32:59 +02:00
# define ATK_TYPE_MASK 0x00ff0000ULL
# define HWMON_TYPE_VOLT 0x00020000ULL
# define HWMON_TYPE_TEMP 0x00030000ULL
# define HWMON_TYPE_FAN 0x00040000ULL
2009-10-09 20:35:18 +02:00
# define ATK_ELEMENT_ID_MASK 0x0000ffffULL
# define ATK_EC_ID 0x11060004ULL
2009-04-07 15:32:59 +02:00
enum atk_pack_member {
HWMON_PACK_FLAGS ,
HWMON_PACK_NAME ,
HWMON_PACK_LIMIT1 ,
HWMON_PACK_LIMIT2 ,
HWMON_PACK_ENABLE
} ;
/* New package format */
# define _HWMON_NEW_PACK_SIZE 7
# define _HWMON_NEW_PACK_FLAGS 0
# define _HWMON_NEW_PACK_NAME 1
# define _HWMON_NEW_PACK_UNK1 2
# define _HWMON_NEW_PACK_UNK2 3
# define _HWMON_NEW_PACK_LIMIT1 4
# define _HWMON_NEW_PACK_LIMIT2 5
# define _HWMON_NEW_PACK_ENABLE 6
/* Old package format */
# define _HWMON_OLD_PACK_SIZE 5
# define _HWMON_OLD_PACK_FLAGS 0
# define _HWMON_OLD_PACK_NAME 1
# define _HWMON_OLD_PACK_LIMIT1 2
# define _HWMON_OLD_PACK_LIMIT2 3
# define _HWMON_OLD_PACK_ENABLE 4
struct atk_data {
struct device * hwmon_dev ;
acpi_handle atk_handle ;
struct acpi_device * acpi_dev ;
bool old_interface ;
/* old interface */
acpi_handle rtmp_handle ;
acpi_handle rvlt_handle ;
acpi_handle rfan_handle ;
/* new inteface */
acpi_handle enumerate_handle ;
acpi_handle read_handle ;
2009-10-09 20:35:18 +02:00
acpi_handle write_handle ;
bool disable_ec ;
2009-04-07 15:32:59 +02:00
int voltage_count ;
int temperature_count ;
int fan_count ;
struct list_head sensor_list ;
2010-01-10 20:52:33 +01:00
struct {
struct dentry * root ;
u32 id ;
} debugfs ;
2009-04-07 15:32:59 +02:00
} ;
typedef ssize_t ( * sysfs_show_func ) ( struct device * dev ,
struct device_attribute * attr , char * buf ) ;
static const struct acpi_device_id atk_ids [ ] = {
{ ATK_HID , 0 } ,
{ " " , 0 } ,
} ;
MODULE_DEVICE_TABLE ( acpi , atk_ids ) ;
# define ATTR_NAME_SIZE 16 /* Worst case is "tempN_input" */
struct atk_sensor_data {
struct list_head list ;
struct atk_data * data ;
struct device_attribute label_attr ;
struct device_attribute input_attr ;
struct device_attribute limit1_attr ;
struct device_attribute limit2_attr ;
char label_attr_name [ ATTR_NAME_SIZE ] ;
char input_attr_name [ ATTR_NAME_SIZE ] ;
char limit1_attr_name [ ATTR_NAME_SIZE ] ;
char limit2_attr_name [ ATTR_NAME_SIZE ] ;
u64 id ;
u64 type ;
u64 limit1 ;
u64 limit2 ;
u64 cached_value ;
unsigned long last_updated ; /* in jiffies */
bool is_valid ;
char const * acpi_name ;
} ;
2009-10-09 20:35:18 +02:00
/* Return buffer format:
* [ 0 - 3 ] " value " is valid flag
* [ 4 - 7 ] value
* [ 8 - ] unknown stuff on newer mobos
*/
struct atk_acpi_ret_buffer {
u32 flags ;
u32 value ;
u8 data [ ] ;
} ;
/* Input buffer used for GITM and SITM methods */
struct atk_acpi_input_buf {
u32 id ;
u32 param1 ;
u32 param2 ;
2009-04-07 15:32:59 +02:00
} ;
static int atk_add ( struct acpi_device * device ) ;
static int atk_remove ( struct acpi_device * device , int type ) ;
static void atk_print_sensor ( struct atk_data * data , union acpi_object * obj ) ;
static int atk_read_value ( struct atk_sensor_data * sensor , u64 * value ) ;
static void atk_free_sensors ( struct atk_data * data ) ;
static struct acpi_driver atk_driver = {
. name = ATK_HID ,
. class = " hwmon " ,
. ids = atk_ids ,
. ops = {
. add = atk_add ,
. remove = atk_remove ,
} ,
} ;
# define input_to_atk_sensor(attr) \
container_of ( attr , struct atk_sensor_data , input_attr )
# define label_to_atk_sensor(attr) \
container_of ( attr , struct atk_sensor_data , label_attr )
# define limit1_to_atk_sensor(attr) \
container_of ( attr , struct atk_sensor_data , limit1_attr )
# define limit2_to_atk_sensor(attr) \
container_of ( attr , struct atk_sensor_data , limit2_attr )
static ssize_t atk_input_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct atk_sensor_data * s = input_to_atk_sensor ( attr ) ;
u64 value ;
int err ;
err = atk_read_value ( s , & value ) ;
if ( err )
return err ;
if ( s - > type = = HWMON_TYPE_TEMP )
/* ACPI returns decidegree */
value * = 100 ;
return sprintf ( buf , " %llu \n " , value ) ;
}
static ssize_t atk_label_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct atk_sensor_data * s = label_to_atk_sensor ( attr ) ;
return sprintf ( buf , " %s \n " , s - > acpi_name ) ;
}
static ssize_t atk_limit1_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct atk_sensor_data * s = limit1_to_atk_sensor ( attr ) ;
u64 value = s - > limit1 ;
if ( s - > type = = HWMON_TYPE_TEMP )
value * = 100 ;
return sprintf ( buf , " %lld \n " , value ) ;
}
static ssize_t atk_limit2_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct atk_sensor_data * s = limit2_to_atk_sensor ( attr ) ;
u64 value = s - > limit2 ;
if ( s - > type = = HWMON_TYPE_TEMP )
value * = 100 ;
return sprintf ( buf , " %lld \n " , value ) ;
}
static ssize_t atk_name_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " atk0110 \n " ) ;
}
static struct device_attribute atk_name_attr =
__ATTR ( name , 0444 , atk_name_show , NULL ) ;
static void atk_init_attribute ( struct device_attribute * attr , char * name ,
sysfs_show_func show )
{
attr - > attr . name = name ;
attr - > attr . mode = 0444 ;
attr - > show = show ;
attr - > store = NULL ;
}
static union acpi_object * atk_get_pack_member ( struct atk_data * data ,
union acpi_object * pack ,
enum atk_pack_member m )
{
bool old_if = data - > old_interface ;
int offset ;
switch ( m ) {
case HWMON_PACK_FLAGS :
offset = old_if ? _HWMON_OLD_PACK_FLAGS : _HWMON_NEW_PACK_FLAGS ;
break ;
case HWMON_PACK_NAME :
offset = old_if ? _HWMON_OLD_PACK_NAME : _HWMON_NEW_PACK_NAME ;
break ;
case HWMON_PACK_LIMIT1 :
offset = old_if ? _HWMON_OLD_PACK_LIMIT1 :
_HWMON_NEW_PACK_LIMIT1 ;
break ;
case HWMON_PACK_LIMIT2 :
offset = old_if ? _HWMON_OLD_PACK_LIMIT2 :
_HWMON_NEW_PACK_LIMIT2 ;
break ;
case HWMON_PACK_ENABLE :
offset = old_if ? _HWMON_OLD_PACK_ENABLE :
_HWMON_NEW_PACK_ENABLE ;
break ;
default :
return NULL ;
}
return & pack - > package . elements [ offset ] ;
}
/* New package format is:
* - flag ( int )
* class - used for de - muxing the request to the correct GITn
* type ( volt , temp , fan )
* sensor id |
* sensor id - used for de - muxing the request _inside_ the GITn
* - name ( str )
* - unknown ( int )
* - unknown ( int )
* - limit1 ( int )
* - limit2 ( int )
* - enable ( int )
*
* The old package has the same format but it ' s missing the two unknown fields .
*/
static int validate_hwmon_pack ( struct atk_data * data , union acpi_object * obj )
{
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * tmp ;
bool old_if = data - > old_interface ;
int const expected_size = old_if ? _HWMON_OLD_PACK_SIZE :
_HWMON_NEW_PACK_SIZE ;
if ( obj - > type ! = ACPI_TYPE_PACKAGE ) {
dev_warn ( dev , " Invalid type: %d \n " , obj - > type ) ;
return - EINVAL ;
}
if ( obj - > package . count ! = expected_size ) {
dev_warn ( dev , " Invalid package size: %d, expected: %d \n " ,
obj - > package . count , expected_size ) ;
return - EINVAL ;
}
tmp = atk_get_pack_member ( data , obj , HWMON_PACK_FLAGS ) ;
if ( tmp - > type ! = ACPI_TYPE_INTEGER ) {
dev_warn ( dev , " Invalid type (flag): %d \n " , tmp - > type ) ;
return - EINVAL ;
}
tmp = atk_get_pack_member ( data , obj , HWMON_PACK_NAME ) ;
if ( tmp - > type ! = ACPI_TYPE_STRING ) {
dev_warn ( dev , " Invalid type (name): %d \n " , tmp - > type ) ;
return - EINVAL ;
}
/* Don't check... we don't know what they're useful for anyway */
#if 0
tmp = & obj - > package . elements [ HWMON_PACK_UNK1 ] ;
if ( tmp - > type ! = ACPI_TYPE_INTEGER ) {
dev_warn ( dev , " Invalid type (unk1): %d \n " , tmp - > type ) ;
return - EINVAL ;
}
tmp = & obj - > package . elements [ HWMON_PACK_UNK2 ] ;
if ( tmp - > type ! = ACPI_TYPE_INTEGER ) {
dev_warn ( dev , " Invalid type (unk2): %d \n " , tmp - > type ) ;
return - EINVAL ;
}
# endif
tmp = atk_get_pack_member ( data , obj , HWMON_PACK_LIMIT1 ) ;
if ( tmp - > type ! = ACPI_TYPE_INTEGER ) {
dev_warn ( dev , " Invalid type (limit1): %d \n " , tmp - > type ) ;
return - EINVAL ;
}
tmp = atk_get_pack_member ( data , obj , HWMON_PACK_LIMIT2 ) ;
if ( tmp - > type ! = ACPI_TYPE_INTEGER ) {
dev_warn ( dev , " Invalid type (limit2): %d \n " , tmp - > type ) ;
return - EINVAL ;
}
tmp = atk_get_pack_member ( data , obj , HWMON_PACK_ENABLE ) ;
if ( tmp - > type ! = ACPI_TYPE_INTEGER ) {
dev_warn ( dev , " Invalid type (enable): %d \n " , tmp - > type ) ;
return - EINVAL ;
}
atk_print_sensor ( data , obj ) ;
return 0 ;
}
2009-05-08 20:27:28 +02:00
# ifdef DEBUG
2009-04-07 15:32:59 +02:00
static char const * atk_sensor_type ( union acpi_object * flags )
{
u64 type = flags - > integer . value & ATK_TYPE_MASK ;
char const * what ;
switch ( type ) {
case HWMON_TYPE_VOLT :
what = " voltage " ;
break ;
case HWMON_TYPE_TEMP :
what = " temperature " ;
break ;
case HWMON_TYPE_FAN :
what = " fan " ;
break ;
default :
what = " unknown " ;
break ;
}
return what ;
}
2009-05-08 20:27:28 +02:00
# endif
2009-04-07 15:32:59 +02:00
static void atk_print_sensor ( struct atk_data * data , union acpi_object * obj )
{
# ifdef DEBUG
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * flags ;
union acpi_object * name ;
union acpi_object * limit1 ;
union acpi_object * limit2 ;
union acpi_object * enable ;
char const * what ;
flags = atk_get_pack_member ( data , obj , HWMON_PACK_FLAGS ) ;
name = atk_get_pack_member ( data , obj , HWMON_PACK_NAME ) ;
limit1 = atk_get_pack_member ( data , obj , HWMON_PACK_LIMIT1 ) ;
limit2 = atk_get_pack_member ( data , obj , HWMON_PACK_LIMIT2 ) ;
enable = atk_get_pack_member ( data , obj , HWMON_PACK_ENABLE ) ;
what = atk_sensor_type ( flags ) ;
dev_dbg ( dev , " %s: %#llx %s [%llu-%llu] %s \n " , what ,
flags - > integer . value ,
name - > string . pointer ,
limit1 - > integer . value , limit2 - > integer . value ,
enable - > integer . value ? " enabled " : " disabled " ) ;
# endif
}
static int atk_read_value_old ( struct atk_sensor_data * sensor , u64 * value )
{
struct atk_data * data = sensor - > data ;
struct device * dev = & data - > acpi_dev - > dev ;
struct acpi_object_list params ;
union acpi_object id ;
acpi_status status ;
acpi_handle method ;
switch ( sensor - > type ) {
case HWMON_TYPE_VOLT :
method = data - > rvlt_handle ;
break ;
case HWMON_TYPE_TEMP :
method = data - > rtmp_handle ;
break ;
case HWMON_TYPE_FAN :
method = data - > rfan_handle ;
break ;
default :
return - EINVAL ;
}
id . type = ACPI_TYPE_INTEGER ;
id . integer . value = sensor - > id ;
params . count = 1 ;
params . pointer = & id ;
status = acpi_evaluate_integer ( method , NULL , & params , value ) ;
if ( status ! = AE_OK ) {
dev_warn ( dev , " %s: ACPI exception: %s \n " , __func__ ,
acpi_format_exception ( status ) ) ;
return - EIO ;
}
return 0 ;
}
2009-10-09 20:35:18 +02:00
static union acpi_object * atk_ggrp ( struct atk_data * data , u16 mux )
2009-04-07 15:32:59 +02:00
{
struct device * dev = & data - > acpi_dev - > dev ;
2009-10-09 20:35:18 +02:00
struct acpi_buffer buf ;
acpi_status ret ;
2009-04-07 15:32:59 +02:00
struct acpi_object_list params ;
union acpi_object id ;
2009-10-09 20:35:18 +02:00
union acpi_object * pack ;
2009-04-07 15:32:59 +02:00
id . type = ACPI_TYPE_INTEGER ;
2009-10-09 20:35:18 +02:00
id . integer . value = mux ;
2009-04-07 15:32:59 +02:00
params . count = 1 ;
params . pointer = & id ;
2009-10-09 20:35:18 +02:00
buf . length = ACPI_ALLOCATE_BUFFER ;
ret = acpi_evaluate_object ( data - > enumerate_handle , NULL , & params , & buf ) ;
if ( ret ! = AE_OK ) {
dev_err ( dev , " GGRP[%#x] ACPI exception: %s \n " , mux ,
acpi_format_exception ( ret ) ) ;
return ERR_PTR ( - EIO ) ;
}
pack = buf . pointer ;
if ( pack - > type ! = ACPI_TYPE_PACKAGE ) {
/* Execution was successful, but the id was not found */
ACPI_FREE ( pack ) ;
return ERR_PTR ( - ENOENT ) ;
}
if ( pack - > package . count < 1 ) {
dev_err ( dev , " GGRP[%#x] package is too small \n " , mux ) ;
ACPI_FREE ( pack ) ;
return ERR_PTR ( - EIO ) ;
}
return pack ;
}
2009-04-07 15:32:59 +02:00
2009-10-09 20:35:18 +02:00
static union acpi_object * atk_gitm ( struct atk_data * data , u64 id )
{
struct device * dev = & data - > acpi_dev - > dev ;
struct atk_acpi_input_buf buf ;
union acpi_object tmp ;
struct acpi_object_list params ;
struct acpi_buffer ret ;
union acpi_object * obj ;
acpi_status status ;
buf . id = id ;
buf . param1 = 0 ;
buf . param2 = 0 ;
tmp . type = ACPI_TYPE_BUFFER ;
tmp . buffer . pointer = ( u8 * ) & buf ;
tmp . buffer . length = sizeof ( buf ) ;
params . count = 1 ;
params . pointer = ( void * ) & tmp ;
ret . length = ACPI_ALLOCATE_BUFFER ;
2009-04-07 15:32:59 +02:00
status = acpi_evaluate_object_typed ( data - > read_handle , NULL , & params ,
& ret , ACPI_TYPE_BUFFER ) ;
if ( status ! = AE_OK ) {
2009-10-09 20:35:18 +02:00
dev_warn ( dev , " GITM[%#llx] ACPI exception: %s \n " , id ,
2009-04-07 15:32:59 +02:00
acpi_format_exception ( status ) ) ;
2009-10-09 20:35:18 +02:00
return ERR_PTR ( - EIO ) ;
2009-04-07 15:32:59 +02:00
}
2009-10-09 20:35:18 +02:00
obj = ret . pointer ;
/* Sanity check */
if ( obj - > buffer . length < 8 ) {
dev_warn ( dev , " Unexpected ASBF length: %u \n " ,
obj - > buffer . length ) ;
ACPI_FREE ( obj ) ;
return ERR_PTR ( - EIO ) ;
}
return obj ;
}
2009-04-07 15:32:59 +02:00
2009-10-09 20:35:18 +02:00
static union acpi_object * atk_sitm ( struct atk_data * data ,
struct atk_acpi_input_buf * buf )
{
struct device * dev = & data - > acpi_dev - > dev ;
struct acpi_object_list params ;
union acpi_object tmp ;
struct acpi_buffer ret ;
union acpi_object * obj ;
acpi_status status ;
tmp . type = ACPI_TYPE_BUFFER ;
tmp . buffer . pointer = ( u8 * ) buf ;
tmp . buffer . length = sizeof ( * buf ) ;
params . count = 1 ;
params . pointer = & tmp ;
ret . length = ACPI_ALLOCATE_BUFFER ;
status = acpi_evaluate_object_typed ( data - > write_handle , NULL , & params ,
& ret , ACPI_TYPE_BUFFER ) ;
if ( status ! = AE_OK ) {
dev_warn ( dev , " SITM[%#x] ACPI exception: %s \n " , buf - > id ,
acpi_format_exception ( status ) ) ;
return ERR_PTR ( - EIO ) ;
}
obj = ret . pointer ;
/* Sanity check */
if ( obj - > buffer . length < 8 ) {
dev_warn ( dev , " Unexpected ASBF length: %u \n " ,
obj - > buffer . length ) ;
ACPI_FREE ( obj ) ;
return ERR_PTR ( - EIO ) ;
}
return obj ;
}
2009-10-09 20:35:18 +02:00
static int atk_read_value_new ( struct atk_sensor_data * sensor , u64 * value )
{
struct atk_data * data = sensor - > data ;
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * obj ;
struct atk_acpi_ret_buffer * buf ;
int err = 0 ;
obj = atk_gitm ( data , sensor - > id ) ;
if ( IS_ERR ( obj ) )
return PTR_ERR ( obj ) ;
buf = ( struct atk_acpi_ret_buffer * ) obj - > buffer . pointer ;
if ( buf - > flags = = 0 ) {
2009-04-07 15:32:59 +02:00
/* The reading is not valid, possible causes:
* - sensor failure
* - enumeration was FUBAR ( and we didn ' t notice )
*/
2009-10-09 20:35:18 +02:00
dev_warn ( dev , " Read failed, sensor = %#llx \n " , sensor - > id ) ;
err = - EIO ;
goto out ;
2009-04-07 15:32:59 +02:00
}
2009-10-09 20:35:18 +02:00
* value = buf - > value ;
out :
ACPI_FREE ( obj ) ;
return err ;
2009-04-07 15:32:59 +02:00
}
static int atk_read_value ( struct atk_sensor_data * sensor , u64 * value )
{
int err ;
if ( ! sensor - > is_valid | |
time_after ( jiffies , sensor - > last_updated + CACHE_TIME ) ) {
if ( sensor - > data - > old_interface )
err = atk_read_value_old ( sensor , value ) ;
else
err = atk_read_value_new ( sensor , value ) ;
sensor - > is_valid = true ;
sensor - > last_updated = jiffies ;
sensor - > cached_value = * value ;
} else {
* value = sensor - > cached_value ;
err = 0 ;
}
return err ;
}
2010-01-10 20:52:33 +01:00
# ifdef CONFIG_DEBUG_FS
static int atk_debugfs_gitm_get ( void * p , u64 * val )
{
struct atk_data * data = p ;
union acpi_object * ret ;
struct atk_acpi_ret_buffer * buf ;
int err = 0 ;
if ( ! data - > read_handle )
return - ENODEV ;
if ( ! data - > debugfs . id )
return - EINVAL ;
ret = atk_gitm ( data , data - > debugfs . id ) ;
if ( IS_ERR ( ret ) )
return PTR_ERR ( ret ) ;
buf = ( struct atk_acpi_ret_buffer * ) ret - > buffer . pointer ;
if ( buf - > flags )
* val = buf - > value ;
else
err = - EIO ;
return err ;
}
DEFINE_SIMPLE_ATTRIBUTE ( atk_debugfs_gitm ,
atk_debugfs_gitm_get ,
NULL ,
" 0x%08llx \n " )
static int atk_acpi_print ( char * buf , size_t sz , union acpi_object * obj )
{
int ret = 0 ;
switch ( obj - > type ) {
case ACPI_TYPE_INTEGER :
ret = snprintf ( buf , sz , " 0x%08llx \n " , obj - > integer . value ) ;
break ;
case ACPI_TYPE_STRING :
ret = snprintf ( buf , sz , " %s \n " , obj - > string . pointer ) ;
break ;
}
return ret ;
}
static void atk_pack_print ( char * buf , size_t sz , union acpi_object * pack )
{
int ret ;
int i ;
for ( i = 0 ; i < pack - > package . count ; i + + ) {
union acpi_object * obj = & pack - > package . elements [ i ] ;
ret = atk_acpi_print ( buf , sz , obj ) ;
if ( ret > = sz )
break ;
buf + = ret ;
sz - = ret ;
}
}
static int atk_debugfs_ggrp_open ( struct inode * inode , struct file * file )
{
struct atk_data * data = inode - > i_private ;
char * buf = NULL ;
union acpi_object * ret ;
u8 cls ;
int i ;
if ( ! data - > enumerate_handle )
return - ENODEV ;
if ( ! data - > debugfs . id )
return - EINVAL ;
cls = ( data - > debugfs . id & 0xff000000 ) > > 24 ;
ret = atk_ggrp ( data , cls ) ;
if ( IS_ERR ( ret ) )
return PTR_ERR ( ret ) ;
for ( i = 0 ; i < ret - > package . count ; i + + ) {
union acpi_object * pack = & ret - > package . elements [ i ] ;
union acpi_object * id ;
if ( pack - > type ! = ACPI_TYPE_PACKAGE )
continue ;
if ( ! pack - > package . count )
continue ;
id = & pack - > package . elements [ 0 ] ;
if ( id - > integer . value = = data - > debugfs . id ) {
/* Print the package */
buf = kzalloc ( 512 , GFP_KERNEL ) ;
if ( ! buf ) {
ACPI_FREE ( ret ) ;
return - ENOMEM ;
}
atk_pack_print ( buf , 512 , pack ) ;
break ;
}
}
ACPI_FREE ( ret ) ;
if ( ! buf )
return - EINVAL ;
file - > private_data = buf ;
return nonseekable_open ( inode , file ) ;
}
static ssize_t atk_debugfs_ggrp_read ( struct file * file , char __user * buf ,
size_t count , loff_t * pos )
{
char * str = file - > private_data ;
size_t len = strlen ( str ) ;
return simple_read_from_buffer ( buf , count , pos , str , len ) ;
}
static int atk_debugfs_ggrp_release ( struct inode * inode , struct file * file )
{
kfree ( file - > private_data ) ;
return 0 ;
}
static const struct file_operations atk_debugfs_ggrp_fops = {
. read = atk_debugfs_ggrp_read ,
. open = atk_debugfs_ggrp_open ,
. release = atk_debugfs_ggrp_release ,
} ;
static void atk_debugfs_init ( struct atk_data * data )
{
struct dentry * d ;
struct dentry * f ;
data - > debugfs . id = 0 ;
d = debugfs_create_dir ( " asus_atk0110 " , NULL ) ;
if ( ! d | | IS_ERR ( d ) )
return ;
f = debugfs_create_x32 ( " id " , S_IRUSR | S_IWUSR , d , & data - > debugfs . id ) ;
if ( ! f | | IS_ERR ( f ) )
goto cleanup ;
f = debugfs_create_file ( " gitm " , S_IRUSR , d , data ,
& atk_debugfs_gitm ) ;
if ( ! f | | IS_ERR ( f ) )
goto cleanup ;
f = debugfs_create_file ( " ggrp " , S_IRUSR , d , data ,
& atk_debugfs_ggrp_fops ) ;
if ( ! f | | IS_ERR ( f ) )
goto cleanup ;
data - > debugfs . root = d ;
return ;
cleanup :
debugfs_remove_recursive ( d ) ;
}
static void atk_debugfs_cleanup ( struct atk_data * data )
{
debugfs_remove_recursive ( data - > debugfs . root ) ;
}
# else /* CONFIG_DEBUG_FS */
static void atk_debugfs_init ( struct atk_data * data )
{
}
static void atk_debugfs_cleanup ( struct atk_data * data )
{
}
# endif
2009-04-07 15:32:59 +02:00
static int atk_add_sensor ( struct atk_data * data , union acpi_object * obj )
{
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * flags ;
union acpi_object * name ;
union acpi_object * limit1 ;
union acpi_object * limit2 ;
union acpi_object * enable ;
struct atk_sensor_data * sensor ;
char const * base_name ;
char const * limit1_name ;
char const * limit2_name ;
u64 type ;
int err ;
int * num ;
int start ;
if ( obj - > type ! = ACPI_TYPE_PACKAGE ) {
/* wft is this? */
dev_warn ( dev , " Unknown type for ACPI object: (%d) \n " ,
obj - > type ) ;
return - EINVAL ;
}
err = validate_hwmon_pack ( data , obj ) ;
if ( err )
return err ;
/* Ok, we have a valid hwmon package */
type = atk_get_pack_member ( data , obj , HWMON_PACK_FLAGS ) - > integer . value
& ATK_TYPE_MASK ;
switch ( type ) {
case HWMON_TYPE_VOLT :
base_name = " in " ;
limit1_name = " min " ;
limit2_name = " max " ;
num = & data - > voltage_count ;
start = 0 ;
break ;
case HWMON_TYPE_TEMP :
base_name = " temp " ;
limit1_name = " max " ;
limit2_name = " crit " ;
num = & data - > temperature_count ;
start = 1 ;
break ;
case HWMON_TYPE_FAN :
base_name = " fan " ;
limit1_name = " min " ;
limit2_name = " max " ;
num = & data - > fan_count ;
start = 1 ;
break ;
default :
dev_warn ( dev , " Unknown sensor type: %#llx \n " , type ) ;
return - EINVAL ;
}
enable = atk_get_pack_member ( data , obj , HWMON_PACK_ENABLE ) ;
if ( ! enable - > integer . value )
/* sensor is disabled */
return 0 ;
flags = atk_get_pack_member ( data , obj , HWMON_PACK_FLAGS ) ;
name = atk_get_pack_member ( data , obj , HWMON_PACK_NAME ) ;
limit1 = atk_get_pack_member ( data , obj , HWMON_PACK_LIMIT1 ) ;
limit2 = atk_get_pack_member ( data , obj , HWMON_PACK_LIMIT2 ) ;
sensor = kzalloc ( sizeof ( * sensor ) , GFP_KERNEL ) ;
if ( ! sensor )
return - ENOMEM ;
sensor - > acpi_name = kstrdup ( name - > string . pointer , GFP_KERNEL ) ;
if ( ! sensor - > acpi_name ) {
err = - ENOMEM ;
goto out ;
}
INIT_LIST_HEAD ( & sensor - > list ) ;
sensor - > type = type ;
sensor - > data = data ;
sensor - > id = flags - > integer . value ;
sensor - > limit1 = limit1 - > integer . value ;
2009-07-28 16:31:39 +02:00
if ( data - > old_interface )
sensor - > limit2 = limit2 - > integer . value ;
else
/* The upper limit is expressed as delta from lower limit */
sensor - > limit2 = sensor - > limit1 + limit2 - > integer . value ;
2009-04-07 15:32:59 +02:00
snprintf ( sensor - > input_attr_name , ATTR_NAME_SIZE ,
" %s%d_input " , base_name , start + * num ) ;
atk_init_attribute ( & sensor - > input_attr ,
sensor - > input_attr_name ,
atk_input_show ) ;
snprintf ( sensor - > label_attr_name , ATTR_NAME_SIZE ,
" %s%d_label " , base_name , start + * num ) ;
atk_init_attribute ( & sensor - > label_attr ,
sensor - > label_attr_name ,
atk_label_show ) ;
snprintf ( sensor - > limit1_attr_name , ATTR_NAME_SIZE ,
" %s%d_%s " , base_name , start + * num , limit1_name ) ;
atk_init_attribute ( & sensor - > limit1_attr ,
sensor - > limit1_attr_name ,
atk_limit1_show ) ;
snprintf ( sensor - > limit2_attr_name , ATTR_NAME_SIZE ,
" %s%d_%s " , base_name , start + * num , limit2_name ) ;
atk_init_attribute ( & sensor - > limit2_attr ,
sensor - > limit2_attr_name ,
atk_limit2_show ) ;
list_add ( & sensor - > list , & data - > sensor_list ) ;
( * num ) + + ;
return 1 ;
out :
kfree ( sensor - > acpi_name ) ;
kfree ( sensor ) ;
return err ;
}
static int atk_enumerate_old_hwmon ( struct atk_data * data )
{
struct device * dev = & data - > acpi_dev - > dev ;
struct acpi_buffer buf ;
union acpi_object * pack ;
acpi_status status ;
int i , ret ;
int count = 0 ;
/* Voltages */
buf . length = ACPI_ALLOCATE_BUFFER ;
status = acpi_evaluate_object_typed ( data - > atk_handle ,
METHOD_OLD_ENUM_VLT , NULL , & buf , ACPI_TYPE_PACKAGE ) ;
if ( status ! = AE_OK ) {
dev_warn ( dev , METHOD_OLD_ENUM_VLT " : ACPI exception: %s \n " ,
acpi_format_exception ( status ) ) ;
return - ENODEV ;
}
pack = buf . pointer ;
for ( i = 1 ; i < pack - > package . count ; i + + ) {
union acpi_object * obj = & pack - > package . elements [ i ] ;
ret = atk_add_sensor ( data , obj ) ;
if ( ret > 0 )
count + + ;
}
ACPI_FREE ( buf . pointer ) ;
/* Temperatures */
buf . length = ACPI_ALLOCATE_BUFFER ;
status = acpi_evaluate_object_typed ( data - > atk_handle ,
METHOD_OLD_ENUM_TMP , NULL , & buf , ACPI_TYPE_PACKAGE ) ;
if ( status ! = AE_OK ) {
dev_warn ( dev , METHOD_OLD_ENUM_TMP " : ACPI exception: %s \n " ,
acpi_format_exception ( status ) ) ;
ret = - ENODEV ;
goto cleanup ;
}
pack = buf . pointer ;
for ( i = 1 ; i < pack - > package . count ; i + + ) {
union acpi_object * obj = & pack - > package . elements [ i ] ;
ret = atk_add_sensor ( data , obj ) ;
if ( ret > 0 )
count + + ;
}
ACPI_FREE ( buf . pointer ) ;
/* Fans */
buf . length = ACPI_ALLOCATE_BUFFER ;
status = acpi_evaluate_object_typed ( data - > atk_handle ,
METHOD_OLD_ENUM_FAN , NULL , & buf , ACPI_TYPE_PACKAGE ) ;
if ( status ! = AE_OK ) {
dev_warn ( dev , METHOD_OLD_ENUM_FAN " : ACPI exception: %s \n " ,
acpi_format_exception ( status ) ) ;
ret = - ENODEV ;
goto cleanup ;
}
pack = buf . pointer ;
for ( i = 1 ; i < pack - > package . count ; i + + ) {
union acpi_object * obj = & pack - > package . elements [ i ] ;
ret = atk_add_sensor ( data , obj ) ;
if ( ret > 0 )
count + + ;
}
ACPI_FREE ( buf . pointer ) ;
return count ;
cleanup :
atk_free_sensors ( data ) ;
return ret ;
}
2009-10-09 20:35:18 +02:00
static int atk_ec_present ( struct atk_data * data )
{
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * pack ;
union acpi_object * ec ;
int ret ;
int i ;
pack = atk_ggrp ( data , ATK_MUX_MGMT ) ;
if ( IS_ERR ( pack ) ) {
if ( PTR_ERR ( pack ) = = - ENOENT ) {
/* The MGMT class does not exists - that's ok */
dev_dbg ( dev , " Class %#llx not found \n " , ATK_MUX_MGMT ) ;
return 0 ;
}
return PTR_ERR ( pack ) ;
}
/* Search the EC */
ec = NULL ;
for ( i = 0 ; i < pack - > package . count ; i + + ) {
union acpi_object * obj = & pack - > package . elements [ i ] ;
union acpi_object * id ;
if ( obj - > type ! = ACPI_TYPE_PACKAGE )
continue ;
id = & obj - > package . elements [ 0 ] ;
if ( id - > type ! = ACPI_TYPE_INTEGER )
continue ;
if ( id - > integer . value = = ATK_EC_ID ) {
ec = obj ;
break ;
}
}
ret = ( ec ! = NULL ) ;
if ( ! ret )
/* The system has no EC */
dev_dbg ( dev , " EC not found \n " ) ;
ACPI_FREE ( pack ) ;
return ret ;
}
static int atk_ec_enabled ( struct atk_data * data )
{
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * obj ;
struct atk_acpi_ret_buffer * buf ;
int err ;
obj = atk_gitm ( data , ATK_EC_ID ) ;
if ( IS_ERR ( obj ) ) {
dev_err ( dev , " Unable to query EC status \n " ) ;
return PTR_ERR ( obj ) ;
}
buf = ( struct atk_acpi_ret_buffer * ) obj - > buffer . pointer ;
if ( buf - > flags = = 0 ) {
dev_err ( dev , " Unable to query EC status \n " ) ;
err = - EIO ;
} else {
err = ( buf - > value ! = 0 ) ;
dev_dbg ( dev , " EC is %sabled \n " ,
err ? " en " : " dis " ) ;
}
ACPI_FREE ( obj ) ;
return err ;
}
static int atk_ec_ctl ( struct atk_data * data , int enable )
{
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * obj ;
struct atk_acpi_input_buf sitm ;
struct atk_acpi_ret_buffer * ec_ret ;
int err = 0 ;
sitm . id = ATK_EC_ID ;
sitm . param1 = enable ;
sitm . param2 = 0 ;
obj = atk_sitm ( data , & sitm ) ;
if ( IS_ERR ( obj ) ) {
dev_err ( dev , " Failed to %sable the EC \n " ,
enable ? " en " : " dis " ) ;
return PTR_ERR ( obj ) ;
}
ec_ret = ( struct atk_acpi_ret_buffer * ) obj - > buffer . pointer ;
if ( ec_ret - > flags = = 0 ) {
dev_err ( dev , " Failed to %sable the EC \n " ,
enable ? " en " : " dis " ) ;
err = - EIO ;
} else {
dev_info ( dev , " EC %sabled \n " ,
enable ? " en " : " dis " ) ;
}
ACPI_FREE ( obj ) ;
return err ;
}
2009-04-07 15:32:59 +02:00
static int atk_enumerate_new_hwmon ( struct atk_data * data )
{
struct device * dev = & data - > acpi_dev - > dev ;
union acpi_object * pack ;
int err ;
int i ;
2009-10-09 20:35:18 +02:00
err = atk_ec_present ( data ) ;
if ( err < 0 )
return err ;
if ( err ) {
err = atk_ec_enabled ( data ) ;
if ( err < 0 )
return err ;
/* If the EC was disabled we will disable it again on unload */
data - > disable_ec = err ;
err = atk_ec_ctl ( data , 1 ) ;
if ( err ) {
data - > disable_ec = false ;
return err ;
}
}
2009-04-07 15:32:59 +02:00
dev_dbg ( dev , " Enumerating hwmon sensors \n " ) ;
2009-10-09 20:35:18 +02:00
pack = atk_ggrp ( data , ATK_MUX_HWMON ) ;
if ( IS_ERR ( pack ) )
return PTR_ERR ( pack ) ;
2009-04-07 15:32:59 +02:00
for ( i = 0 ; i < pack - > package . count ; i + + ) {
union acpi_object * obj = & pack - > package . elements [ i ] ;
atk_add_sensor ( data , obj ) ;
}
err = data - > voltage_count + data - > temperature_count + data - > fan_count ;
2009-10-09 20:35:18 +02:00
ACPI_FREE ( pack ) ;
2009-04-07 15:32:59 +02:00
return err ;
}
static int atk_create_files ( struct atk_data * data )
{
struct atk_sensor_data * s ;
int err ;
list_for_each_entry ( s , & data - > sensor_list , list ) {
err = device_create_file ( data - > hwmon_dev , & s - > input_attr ) ;
if ( err )
return err ;
err = device_create_file ( data - > hwmon_dev , & s - > label_attr ) ;
if ( err )
return err ;
err = device_create_file ( data - > hwmon_dev , & s - > limit1_attr ) ;
if ( err )
return err ;
err = device_create_file ( data - > hwmon_dev , & s - > limit2_attr ) ;
if ( err )
return err ;
}
err = device_create_file ( data - > hwmon_dev , & atk_name_attr ) ;
return err ;
}
static void atk_remove_files ( struct atk_data * data )
{
struct atk_sensor_data * s ;
list_for_each_entry ( s , & data - > sensor_list , list ) {
device_remove_file ( data - > hwmon_dev , & s - > input_attr ) ;
device_remove_file ( data - > hwmon_dev , & s - > label_attr ) ;
device_remove_file ( data - > hwmon_dev , & s - > limit1_attr ) ;
device_remove_file ( data - > hwmon_dev , & s - > limit2_attr ) ;
}
device_remove_file ( data - > hwmon_dev , & atk_name_attr ) ;
}
static void atk_free_sensors ( struct atk_data * data )
{
struct list_head * head = & data - > sensor_list ;
struct atk_sensor_data * s , * tmp ;
list_for_each_entry_safe ( s , tmp , head , list ) {
kfree ( s - > acpi_name ) ;
kfree ( s ) ;
}
}
static int atk_register_hwmon ( struct atk_data * data )
{
struct device * dev = & data - > acpi_dev - > dev ;
int err ;
dev_dbg ( dev , " registering hwmon device \n " ) ;
data - > hwmon_dev = hwmon_device_register ( dev ) ;
if ( IS_ERR ( data - > hwmon_dev ) )
return PTR_ERR ( data - > hwmon_dev ) ;
dev_dbg ( dev , " populating sysfs directory \n " ) ;
err = atk_create_files ( data ) ;
if ( err )
goto remove ;
return 0 ;
remove :
/* Cleanup the registered files */
atk_remove_files ( data ) ;
hwmon_device_unregister ( data - > hwmon_dev ) ;
return err ;
}
2010-01-10 20:52:33 +01:00
static int atk_probe_if ( struct atk_data * data )
2009-04-07 15:32:59 +02:00
{
struct device * dev = & data - > acpi_dev - > dev ;
acpi_handle ret ;
acpi_status status ;
2010-01-10 20:52:33 +01:00
int err = 0 ;
2009-04-07 15:32:59 +02:00
/* RTMP: read temperature */
status = acpi_get_handle ( data - > atk_handle , METHOD_OLD_READ_TMP , & ret ) ;
2010-01-10 20:52:33 +01:00
if ( ACPI_SUCCESS ( status ) )
data - > rtmp_handle = ret ;
else
2009-04-07 15:32:59 +02:00
dev_dbg ( dev , " method " METHOD_OLD_READ_TMP " not found: %s \n " ,
acpi_format_exception ( status ) ) ;
/* RVLT: read voltage */
status = acpi_get_handle ( data - > atk_handle , METHOD_OLD_READ_VLT , & ret ) ;
2010-01-10 20:52:33 +01:00
if ( ACPI_SUCCESS ( status ) )
data - > rvlt_handle = ret ;
else
2009-04-07 15:32:59 +02:00
dev_dbg ( dev , " method " METHOD_OLD_READ_VLT " not found: %s \n " ,
acpi_format_exception ( status ) ) ;
/* RFAN: read fan status */
status = acpi_get_handle ( data - > atk_handle , METHOD_OLD_READ_FAN , & ret ) ;
2010-01-10 20:52:33 +01:00
if ( ACPI_SUCCESS ( status ) )
data - > rfan_handle = ret ;
else
2009-04-07 15:32:59 +02:00
dev_dbg ( dev , " method " METHOD_OLD_READ_FAN " not found: %s \n " ,
acpi_format_exception ( status ) ) ;
/* Enumeration */
status = acpi_get_handle ( data - > atk_handle , METHOD_ENUMERATE , & ret ) ;
2010-01-10 20:52:33 +01:00
if ( ACPI_SUCCESS ( status ) )
data - > enumerate_handle = ret ;
else
2009-04-07 15:32:59 +02:00
dev_dbg ( dev , " method " METHOD_ENUMERATE " not found: %s \n " ,
acpi_format_exception ( status ) ) ;
/* De-multiplexer (read) */
status = acpi_get_handle ( data - > atk_handle , METHOD_READ , & ret ) ;
2010-01-10 20:52:33 +01:00
if ( ACPI_SUCCESS ( status ) )
data - > read_handle = ret ;
else
2009-04-07 15:32:59 +02:00
dev_dbg ( dev , " method " METHOD_READ " not found: %s \n " ,
acpi_format_exception ( status ) ) ;
2009-10-09 20:35:18 +02:00
/* De-multiplexer (write) */
status = acpi_get_handle ( data - > atk_handle , METHOD_WRITE , & ret ) ;
2010-01-10 20:52:33 +01:00
if ( ACPI_SUCCESS ( status ) )
data - > write_handle = ret ;
else
dev_dbg ( dev , " method " METHOD_WRITE " not found: %s \n " ,
2009-10-09 20:35:18 +02:00
acpi_format_exception ( status ) ) ;
2010-01-10 20:52:33 +01:00
/* Check for hwmon methods: first check "old" style methods; note that
* both may be present : in this case we stick to the old interface ;
* analysis of multiple DSDTs indicates that when both interfaces
* are present the new one ( GGRP / GITM ) is not functional .
*/
if ( data - > rtmp_handle & & data - > rvlt_handle & & data - > rfan_handle )
data - > old_interface = true ;
else if ( data - > enumerate_handle & & data - > read_handle & &
data - > write_handle )
data - > old_interface = false ;
else
err = - ENODEV ;
return err ;
2009-04-07 15:32:59 +02:00
}
static int atk_add ( struct acpi_device * device )
{
acpi_status ret ;
int err ;
struct acpi_buffer buf ;
union acpi_object * obj ;
struct atk_data * data ;
dev_dbg ( & device - > dev , " adding... \n " ) ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > acpi_dev = device ;
data - > atk_handle = device - > handle ;
INIT_LIST_HEAD ( & data - > sensor_list ) ;
2009-10-09 20:35:18 +02:00
data - > disable_ec = false ;
2009-04-07 15:32:59 +02:00
buf . length = ACPI_ALLOCATE_BUFFER ;
ret = acpi_evaluate_object_typed ( data - > atk_handle , BOARD_ID , NULL ,
& buf , ACPI_TYPE_PACKAGE ) ;
if ( ret ! = AE_OK ) {
dev_dbg ( & device - > dev , " atk: method MBIF not found \n " ) ;
2010-01-25 15:00:49 +01:00
} else {
obj = buf . pointer ;
if ( obj - > package . count > = 2 ) {
union acpi_object * id = & obj - > package . elements [ 1 ] ;
if ( id - > type = = ACPI_TYPE_STRING )
dev_dbg ( & device - > dev , " board ID = %s \n " ,
id - > string . pointer ) ;
}
ACPI_FREE ( buf . pointer ) ;
2009-04-07 15:32:59 +02:00
}
2010-01-10 20:52:33 +01:00
err = atk_probe_if ( data ) ;
if ( err ) {
dev_err ( & device - > dev , " No usable hwmon interface detected \n " ) ;
goto out ;
2009-04-07 15:32:59 +02:00
}
2010-01-10 20:52:33 +01:00
if ( data - > old_interface ) {
dev_dbg ( & device - > dev , " Using old hwmon interface \n " ) ;
2009-04-07 15:32:59 +02:00
err = atk_enumerate_old_hwmon ( data ) ;
2010-01-10 20:52:33 +01:00
} else {
dev_dbg ( & device - > dev , " Using new hwmon interface \n " ) ;
2009-04-07 15:32:59 +02:00
err = atk_enumerate_new_hwmon ( data ) ;
2010-01-10 20:52:33 +01:00
}
2009-04-07 15:32:59 +02:00
if ( err < 0 )
goto out ;
if ( err = = 0 ) {
dev_info ( & device - > dev ,
" No usable sensor detected, bailing out \n " ) ;
err = - ENODEV ;
goto out ;
}
err = atk_register_hwmon ( data ) ;
if ( err )
goto cleanup ;
2010-01-10 20:52:33 +01:00
atk_debugfs_init ( data ) ;
2009-04-07 15:32:59 +02:00
device - > driver_data = data ;
return 0 ;
cleanup :
atk_free_sensors ( data ) ;
out :
2009-10-09 20:35:18 +02:00
if ( data - > disable_ec )
atk_ec_ctl ( data , 0 ) ;
2009-04-07 15:32:59 +02:00
kfree ( data ) ;
return err ;
}
static int atk_remove ( struct acpi_device * device , int type )
{
struct atk_data * data = device - > driver_data ;
dev_dbg ( & device - > dev , " removing... \n " ) ;
device - > driver_data = NULL ;
2010-01-10 20:52:33 +01:00
atk_debugfs_cleanup ( data ) ;
2009-04-07 15:32:59 +02:00
atk_remove_files ( data ) ;
atk_free_sensors ( data ) ;
hwmon_device_unregister ( data - > hwmon_dev ) ;
2009-10-09 20:35:18 +02:00
if ( data - > disable_ec ) {
if ( atk_ec_ctl ( data , 0 ) )
dev_err ( & device - > dev , " Failed to disable EC \n " ) ;
}
2009-04-07 15:32:59 +02:00
kfree ( data ) ;
return 0 ;
}
static int __init atk0110_init ( void )
{
int ret ;
ret = acpi_bus_register_driver ( & atk_driver ) ;
if ( ret )
pr_info ( " atk: acpi_bus_register_driver failed: %d \n " , ret ) ;
return ret ;
}
static void __exit atk0110_exit ( void )
{
acpi_bus_unregister_driver ( & atk_driver ) ;
}
module_init ( atk0110_init ) ;
module_exit ( atk0110_exit ) ;
MODULE_LICENSE ( " GPL " ) ;