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>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# 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)
# 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 ;
} ;
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 :
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-06-02 20:18:53 +03:00
if ( of_device_is_compatible ( power - > np ,
" x-powers,axp202-usb-power-supply " ) ) {
val - > intval = 100000 ;
} else {
val - > intval = - 1 ; /* No 100mA limit */
}
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 :
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-06-02 20:18:53 +03:00
if ( of_device_is_compatible ( power - > np ,
" x-powers,axp202-usb-power-supply " ) ) {
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 ;
}
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 ) ,
. get_property = axp20x_usb_power_get_property ,
} ;
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 ) ,
. get_property = axp20x_usb_power_get_property ,
} ;
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 } ;
static const char * const * irq_names ;
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 ;
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-06-02 20:18:53 +03:00
if ( of_device_is_compatible ( power - > np ,
" x-powers,axp202-usb-power-supply " ) ) {
/* 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
2016-06-02 20:18:53 +03:00
/* Enable vbus voltage and current measurement */
ret = regmap_update_bits ( power - > regmap , AXP20X_ADC_EN1 ,
2015-08-01 11:39:38 +03:00
AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT ,
AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT ) ;
2016-06-02 20:18:53 +03:00
if ( ret )
return ret ;
usb_power_desc = & axp20x_usb_power_desc ;
irq_names = axp20x_irq_names ;
} else if ( of_device_is_compatible ( power - > np ,
" x-powers,axp221-usb-power-supply " ) ) {
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 [ ] = {
{ . compatible = " x-powers,axp202-usb-power-supply " } ,
2016-06-02 20:18:53 +03:00
{ . compatible = " x-powers,axp221-usb-power-supply " } ,
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 " ) ;