2015-08-01 11:39:38 +03:00
/*
* AXP20x PMIC USB power supply status driver
*
* Copyright ( C ) 2015 Hans de Goede < hdegoede @ redhat . com >
* Copyright ( C ) 2014 Bruno Prémont < bonbons @ linux - vserver . org >
*
* 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 .
*/
# include <linux/device.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/mfd/axp20x.h>
# include <linux/module.h>
# include <linux/of.h>
2016-12-09 14:04:09 +03:00
# include <linux/of_device.h>
2015-08-01 11:39:38 +03:00
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/regmap.h>
# include <linux/slab.h>
2017-01-27 11:54:36 +03:00
# include <linux/iio/consumer.h>
2015-08-01 11:39:38 +03:00
# define DRVNAME "axp20x-usb-power-supply"
# define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5)
# define AXP20X_PWR_STATUS_VBUS_USED BIT(4)
# define AXP20X_USB_STATUS_VBUS_VALID BIT(2)
# define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000)
2016-12-09 14:04:11 +03:00
# define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3)
# define AXP20X_VBUS_VHOLD_OFFSET 3
2015-08-01 11:39:38 +03:00
# define AXP20X_VBUS_CLIMIT_MASK 3
# define AXP20X_VBUC_CLIMIT_900mA 0
# define AXP20X_VBUC_CLIMIT_500mA 1
# define AXP20X_VBUC_CLIMIT_100mA 2
# define AXP20X_VBUC_CLIMIT_NONE 3
# define AXP20X_ADC_EN1_VBUS_CURR BIT(2)
# define AXP20X_ADC_EN1_VBUS_VOLT BIT(3)
# define AXP20X_VBUS_MON_VBUS_VALID BIT(3)
struct axp20x_usb_power {
2016-06-02 20:18:53 +03:00
struct device_node * np ;
2015-08-01 11:39:38 +03:00
struct regmap * regmap ;
struct power_supply * supply ;
2017-01-10 20:48:12 +03:00
enum axp20x_variants axp20x_id ;
2017-01-27 11:54:36 +03:00
struct iio_channel * vbus_v ;
struct iio_channel * vbus_i ;
2015-08-01 11:39:38 +03:00
} ;
static irqreturn_t axp20x_usb_power_irq ( int irq , void * devid )
{
struct axp20x_usb_power * power = devid ;
power_supply_changed ( power - > supply ) ;
return IRQ_HANDLED ;
}
static int axp20x_usb_power_get_property ( struct power_supply * psy ,
enum power_supply_property psp , union power_supply_propval * val )
{
struct axp20x_usb_power * power = power_supply_get_drvdata ( psy ) ;
unsigned int input , v ;
int ret ;
switch ( psp ) {
case POWER_SUPPLY_PROP_VOLTAGE_MIN :
ret = regmap_read ( power - > regmap , AXP20X_VBUS_IPSOUT_MGMT , & v ) ;
if ( ret )
return ret ;
val - > intval = AXP20X_VBUS_VHOLD_uV ( v ) ;
return 0 ;
case POWER_SUPPLY_PROP_VOLTAGE_NOW :
2017-01-27 11:54:36 +03:00
if ( IS_ENABLED ( CONFIG_AXP20X_ADC ) ) {
ret = iio_read_channel_processed ( power - > vbus_v ,
& val - > intval ) ;
if ( ret )
return ret ;
/*
* IIO framework gives mV but Power Supply framework
* gives uV .
*/
val - > intval * = 1000 ;
return 0 ;
}
2015-08-01 11:39:38 +03:00
ret = axp20x_read_variable_width ( power - > regmap ,
AXP20X_VBUS_V_ADC_H , 12 ) ;
if ( ret < 0 )
return ret ;
val - > intval = ret * 1700 ; /* 1 step = 1.7 mV */
return 0 ;
case POWER_SUPPLY_PROP_CURRENT_MAX :
ret = regmap_read ( power - > regmap , AXP20X_VBUS_IPSOUT_MGMT , & v ) ;
if ( ret )
return ret ;
switch ( v & AXP20X_VBUS_CLIMIT_MASK ) {
case AXP20X_VBUC_CLIMIT_100mA :
2016-12-09 14:04:13 +03:00
if ( power - > axp20x_id = = AXP221_ID )
2016-06-02 20:18:53 +03:00
val - > intval = - 1 ; /* No 100mA limit */
2016-12-09 14:04:13 +03:00
else
val - > intval = 100000 ;
2015-08-01 11:39:38 +03:00
break ;
case AXP20X_VBUC_CLIMIT_500mA :
val - > intval = 500000 ;
break ;
case AXP20X_VBUC_CLIMIT_900mA :
val - > intval = 900000 ;
break ;
case AXP20X_VBUC_CLIMIT_NONE :
val - > intval = - 1 ;
break ;
}
return 0 ;
case POWER_SUPPLY_PROP_CURRENT_NOW :
2017-01-27 11:54:36 +03:00
if ( IS_ENABLED ( CONFIG_AXP20X_ADC ) ) {
ret = iio_read_channel_processed ( power - > vbus_i ,
& val - > intval ) ;
if ( ret )
return ret ;
/*
* IIO framework gives mA but Power Supply framework
* gives uA .
*/
val - > intval * = 1000 ;
return 0 ;
}
2015-08-01 11:39:38 +03:00
ret = axp20x_read_variable_width ( power - > regmap ,
AXP20X_VBUS_I_ADC_H , 12 ) ;
if ( ret < 0 )
return ret ;
val - > intval = ret * 375 ; /* 1 step = 0.375 mA */
return 0 ;
default :
break ;
}
/* All the properties below need the input-status reg value */
ret = regmap_read ( power - > regmap , AXP20X_PWR_INPUT_STATUS , & input ) ;
if ( ret )
return ret ;
switch ( psp ) {
case POWER_SUPPLY_PROP_HEALTH :
if ( ! ( input & AXP20X_PWR_STATUS_VBUS_PRESENT ) ) {
val - > intval = POWER_SUPPLY_HEALTH_UNKNOWN ;
break ;
}
2016-06-02 20:18:53 +03:00
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
2015-08-01 11:39:38 +03:00
2016-12-09 14:04:09 +03:00
if ( power - > axp20x_id = = AXP202_ID ) {
2016-06-02 20:18:53 +03:00
ret = regmap_read ( power - > regmap ,
AXP20X_USB_OTG_STATUS , & v ) ;
if ( ret )
return ret ;
2015-08-01 11:39:38 +03:00
2016-06-02 20:18:53 +03:00
if ( ! ( v & AXP20X_USB_STATUS_VBUS_VALID ) )
val - > intval =
POWER_SUPPLY_HEALTH_UNSPEC_FAILURE ;
}
2015-08-01 11:39:38 +03:00
break ;
case POWER_SUPPLY_PROP_PRESENT :
val - > intval = ! ! ( input & AXP20X_PWR_STATUS_VBUS_PRESENT ) ;
break ;
case POWER_SUPPLY_PROP_ONLINE :
val - > intval = ! ! ( input & AXP20X_PWR_STATUS_VBUS_USED ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
2016-12-09 14:04:11 +03:00
static int axp20x_usb_power_set_voltage_min ( struct axp20x_usb_power * power ,
int intval )
{
int val ;
switch ( intval ) {
case 4000000 :
case 4100000 :
case 4200000 :
case 4300000 :
case 4400000 :
case 4500000 :
case 4600000 :
case 4700000 :
val = ( intval - 4000000 ) / 100000 ;
return regmap_update_bits ( power - > regmap ,
AXP20X_VBUS_IPSOUT_MGMT ,
AXP20X_VBUS_VHOLD_MASK ,
val < < AXP20X_VBUS_VHOLD_OFFSET ) ;
default :
return - EINVAL ;
}
return - EINVAL ;
}
static int axp20x_usb_power_set_current_max ( struct axp20x_usb_power * power ,
int intval )
{
int val ;
switch ( intval ) {
case 100000 :
if ( power - > axp20x_id = = AXP221_ID )
return - EINVAL ;
case 500000 :
case 900000 :
val = ( 900000 - intval ) / 400000 ;
return regmap_update_bits ( power - > regmap ,
AXP20X_VBUS_IPSOUT_MGMT ,
AXP20X_VBUS_CLIMIT_MASK , val ) ;
default :
return - EINVAL ;
}
return - EINVAL ;
}
static int axp20x_usb_power_set_property ( struct power_supply * psy ,
enum power_supply_property psp ,
const union power_supply_propval * val )
{
struct axp20x_usb_power * power = power_supply_get_drvdata ( psy ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_VOLTAGE_MIN :
return axp20x_usb_power_set_voltage_min ( power , val - > intval ) ;
case POWER_SUPPLY_PROP_CURRENT_MAX :
return axp20x_usb_power_set_current_max ( power , val - > intval ) ;
default :
return - EINVAL ;
}
return - EINVAL ;
}
static int axp20x_usb_power_prop_writeable ( struct power_supply * psy ,
enum power_supply_property psp )
{
return psp = = POWER_SUPPLY_PROP_VOLTAGE_MIN | |
psp = = POWER_SUPPLY_PROP_CURRENT_MAX ;
}
2015-08-01 11:39:38 +03:00
static enum power_supply_property axp20x_usb_power_properties [ ] = {
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_ONLINE ,
POWER_SUPPLY_PROP_VOLTAGE_MIN ,
POWER_SUPPLY_PROP_VOLTAGE_NOW ,
POWER_SUPPLY_PROP_CURRENT_MAX ,
POWER_SUPPLY_PROP_CURRENT_NOW ,
} ;
2016-06-02 20:18:53 +03:00
static enum power_supply_property axp22x_usb_power_properties [ ] = {
POWER_SUPPLY_PROP_HEALTH ,
POWER_SUPPLY_PROP_PRESENT ,
POWER_SUPPLY_PROP_ONLINE ,
POWER_SUPPLY_PROP_VOLTAGE_MIN ,
POWER_SUPPLY_PROP_CURRENT_MAX ,
} ;
2015-08-01 11:39:38 +03:00
static const struct power_supply_desc axp20x_usb_power_desc = {
. name = " axp20x-usb " ,
. type = POWER_SUPPLY_TYPE_USB ,
. properties = axp20x_usb_power_properties ,
. num_properties = ARRAY_SIZE ( axp20x_usb_power_properties ) ,
2016-12-09 14:04:11 +03:00
. property_is_writeable = axp20x_usb_power_prop_writeable ,
2015-08-01 11:39:38 +03:00
. get_property = axp20x_usb_power_get_property ,
2016-12-09 14:04:11 +03:00
. set_property = axp20x_usb_power_set_property ,
2015-08-01 11:39:38 +03:00
} ;
2016-06-02 20:18:53 +03:00
static const struct power_supply_desc axp22x_usb_power_desc = {
. name = " axp20x-usb " ,
. type = POWER_SUPPLY_TYPE_USB ,
. properties = axp22x_usb_power_properties ,
. num_properties = ARRAY_SIZE ( axp22x_usb_power_properties ) ,
2016-12-09 14:04:11 +03:00
. property_is_writeable = axp20x_usb_power_prop_writeable ,
2016-06-02 20:18:53 +03:00
. get_property = axp20x_usb_power_get_property ,
2016-12-09 14:04:11 +03:00
. set_property = axp20x_usb_power_set_property ,
2016-06-02 20:18:53 +03:00
} ;
2017-01-27 11:54:36 +03:00
static int configure_iio_channels ( struct platform_device * pdev ,
struct axp20x_usb_power * power )
{
power - > vbus_v = devm_iio_channel_get ( & pdev - > dev , " vbus_v " ) ;
if ( IS_ERR ( power - > vbus_v ) ) {
if ( PTR_ERR ( power - > vbus_v ) = = - ENODEV )
return - EPROBE_DEFER ;
return PTR_ERR ( power - > vbus_v ) ;
}
power - > vbus_i = devm_iio_channel_get ( & pdev - > dev , " vbus_i " ) ;
if ( IS_ERR ( power - > vbus_i ) ) {
if ( PTR_ERR ( power - > vbus_i ) = = - ENODEV )
return - EPROBE_DEFER ;
return PTR_ERR ( power - > vbus_i ) ;
}
return 0 ;
}
static int configure_adc_registers ( struct axp20x_usb_power * power )
{
/* Enable vbus voltage and current measurement */
return regmap_update_bits ( power - > regmap , AXP20X_ADC_EN1 ,
AXP20X_ADC_EN1_VBUS_CURR |
AXP20X_ADC_EN1_VBUS_VOLT ,
AXP20X_ADC_EN1_VBUS_CURR |
AXP20X_ADC_EN1_VBUS_VOLT ) ;
}
2015-08-01 11:39:38 +03:00
static int axp20x_usb_power_probe ( struct platform_device * pdev )
{
struct axp20x_dev * axp20x = dev_get_drvdata ( pdev - > dev . parent ) ;
struct power_supply_config psy_cfg = { } ;
struct axp20x_usb_power * power ;
2016-06-02 20:18:53 +03:00
static const char * const axp20x_irq_names [ ] = { " VBUS_PLUGIN " ,
" VBUS_REMOVAL " , " VBUS_VALID " , " VBUS_NOT_VALID " , NULL } ;
static const char * const axp22x_irq_names [ ] = {
" VBUS_PLUGIN " , " VBUS_REMOVAL " , NULL } ;
2017-05-04 23:10:49 +03:00
const char * const * irq_names ;
2016-06-02 20:18:53 +03:00
const struct power_supply_desc * usb_power_desc ;
2015-08-01 11:39:38 +03:00
int i , irq , ret ;
if ( ! of_device_is_available ( pdev - > dev . of_node ) )
return - ENODEV ;
if ( ! axp20x ) {
dev_err ( & pdev - > dev , " Parent drvdata not set \n " ) ;
return - EINVAL ;
}
power = devm_kzalloc ( & pdev - > dev , sizeof ( * power ) , GFP_KERNEL ) ;
if ( ! power )
return - ENOMEM ;
2017-01-10 20:48:12 +03:00
power - > axp20x_id = ( enum axp20x_variants ) of_device_get_match_data (
& pdev - > dev ) ;
2016-12-09 14:04:09 +03:00
2016-06-02 20:18:53 +03:00
power - > np = pdev - > dev . of_node ;
2015-08-01 11:39:38 +03:00
power - > regmap = axp20x - > regmap ;
2016-12-09 14:04:09 +03:00
if ( power - > axp20x_id = = AXP202_ID ) {
2016-06-02 20:18:53 +03:00
/* Enable vbus valid checking */
ret = regmap_update_bits ( power - > regmap , AXP20X_VBUS_MON ,
AXP20X_VBUS_MON_VBUS_VALID ,
AXP20X_VBUS_MON_VBUS_VALID ) ;
if ( ret )
return ret ;
2015-08-01 11:39:38 +03:00
2017-01-27 11:54:36 +03:00
if ( IS_ENABLED ( CONFIG_AXP20X_ADC ) )
ret = configure_iio_channels ( pdev , power ) ;
else
ret = configure_adc_registers ( power ) ;
2016-06-02 20:18:53 +03:00
if ( ret )
return ret ;
usb_power_desc = & axp20x_usb_power_desc ;
irq_names = axp20x_irq_names ;
2016-12-09 14:04:13 +03:00
} else if ( power - > axp20x_id = = AXP221_ID | |
power - > axp20x_id = = AXP223_ID ) {
2016-06-02 20:18:53 +03:00
usb_power_desc = & axp22x_usb_power_desc ;
irq_names = axp22x_irq_names ;
} else {
dev_err ( & pdev - > dev , " Unsupported AXP variant: %ld \n " ,
axp20x - > variant ) ;
return - EINVAL ;
}
2015-08-01 11:39:38 +03:00
psy_cfg . of_node = pdev - > dev . of_node ;
psy_cfg . drv_data = power ;
2016-06-02 20:18:53 +03:00
power - > supply = devm_power_supply_register ( & pdev - > dev , usb_power_desc ,
& psy_cfg ) ;
2015-08-01 11:39:38 +03:00
if ( IS_ERR ( power - > supply ) )
return PTR_ERR ( power - > supply ) ;
/* Request irqs after registering, as irqs may trigger immediately */
2016-06-02 20:18:53 +03:00
for ( i = 0 ; irq_names [ i ] ; i + + ) {
2015-08-01 11:39:38 +03:00
irq = platform_get_irq_byname ( pdev , irq_names [ i ] ) ;
if ( irq < 0 ) {
dev_warn ( & pdev - > dev , " No IRQ for %s: %d \n " ,
irq_names [ i ] , irq ) ;
continue ;
}
irq = regmap_irq_get_virq ( axp20x - > regmap_irqc , irq ) ;
ret = devm_request_any_context_irq ( & pdev - > dev , irq ,
axp20x_usb_power_irq , 0 , DRVNAME , power ) ;
if ( ret < 0 )
dev_warn ( & pdev - > dev , " Error requesting %s IRQ: %d \n " ,
irq_names [ i ] , ret ) ;
}
return 0 ;
}
static const struct of_device_id axp20x_usb_power_match [ ] = {
2016-12-09 14:04:09 +03:00
{
. compatible = " x-powers,axp202-usb-power-supply " ,
. data = ( void * ) AXP202_ID ,
} , {
. compatible = " x-powers,axp221-usb-power-supply " ,
. data = ( void * ) AXP221_ID ,
2016-12-09 14:04:13 +03:00
} , {
. compatible = " x-powers,axp223-usb-power-supply " ,
. data = ( void * ) AXP223_ID ,
2016-12-09 14:04:09 +03:00
} , { /* sentinel */ }
2015-08-01 11:39:38 +03:00
} ;
MODULE_DEVICE_TABLE ( of , axp20x_usb_power_match ) ;
static struct platform_driver axp20x_usb_power_driver = {
. probe = axp20x_usb_power_probe ,
. driver = {
. name = DRVNAME ,
. of_match_table = axp20x_usb_power_match ,
} ,
} ;
module_platform_driver ( axp20x_usb_power_driver ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_DESCRIPTION ( " AXP20x PMIC USB power supply status driver " ) ;
MODULE_LICENSE ( " GPL " ) ;