2019-05-27 08:55:00 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2010-10-21 17:55:01 +02:00
/*
* Copyright ( C ) 2010 , Lars - Peter Clausen < lars @ metafoo . de >
* Driver for chargers which report their online status through a GPIO pin
*/
# include <linux/device.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/power_supply.h>
# include <linux/slab.h>
2014-09-23 22:42:15 +02:00
# include <linux/of.h>
2016-12-26 21:18:55 +01:00
# include <linux/gpio/consumer.h>
2010-10-21 17:55:01 +02:00
# include <linux/power/gpio-charger.h>
2020-06-06 00:44:00 +02:00
struct gpio_mapping {
u32 limit_ua ;
u32 gpiodata ;
} __packed ;
2010-10-21 17:55:01 +02:00
struct gpio_charger {
2020-06-06 00:44:00 +02:00
struct device * dev ;
2010-10-21 17:55:01 +02:00
unsigned int irq ;
2019-04-14 14:40:39 +02:00
unsigned int charge_status_irq ;
2013-12-11 02:47:16 +04:00
bool wakeup_enabled ;
2010-10-21 17:55:01 +02:00
2015-03-12 08:44:11 +01:00
struct power_supply * charger ;
struct power_supply_desc charger_desc ;
2016-12-26 21:18:55 +01:00
struct gpio_desc * gpiod ;
2019-04-14 14:40:39 +02:00
struct gpio_desc * charge_status ;
2020-06-06 00:44:00 +02:00
struct gpio_descs * current_limit_gpios ;
struct gpio_mapping * current_limit_map ;
u32 current_limit_map_size ;
u32 charge_current_limit ;
2010-10-21 17:55:01 +02:00
} ;
static irqreturn_t gpio_charger_irq ( int irq , void * devid )
{
struct power_supply * charger = devid ;
power_supply_changed ( charger ) ;
return IRQ_HANDLED ;
}
static inline struct gpio_charger * psy_to_gpio_charger ( struct power_supply * psy )
{
2015-03-12 08:44:11 +01:00
return power_supply_get_drvdata ( psy ) ;
2010-10-21 17:55:01 +02:00
}
2020-06-06 00:44:00 +02:00
static int set_charge_current_limit ( struct gpio_charger * gpio_charger , int val )
{
struct gpio_mapping mapping ;
int ndescs = gpio_charger - > current_limit_gpios - > ndescs ;
struct gpio_desc * * gpios = gpio_charger - > current_limit_gpios - > desc ;
int i ;
if ( ! gpio_charger - > current_limit_map_size )
return - EINVAL ;
for ( i = 0 ; i < gpio_charger - > current_limit_map_size ; i + + ) {
if ( gpio_charger - > current_limit_map [ i ] . limit_ua < = val )
break ;
}
mapping = gpio_charger - > current_limit_map [ i ] ;
for ( i = 0 ; i < ndescs ; i + + ) {
bool val = ( mapping . gpiodata > > i ) & 1 ;
gpiod_set_value_cansleep ( gpios [ ndescs - i - 1 ] , val ) ;
}
gpio_charger - > charge_current_limit = mapping . limit_ua ;
dev_dbg ( gpio_charger - > dev , " set charge current limit to %d (requested: %d) \n " ,
gpio_charger - > charge_current_limit , val ) ;
return 0 ;
}
2010-10-21 17:55:01 +02:00
static int gpio_charger_get_property ( struct power_supply * psy ,
enum power_supply_property psp , union power_supply_propval * val )
{
struct gpio_charger * gpio_charger = psy_to_gpio_charger ( psy ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_ONLINE :
2016-12-26 21:18:55 +01:00
val - > intval = gpiod_get_value_cansleep ( gpio_charger - > gpiod ) ;
2010-10-21 17:55:01 +02:00
break ;
2019-04-14 14:40:39 +02:00
case POWER_SUPPLY_PROP_STATUS :
if ( gpiod_get_value_cansleep ( gpio_charger - > charge_status ) )
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
else
val - > intval = POWER_SUPPLY_STATUS_NOT_CHARGING ;
break ;
2020-06-06 00:44:00 +02:00
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX :
val - > intval = gpio_charger - > charge_current_limit ;
break ;
2010-10-21 17:55:01 +02:00
default :
return - EINVAL ;
}
return 0 ;
}
2020-06-06 00:44:00 +02:00
static int gpio_charger_set_property ( struct power_supply * psy ,
enum power_supply_property psp , const union power_supply_propval * val )
{
struct gpio_charger * gpio_charger = psy_to_gpio_charger ( psy ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX :
return set_charge_current_limit ( gpio_charger , val - > intval ) ;
default :
return - EINVAL ;
}
return 0 ;
}
static int gpio_charger_property_is_writeable ( struct power_supply * psy ,
enum power_supply_property psp )
{
switch ( psp ) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX :
return 1 ;
default :
break ;
}
return 0 ;
}
2018-03-05 19:05:55 +01:00
static enum power_supply_type gpio_charger_get_type ( struct device * dev )
2014-09-23 22:42:15 +02:00
{
const char * chargetype ;
2018-03-05 19:05:55 +01:00
if ( ! device_property_read_string ( dev , " charger-type " , & chargetype ) ) {
if ( ! strcmp ( " unknown " , chargetype ) )
return POWER_SUPPLY_TYPE_UNKNOWN ;
if ( ! strcmp ( " battery " , chargetype ) )
return POWER_SUPPLY_TYPE_BATTERY ;
if ( ! strcmp ( " ups " , chargetype ) )
return POWER_SUPPLY_TYPE_UPS ;
if ( ! strcmp ( " mains " , chargetype ) )
return POWER_SUPPLY_TYPE_MAINS ;
if ( ! strcmp ( " usb-sdp " , chargetype ) )
return POWER_SUPPLY_TYPE_USB ;
if ( ! strcmp ( " usb-dcp " , chargetype ) )
2018-09-29 10:03:27 +02:00
return POWER_SUPPLY_TYPE_USB ;
2018-03-05 19:05:55 +01:00
if ( ! strcmp ( " usb-cdp " , chargetype ) )
2018-09-29 10:03:27 +02:00
return POWER_SUPPLY_TYPE_USB ;
2018-03-05 19:05:55 +01:00
if ( ! strcmp ( " usb-aca " , chargetype ) )
2018-09-29 10:03:27 +02:00
return POWER_SUPPLY_TYPE_USB ;
2014-09-23 22:42:15 +02:00
}
2018-03-05 19:05:55 +01:00
dev_warn ( dev , " unknown charger type %s \n " , chargetype ) ;
2014-09-23 22:42:15 +02:00
2018-03-05 19:05:55 +01:00
return POWER_SUPPLY_TYPE_UNKNOWN ;
2014-09-23 22:42:15 +02:00
}
2019-04-14 14:40:39 +02:00
static int gpio_charger_get_irq ( struct device * dev , void * dev_id ,
struct gpio_desc * gpio )
{
int ret , irq = gpiod_to_irq ( gpio ) ;
if ( irq > 0 ) {
ret = devm_request_any_context_irq ( dev , irq , gpio_charger_irq ,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING ,
dev_name ( dev ) ,
dev_id ) ;
if ( ret < 0 ) {
dev_warn ( dev , " Failed to request irq: %d \n " , ret ) ;
irq = 0 ;
}
}
return irq ;
}
2020-06-06 00:44:00 +02:00
static int init_charge_current_limit ( struct device * dev ,
struct gpio_charger * gpio_charger )
{
int i , len ;
u32 cur_limit = U32_MAX ;
gpio_charger - > current_limit_gpios = devm_gpiod_get_array_optional ( dev ,
" charge-current-limit " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( gpio_charger - > current_limit_gpios ) ) {
dev_err ( dev , " error getting current-limit GPIOs \n " ) ;
return PTR_ERR ( gpio_charger - > current_limit_gpios ) ;
}
if ( ! gpio_charger - > current_limit_gpios )
return 0 ;
len = device_property_read_u32_array ( dev , " charge-current-limit-mapping " ,
NULL , 0 ) ;
if ( len < 0 )
return len ;
if ( len = = 0 | | len % 2 ) {
dev_err ( dev , " invalid charge-current-limit-mapping length \n " ) ;
return - EINVAL ;
}
gpio_charger - > current_limit_map = devm_kmalloc_array ( dev ,
len / 2 , sizeof ( * gpio_charger - > current_limit_map ) , GFP_KERNEL ) ;
if ( ! gpio_charger - > current_limit_map )
return - ENOMEM ;
gpio_charger - > current_limit_map_size = len / 2 ;
len = device_property_read_u32_array ( dev , " charge-current-limit-mapping " ,
( u32 * ) gpio_charger - > current_limit_map , len ) ;
if ( len < 0 )
return len ;
for ( i = 0 ; i < gpio_charger - > current_limit_map_size ; i + + ) {
if ( gpio_charger - > current_limit_map [ i ] . limit_ua > cur_limit ) {
dev_err ( dev , " charge-current-limit-mapping not sorted by current in descending order \n " ) ;
return - EINVAL ;
}
cur_limit = gpio_charger - > current_limit_map [ i ] . limit_ua ;
}
/* default to smallest current limitation for safety reasons */
len = gpio_charger - > current_limit_map_size - 1 ;
set_charge_current_limit ( gpio_charger ,
gpio_charger - > current_limit_map [ len ] . limit_ua ) ;
return 0 ;
}
2020-06-06 00:43:59 +02:00
/*
* The entries will be overwritten by driver ' s probe routine depending
* on the available features . This list ensures , that the array is big
* enough for all optional features .
*/
2018-03-05 19:05:55 +01:00
static enum power_supply_property gpio_charger_properties [ ] = {
POWER_SUPPLY_PROP_ONLINE ,
2020-06-06 00:43:59 +02:00
POWER_SUPPLY_PROP_STATUS ,
2020-06-06 00:44:00 +02:00
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX ,
2018-03-05 19:05:55 +01:00
} ;
2012-11-19 13:22:23 -05:00
static int gpio_charger_probe ( struct platform_device * pdev )
2010-10-21 17:55:01 +02:00
{
2018-01-17 21:31:49 +01:00
struct device * dev = & pdev - > dev ;
const struct gpio_charger_platform_data * pdata = dev - > platform_data ;
2015-03-12 08:44:02 +01:00
struct power_supply_config psy_cfg = { } ;
2010-10-21 17:55:01 +02:00
struct gpio_charger * gpio_charger ;
2015-03-12 08:44:11 +01:00
struct power_supply_desc * charger_desc ;
2019-04-14 14:40:39 +02:00
struct gpio_desc * charge_status ;
int charge_status_irq ;
int ret ;
2020-06-06 00:43:59 +02:00
int num_props = 0 ;
2010-10-21 17:55:01 +02:00
2018-03-05 19:05:55 +01:00
if ( ! pdata & & ! dev - > of_node ) {
dev_err ( dev , " No platform data \n " ) ;
return - ENOENT ;
2010-10-21 17:55:01 +02:00
}
2018-01-17 21:31:49 +01:00
gpio_charger = devm_kzalloc ( dev , sizeof ( * gpio_charger ) , GFP_KERNEL ) ;
2018-03-05 19:04:14 +01:00
if ( ! gpio_charger )
2010-11-18 23:08:37 +01:00
return - ENOMEM ;
2020-06-06 00:44:00 +02:00
gpio_charger - > dev = dev ;
2010-10-21 17:55:01 +02:00
2016-12-26 21:18:55 +01:00
/*
* This will fetch a GPIO descriptor from device tree , ACPI or
* boardfile descriptor tables . It ' s good to try this first .
*/
2020-06-06 00:43:59 +02:00
gpio_charger - > gpiod = devm_gpiod_get_optional ( dev , NULL , GPIOD_IN ) ;
2020-08-27 10:48:28 +02:00
if ( IS_ERR ( gpio_charger - > gpiod ) ) {
2016-12-26 21:18:55 +01:00
/* Just try again if this happens */
2020-08-26 16:48:56 +02:00
return dev_err_probe ( dev , PTR_ERR ( gpio_charger - > gpiod ) ,
" error getting GPIO descriptor \n " ) ;
2016-12-26 21:18:55 +01:00
}
2020-06-06 00:43:59 +02:00
if ( gpio_charger - > gpiod ) {
gpio_charger_properties [ num_props ] = POWER_SUPPLY_PROP_ONLINE ;
num_props + + ;
}
2019-04-14 14:40:39 +02:00
charge_status = devm_gpiod_get_optional ( dev , " charge-status " , GPIOD_IN ) ;
2020-06-06 00:43:59 +02:00
if ( IS_ERR ( charge_status ) )
return PTR_ERR ( charge_status ) ;
if ( charge_status ) {
gpio_charger - > charge_status = charge_status ;
gpio_charger_properties [ num_props ] = POWER_SUPPLY_PROP_STATUS ;
num_props + + ;
}
2019-04-14 14:40:39 +02:00
2020-06-06 00:44:00 +02:00
ret = init_charge_current_limit ( dev , gpio_charger ) ;
if ( ret < 0 )
return ret ;
if ( gpio_charger - > current_limit_map ) {
gpio_charger_properties [ num_props ] =
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX ;
num_props + + ;
}
2015-03-12 08:44:11 +01:00
charger_desc = & gpio_charger - > charger_desc ;
charger_desc - > properties = gpio_charger_properties ;
2020-06-06 00:43:59 +02:00
charger_desc - > num_properties = num_props ;
2015-03-12 08:44:11 +01:00
charger_desc - > get_property = gpio_charger_get_property ;
2020-06-06 00:44:00 +02:00
charger_desc - > set_property = gpio_charger_set_property ;
charger_desc - > property_is_writeable =
gpio_charger_property_is_writeable ;
2015-03-12 08:44:02 +01:00
2018-01-17 21:31:49 +01:00
psy_cfg . of_node = dev - > of_node ;
2015-03-12 08:44:11 +01:00
psy_cfg . drv_data = gpio_charger ;
2010-10-21 17:55:01 +02:00
2018-03-05 19:05:55 +01:00
if ( pdata ) {
charger_desc - > name = pdata - > name ;
charger_desc - > type = pdata - > type ;
psy_cfg . supplied_to = pdata - > supplied_to ;
psy_cfg . num_supplicants = pdata - > num_supplicants ;
} else {
charger_desc - > name = dev - > of_node - > name ;
charger_desc - > type = gpio_charger_get_type ( dev ) ;
}
if ( ! charger_desc - > name )
charger_desc - > name = pdev - > name ;
2018-01-17 21:31:49 +01:00
gpio_charger - > charger = devm_power_supply_register ( dev , charger_desc ,
& psy_cfg ) ;
2015-03-12 08:44:11 +01:00
if ( IS_ERR ( gpio_charger - > charger ) ) {
ret = PTR_ERR ( gpio_charger - > charger ) ;
2018-01-17 21:31:49 +01:00
dev_err ( dev , " Failed to register power supply: %d \n " , ret ) ;
2018-01-17 21:31:14 +01:00
return ret ;
2010-11-18 23:08:38 +01:00
}
2019-04-14 14:40:39 +02:00
gpio_charger - > irq = gpio_charger_get_irq ( dev , gpio_charger - > charger ,
gpio_charger - > gpiod ) ;
charge_status_irq = gpio_charger_get_irq ( dev , gpio_charger - > charger ,
gpio_charger - > charge_status ) ;
gpio_charger - > charge_status_irq = charge_status_irq ;
2010-10-21 17:55:01 +02:00
platform_set_drvdata ( pdev , gpio_charger ) ;
2018-01-17 21:31:49 +01:00
device_init_wakeup ( dev , 1 ) ;
2013-12-11 02:47:16 +04:00
2010-10-21 17:55:01 +02:00
return 0 ;
}
2011-04-06 16:55:20 -07:00
# ifdef CONFIG_PM_SLEEP
2013-12-11 02:47:16 +04:00
static int gpio_charger_suspend ( struct device * dev )
{
struct gpio_charger * gpio_charger = dev_get_drvdata ( dev ) ;
if ( device_may_wakeup ( dev ) )
gpio_charger - > wakeup_enabled =
2015-01-15 05:00:37 +03:00
! enable_irq_wake ( gpio_charger - > irq ) ;
2013-12-11 02:47:16 +04:00
return 0 ;
}
2011-04-06 16:55:20 -07:00
static int gpio_charger_resume ( struct device * dev )
{
2018-04-19 16:06:11 +02:00
struct gpio_charger * gpio_charger = dev_get_drvdata ( dev ) ;
2011-04-06 16:55:20 -07:00
2015-01-15 05:00:37 +03:00
if ( device_may_wakeup ( dev ) & & gpio_charger - > wakeup_enabled )
2013-12-11 02:47:16 +04:00
disable_irq_wake ( gpio_charger - > irq ) ;
2015-03-12 08:44:11 +01:00
power_supply_changed ( gpio_charger - > charger ) ;
2011-04-06 16:55:20 -07:00
return 0 ;
}
# endif
2013-12-11 02:47:16 +04:00
static SIMPLE_DEV_PM_OPS ( gpio_charger_pm_ops ,
gpio_charger_suspend , gpio_charger_resume ) ;
2011-04-06 16:55:20 -07:00
2014-09-23 22:42:15 +02:00
static const struct of_device_id gpio_charger_match [ ] = {
{ . compatible = " gpio-charger " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , gpio_charger_match ) ;
2010-10-21 17:55:01 +02:00
static struct platform_driver gpio_charger_driver = {
. probe = gpio_charger_probe ,
. driver = {
. name = " gpio-charger " ,
2011-04-06 16:55:20 -07:00
. pm = & gpio_charger_pm_ops ,
2014-09-23 22:42:15 +02:00
. of_match_table = gpio_charger_match ,
2010-10-21 17:55:01 +02:00
} ,
} ;
2011-11-26 12:01:10 +08:00
module_platform_driver ( gpio_charger_driver ) ;
2010-10-21 17:55:01 +02:00
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
2020-06-06 00:43:59 +02:00
MODULE_DESCRIPTION ( " Driver for chargers only communicating via GPIO(s) " ) ;
2010-10-21 17:55:01 +02:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:gpio-charger " ) ;