2005-04-14 00:27:53 +04:00
/*
atxp1 . c - kernel module for setting CPU VID and general purpose
I / Os using the Attansic ATXP1 chip .
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
2005-07-29 23:15:12 +04:00
# include <linux/jiffies.h>
2005-04-14 00:27:53 +04:00
# include <linux/i2c.h>
2005-07-16 05:39:18 +04:00
# include <linux/hwmon.h>
2005-07-31 23:52:01 +04:00
# include <linux/hwmon-vid.h>
2005-07-16 05:39:18 +04:00
# include <linux/err.h>
2006-01-19 01:19:26 +03:00
# include <linux/mutex.h>
2006-09-24 23:24:46 +04:00
# include <linux/sysfs.h>
2005-04-14 00:27:53 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " System voltages control via Attansic ATXP1 " ) ;
2008-09-20 12:25:19 +04:00
MODULE_VERSION ( " 0.6.3 " ) ;
2005-04-14 00:27:53 +04:00
MODULE_AUTHOR ( " Sebastian Witt <se.witt@gmx.net> " ) ;
# define ATXP1_VID 0x00
# define ATXP1_CVID 0x01
# define ATXP1_GPIO1 0x06
# define ATXP1_GPIO2 0x0a
# define ATXP1_VIDENA 0x20
# define ATXP1_VIDMASK 0x1f
# define ATXP1_GPIO1MASK 0x0f
2008-02-18 06:28:03 +03:00
static const unsigned short normal_i2c [ ] = { 0x37 , 0x4e , I2C_CLIENT_END } ;
2005-04-14 00:27:53 +04:00
2005-07-31 23:49:03 +04:00
I2C_CLIENT_INSMOD_1 ( atxp1 ) ;
2005-04-14 00:27:53 +04:00
2008-07-16 21:30:11 +04:00
static int atxp1_probe ( struct i2c_client * client ,
const struct i2c_device_id * id ) ;
static int atxp1_remove ( struct i2c_client * client ) ;
2005-04-14 00:27:53 +04:00
static struct atxp1_data * atxp1_update_device ( struct device * dev ) ;
2008-07-16 21:30:11 +04:00
static int atxp1_detect ( struct i2c_client * client , int kind ,
struct i2c_board_info * info ) ;
static const struct i2c_device_id atxp1_id [ ] = {
{ " atxp1 " , atxp1 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , atxp1_id ) ;
2005-04-14 00:27:53 +04:00
static struct i2c_driver atxp1_driver = {
2008-07-16 21:30:11 +04:00
. class = I2C_CLASS_HWMON ,
2005-11-26 22:37:41 +03:00
. driver = {
. name = " atxp1 " ,
} ,
2008-07-16 21:30:11 +04:00
. probe = atxp1_probe ,
. remove = atxp1_remove ,
. id_table = atxp1_id ,
. detect = atxp1_detect ,
. address_data = & addr_data ,
2005-04-14 00:27:53 +04:00
} ;
struct atxp1_data {
2007-08-21 00:46:20 +04:00
struct device * hwmon_dev ;
2006-01-19 01:19:26 +03:00
struct mutex update_lock ;
2005-04-14 00:27:53 +04:00
unsigned long last_updated ;
u8 valid ;
struct {
u8 vid ; /* VID output register */
u8 cpu_vid ; /* VID input from CPU */
u8 gpio1 ; /* General purpose I/O register 1 */
u8 gpio2 ; /* General purpose I/O register 2 */
} reg ;
u8 vrm ; /* Detected CPU VRM */
} ;
static struct atxp1_data * atxp1_update_device ( struct device * dev )
{
struct i2c_client * client ;
struct atxp1_data * data ;
client = to_i2c_client ( dev ) ;
data = i2c_get_clientdata ( client ) ;
2006-01-19 01:19:26 +03:00
mutex_lock ( & data - > update_lock ) ;
2005-04-14 00:27:53 +04:00
2005-07-29 23:15:12 +04:00
if ( time_after ( jiffies , data - > last_updated + HZ ) | | ! data - > valid ) {
2005-04-14 00:27:53 +04:00
/* Update local register data */
data - > reg . vid = i2c_smbus_read_byte_data ( client , ATXP1_VID ) ;
data - > reg . cpu_vid = i2c_smbus_read_byte_data ( client , ATXP1_CVID ) ;
data - > reg . gpio1 = i2c_smbus_read_byte_data ( client , ATXP1_GPIO1 ) ;
data - > reg . gpio2 = i2c_smbus_read_byte_data ( client , ATXP1_GPIO2 ) ;
data - > valid = 1 ;
}
2006-01-19 01:19:26 +03:00
mutex_unlock ( & data - > update_lock ) ;
2005-04-14 00:27:53 +04:00
return ( data ) ;
}
/* sys file functions for cpu0_vid */
2005-06-22 08:01:59 +04:00
static ssize_t atxp1_showvcore ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-14 00:27:53 +04:00
{
int size ;
struct atxp1_data * data ;
data = atxp1_update_device ( dev ) ;
size = sprintf ( buf , " %d \n " , vid_from_reg ( data - > reg . vid & ATXP1_VIDMASK , data - > vrm ) ) ;
return size ;
}
2005-06-22 08:01:59 +04:00
static ssize_t atxp1_storevcore ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-14 00:27:53 +04:00
{
struct atxp1_data * data ;
struct i2c_client * client ;
2006-08-28 16:18:14 +04:00
int vid , cvid ;
2005-04-14 00:27:53 +04:00
unsigned int vcore ;
client = to_i2c_client ( dev ) ;
data = atxp1_update_device ( dev ) ;
vcore = simple_strtoul ( buf , NULL , 10 ) ;
vcore / = 25 ;
vcore * = 25 ;
/* Calculate VID */
vid = vid_to_reg ( vcore , data - > vrm ) ;
if ( vid < 0 ) {
dev_err ( dev , " VID calculation failed. \n " ) ;
return - 1 ;
}
/* If output enabled, use control register value. Otherwise original CPU VID */
if ( data - > reg . vid & ATXP1_VIDENA )
cvid = data - > reg . vid & ATXP1_VIDMASK ;
else
cvid = data - > reg . cpu_vid ;
/* Nothing changed, aborting */
if ( vid = = cvid )
return count ;
2005-06-29 16:13:54 +04:00
dev_dbg ( dev , " Setting VCore to %d mV (0x%02x) \n " , vcore , vid ) ;
2005-04-14 00:27:53 +04:00
/* Write every 25 mV step to increase stability */
if ( cvid > vid ) {
for ( ; cvid > = vid ; cvid - - ) {
i2c_smbus_write_byte_data ( client , ATXP1_VID , cvid | ATXP1_VIDENA ) ;
}
}
else {
for ( ; cvid < = vid ; cvid + + ) {
i2c_smbus_write_byte_data ( client , ATXP1_VID , cvid | ATXP1_VIDENA ) ;
}
}
data - > valid = 0 ;
return count ;
}
/* CPU core reference voltage
unit : millivolt
*/
static DEVICE_ATTR ( cpu0_vid , S_IRUGO | S_IWUSR , atxp1_showvcore , atxp1_storevcore ) ;
/* sys file functions for GPIO1 */
2005-06-22 08:01:59 +04:00
static ssize_t atxp1_showgpio1 ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-14 00:27:53 +04:00
{
int size ;
struct atxp1_data * data ;
data = atxp1_update_device ( dev ) ;
size = sprintf ( buf , " 0x%02x \n " , data - > reg . gpio1 & ATXP1_GPIO1MASK ) ;
return size ;
}
2005-06-22 08:01:59 +04:00
static ssize_t atxp1_storegpio1 ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-14 00:27:53 +04:00
{
struct atxp1_data * data ;
struct i2c_client * client ;
unsigned int value ;
client = to_i2c_client ( dev ) ;
data = atxp1_update_device ( dev ) ;
value = simple_strtoul ( buf , NULL , 16 ) ;
value & = ATXP1_GPIO1MASK ;
if ( value ! = ( data - > reg . gpio1 & ATXP1_GPIO1MASK ) ) {
dev_info ( dev , " Writing 0x%x to GPIO1. \n " , value ) ;
i2c_smbus_write_byte_data ( client , ATXP1_GPIO1 , value ) ;
data - > valid = 0 ;
}
return count ;
}
/* GPIO1 data register
unit : Four bit as hex ( e . g . 0x0f )
*/
static DEVICE_ATTR ( gpio1 , S_IRUGO | S_IWUSR , atxp1_showgpio1 , atxp1_storegpio1 ) ;
/* sys file functions for GPIO2 */
2005-06-22 08:01:59 +04:00
static ssize_t atxp1_showgpio2 ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-14 00:27:53 +04:00
{
int size ;
struct atxp1_data * data ;
data = atxp1_update_device ( dev ) ;
size = sprintf ( buf , " 0x%02x \n " , data - > reg . gpio2 ) ;
return size ;
}
2005-06-22 08:01:59 +04:00
static ssize_t atxp1_storegpio2 ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-14 00:27:53 +04:00
{
struct atxp1_data * data ;
struct i2c_client * client ;
unsigned int value ;
client = to_i2c_client ( dev ) ;
data = atxp1_update_device ( dev ) ;
value = simple_strtoul ( buf , NULL , 16 ) & 0xff ;
if ( value ! = data - > reg . gpio2 ) {
dev_info ( dev , " Writing 0x%x to GPIO1. \n " , value ) ;
i2c_smbus_write_byte_data ( client , ATXP1_GPIO2 , value ) ;
data - > valid = 0 ;
}
return count ;
}
/* GPIO2 data register
unit : Eight bit as hex ( e . g . 0xff )
*/
static DEVICE_ATTR ( gpio2 , S_IRUGO | S_IWUSR , atxp1_showgpio2 , atxp1_storegpio2 ) ;
2006-09-24 23:24:46 +04:00
static struct attribute * atxp1_attributes [ ] = {
& dev_attr_gpio1 . attr ,
& dev_attr_gpio2 . attr ,
& dev_attr_cpu0_vid . attr ,
NULL
} ;
static const struct attribute_group atxp1_group = {
. attrs = atxp1_attributes ,
} ;
2005-04-14 00:27:53 +04:00
2008-07-16 21:30:11 +04:00
/* Return 0 if detection is successful, -ENODEV otherwise */
static int atxp1_detect ( struct i2c_client * new_client , int kind ,
struct i2c_board_info * info )
2005-04-14 00:27:53 +04:00
{
2008-07-16 21:30:11 +04:00
struct i2c_adapter * adapter = new_client - > adapter ;
2005-04-14 00:27:53 +04:00
u8 temp ;
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_BYTE_DATA ) )
2008-07-16 21:30:11 +04:00
return - ENODEV ;
2005-04-14 00:27:53 +04:00
/* Detect ATXP1, checking if vendor ID registers are all zero */
if ( ! ( ( i2c_smbus_read_byte_data ( new_client , 0x3e ) = = 0 ) & &
( i2c_smbus_read_byte_data ( new_client , 0x3f ) = = 0 ) & &
( i2c_smbus_read_byte_data ( new_client , 0xfe ) = = 0 ) & &
2008-09-20 12:25:19 +04:00
( i2c_smbus_read_byte_data ( new_client , 0xff ) = = 0 ) ) )
return - ENODEV ;
2005-04-14 00:27:53 +04:00
2008-09-20 12:25:19 +04:00
/* No vendor ID, now checking if registers 0x10,0x11 (non-existent)
* showing the same as register 0x00 */
temp = i2c_smbus_read_byte_data ( new_client , 0x00 ) ;
2005-04-14 00:27:53 +04:00
2008-09-20 12:25:19 +04:00
if ( ! ( ( i2c_smbus_read_byte_data ( new_client , 0x10 ) = = temp ) & &
( i2c_smbus_read_byte_data ( new_client , 0x11 ) = = temp ) ) )
return - ENODEV ;
2005-04-14 00:27:53 +04:00
/* Get VRM */
2008-07-16 21:30:11 +04:00
temp = vid_which_vrm ( ) ;
2005-04-14 00:27:53 +04:00
2008-07-16 21:30:11 +04:00
if ( ( temp ! = 90 ) & & ( temp ! = 91 ) ) {
dev_err ( & adapter - > dev , " atxp1: Not supporting VRM %d.%d \n " ,
temp / 10 , temp % 10 ) ;
return - ENODEV ;
2005-04-14 00:27:53 +04:00
}
2008-07-16 21:30:11 +04:00
strlcpy ( info - > type , " atxp1 " , I2C_NAME_SIZE ) ;
2005-04-14 00:27:53 +04:00
2008-07-16 21:30:11 +04:00
return 0 ;
}
2005-04-14 00:27:53 +04:00
2008-07-16 21:30:11 +04:00
static int atxp1_probe ( struct i2c_client * new_client ,
const struct i2c_device_id * id )
{
struct atxp1_data * data ;
int err ;
2005-04-14 00:27:53 +04:00
2008-07-16 21:30:11 +04:00
data = kzalloc ( sizeof ( struct atxp1_data ) , GFP_KERNEL ) ;
if ( ! data ) {
err = - ENOMEM ;
goto exit ;
2005-04-14 00:27:53 +04:00
}
2008-07-16 21:30:11 +04:00
/* Get VRM */
data - > vrm = vid_which_vrm ( ) ;
i2c_set_clientdata ( new_client , data ) ;
data - > valid = 0 ;
mutex_init ( & data - > update_lock ) ;
2006-09-24 23:24:46 +04:00
/* Register sysfs hooks */
if ( ( err = sysfs_create_group ( & new_client - > dev . kobj , & atxp1_group ) ) )
2008-07-16 21:30:11 +04:00
goto exit_free ;
2006-09-24 23:24:46 +04:00
2007-08-21 00:46:20 +04:00
data - > hwmon_dev = hwmon_device_register ( & new_client - > dev ) ;
if ( IS_ERR ( data - > hwmon_dev ) ) {
err = PTR_ERR ( data - > hwmon_dev ) ;
2006-09-24 23:24:46 +04:00
goto exit_remove_files ;
2005-07-16 05:39:18 +04:00
}
2005-04-14 00:27:53 +04:00
dev_info ( & new_client - > dev , " Using VRM: %d.%d \n " ,
data - > vrm / 10 , data - > vrm % 10 ) ;
return 0 ;
2006-09-24 23:24:46 +04:00
exit_remove_files :
sysfs_remove_group ( & new_client - > dev . kobj , & atxp1_group ) ;
2005-04-14 00:27:53 +04:00
exit_free :
kfree ( data ) ;
exit :
return err ;
} ;
2008-07-16 21:30:11 +04:00
static int atxp1_remove ( struct i2c_client * client )
2005-04-14 00:27:53 +04:00
{
2005-07-16 05:39:18 +04:00
struct atxp1_data * data = i2c_get_clientdata ( client ) ;
2005-04-14 00:27:53 +04:00
2007-08-21 00:46:20 +04:00
hwmon_device_unregister ( data - > hwmon_dev ) ;
2006-09-24 23:24:46 +04:00
sysfs_remove_group ( & client - > dev . kobj , & atxp1_group ) ;
2005-07-16 05:39:18 +04:00
2008-07-16 21:30:11 +04:00
kfree ( data ) ;
2005-04-14 00:27:53 +04:00
2008-07-16 21:30:11 +04:00
return 0 ;
2005-04-14 00:27:53 +04:00
} ;
static int __init atxp1_init ( void )
{
return i2c_add_driver ( & atxp1_driver ) ;
} ;
static void __exit atxp1_exit ( void )
{
i2c_del_driver ( & atxp1_driver ) ;
} ;
module_init ( atxp1_init ) ;
module_exit ( atxp1_exit ) ;