2019-05-20 10:19:02 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2011-03-07 02:54:27 +03:00
/*
* Hardware monitoring driver for ucd9200 series Digital PWM System Controllers
*
* Copyright ( C ) 2011 Ericsson AB .
*/
# include <linux/kernel.h>
# include <linux/module.h>
2017-02-24 16:13:08 +03:00
# include <linux/of_device.h>
2011-03-07 02:54:27 +03:00
# include <linux/init.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/i2c.h>
2017-05-21 23:34:43 +03:00
# include <linux/pmbus.h>
2011-03-07 02:54:27 +03:00
# include "pmbus.h"
# define UCD9200_PHASE_INFO 0xd2
# define UCD9200_DEVICE_ID 0xfd
enum chips { ucd9200 , ucd9220 , ucd9222 , ucd9224 , ucd9240 , ucd9244 , ucd9246 ,
ucd9248 } ;
static const struct i2c_device_id ucd9200_id [ ] = {
{ " ucd9200 " , ucd9200 } ,
{ " ucd9220 " , ucd9220 } ,
{ " ucd9222 " , ucd9222 } ,
{ " ucd9224 " , ucd9224 } ,
{ " ucd9240 " , ucd9240 } ,
{ " ucd9244 " , ucd9244 } ,
{ " ucd9246 " , ucd9246 } ,
{ " ucd9248 " , ucd9248 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , ucd9200_id ) ;
2019-04-04 16:13:31 +03:00
static const struct of_device_id __maybe_unused ucd9200_of_match [ ] = {
2017-02-24 16:13:08 +03:00
{
. compatible = " ti,cd9200 " ,
. data = ( void * ) ucd9200
} ,
{
. compatible = " ti,cd9220 " ,
. data = ( void * ) ucd9220
} ,
{
. compatible = " ti,cd9222 " ,
. data = ( void * ) ucd9222
} ,
{
. compatible = " ti,cd9224 " ,
. data = ( void * ) ucd9224
} ,
{
. compatible = " ti,cd9240 " ,
. data = ( void * ) ucd9240
} ,
{
. compatible = " ti,cd9244 " ,
. data = ( void * ) ucd9244
} ,
{
. compatible = " ti,cd9246 " ,
. data = ( void * ) ucd9246
} ,
{
. compatible = " ti,cd9248 " ,
. data = ( void * ) ucd9248
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ucd9200_of_match ) ;
2020-08-09 00:00:04 +03:00
static int ucd9200_probe ( struct i2c_client * client )
2011-03-07 02:54:27 +03:00
{
u8 block_buffer [ I2C_SMBUS_BLOCK_MAX + 1 ] ;
struct pmbus_driver_info * info ;
const struct i2c_device_id * mid ;
2017-02-24 16:13:08 +03:00
enum chips chip ;
2011-03-07 02:54:27 +03:00
int i , j , ret ;
if ( ! i2c_check_functionality ( client - > adapter ,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA ) )
return - ENODEV ;
ret = i2c_smbus_read_block_data ( client , UCD9200_DEVICE_ID ,
block_buffer ) ;
if ( ret < 0 ) {
dev_err ( & client - > dev , " Failed to read device ID \n " ) ;
return ret ;
}
block_buffer [ ret ] = ' \0 ' ;
dev_info ( & client - > dev , " Device ID %s \n " , block_buffer ) ;
2011-08-31 19:53:41 +04:00
for ( mid = ucd9200_id ; mid - > name [ 0 ] ; mid + + ) {
2011-03-07 02:54:27 +03:00
if ( ! strncasecmp ( mid - > name , block_buffer , strlen ( mid - > name ) ) )
break ;
}
2011-08-31 19:53:41 +04:00
if ( ! mid - > name [ 0 ] ) {
2011-03-07 02:54:27 +03:00
dev_err ( & client - > dev , " Unsupported device \n " ) ;
return - ENODEV ;
}
2017-02-24 16:13:08 +03:00
if ( client - > dev . of_node )
chip = ( enum chips ) of_device_get_match_data ( & client - > dev ) ;
else
2020-08-09 00:00:04 +03:00
chip = mid - > driver_data ;
2017-02-24 16:13:08 +03:00
2020-08-09 00:00:04 +03:00
if ( chip ! = ucd9200 & & strcmp ( client - > name , mid - > name ) ! = 0 )
2011-03-07 02:54:27 +03:00
dev_notice ( & client - > dev ,
" Device mismatch: Configured %s, detected %s \n " ,
2020-08-09 00:00:04 +03:00
client - > name , mid - > name ) ;
2011-03-07 02:54:27 +03:00
2012-02-22 20:56:43 +04:00
info = devm_kzalloc ( & client - > dev , sizeof ( struct pmbus_driver_info ) ,
GFP_KERNEL ) ;
2011-03-07 02:54:27 +03:00
if ( ! info )
return - ENOMEM ;
ret = i2c_smbus_read_block_data ( client , UCD9200_PHASE_INFO ,
block_buffer ) ;
if ( ret < 0 ) {
dev_err ( & client - > dev , " Failed to read phase information \n " ) ;
2012-02-22 20:56:43 +04:00
return ret ;
2011-03-07 02:54:27 +03:00
}
/*
* Calculate number of configured pages ( rails ) from PHASE_INFO
* register .
* Rails have to be sequential , so we can abort after finding
* the first unconfigured rail .
*/
info - > pages = 0 ;
for ( i = 0 ; i < ret ; i + + ) {
if ( ! block_buffer [ i ] )
break ;
info - > pages + + ;
}
if ( ! info - > pages ) {
dev_err ( & client - > dev , " No rails configured \n " ) ;
2012-02-22 20:56:43 +04:00
return - ENODEV ;
2011-03-07 02:54:27 +03:00
}
dev_info ( & client - > dev , " %d rails configured \n " , info - > pages ) ;
/*
* Set PHASE registers on all pages to 0xff to ensure that phase
* specific commands will apply to all phases of a given page ( rail ) .
* This only affects the READ_IOUT and READ_TEMPERATURE2 registers .
* READ_IOUT will return the sum of currents of all phases of a rail ,
* and READ_TEMPERATURE2 will return the maximum temperature detected
* for the the phases of the rail .
*/
for ( i = 0 ; i < info - > pages ; i + + ) {
/*
* Setting PAGE & PHASE fails once in a while for no obvious
* reason , so we need to retry a couple of times .
*/
for ( j = 0 ; j < 3 ; j + + ) {
ret = i2c_smbus_write_byte_data ( client , PMBUS_PAGE , i ) ;
if ( ret < 0 )
continue ;
ret = i2c_smbus_write_byte_data ( client , PMBUS_PHASE ,
0xff ) ;
if ( ret < 0 )
continue ;
break ;
}
if ( ret < 0 ) {
dev_err ( & client - > dev ,
" Failed to initialize PHASE registers \n " ) ;
2012-02-22 20:56:43 +04:00
return ret ;
2011-03-07 02:54:27 +03:00
}
}
if ( info - > pages > 1 )
i2c_smbus_write_byte_data ( client , PMBUS_PAGE , 0 ) ;
info - > func [ 0 ] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP |
PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP ;
for ( i = 1 ; i < info - > pages ; i + + )
info - > func [ i ] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_POUT |
PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP ;
/* ucd9240 supports a single fan */
if ( mid - > driver_data = = ucd9240 )
info - > func [ 0 ] | = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 ;
2020-08-09 00:00:04 +03:00
return pmbus_do_probe ( client , info ) ;
2011-03-07 02:54:27 +03:00
}
/* This is the driver that will be inserted */
static struct i2c_driver ucd9200_driver = {
. driver = {
. name = " ucd9200 " ,
2017-02-24 16:13:08 +03:00
. of_match_table = of_match_ptr ( ucd9200_of_match ) ,
2011-03-07 02:54:27 +03:00
} ,
2020-08-09 00:00:04 +03:00
. probe_new = ucd9200_probe ,
2011-03-07 02:54:27 +03:00
. id_table = ucd9200_id ,
} ;
2012-01-20 11:38:18 +04:00
module_i2c_driver ( ucd9200_driver ) ;
2011-03-07 02:54:27 +03:00
MODULE_AUTHOR ( " Guenter Roeck " ) ;
MODULE_DESCRIPTION ( " PMBus driver for TI UCD922x, UCD924x " ) ;
MODULE_LICENSE ( " GPL " ) ;
2021-04-20 09:07:07 +03:00
MODULE_IMPORT_NS ( PMBUS ) ;