2019-05-27 09:55:05 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2011-03-29 05:10:16 +04:00
/*
* max8903_charger . c - Maxim 8903 USB / Adapter Charger Driver
*
* Copyright ( C ) 2011 Samsung Electronics
* MyungJoo Ham < myungjoo . ham @ samsung . com >
*/
2021-01-10 17:02:00 +03:00
# include <linux/gpio/consumer.h>
2011-03-29 05:10:16 +04:00
# include <linux/interrupt.h>
2011-07-03 23:28:29 +04:00
# include <linux/module.h>
2016-06-24 05:26:12 +03:00
# include <linux/of.h>
# include <linux/of_device.h>
2011-03-29 05:10:16 +04:00
# include <linux/slab.h>
# include <linux/power_supply.h>
# include <linux/platform_device.h>
2021-01-10 17:01:59 +03:00
2011-03-29 05:10:16 +04:00
struct max8903_data {
struct device * dev ;
2015-03-12 10:44:11 +03:00
struct power_supply * psy ;
struct power_supply_desc psy_desc ;
2021-01-10 17:02:00 +03:00
/*
* GPIOs
* chg , flt , dcm and usus are optional .
* dok or uok must be present .
* If dok is present , cen must be present .
*/
struct gpio_desc * cen ; /* Charger Enable input */
struct gpio_desc * dok ; /* DC (Adapter) Power OK output */
struct gpio_desc * uok ; /* USB Power OK output */
struct gpio_desc * chg ; /* Charger status output */
struct gpio_desc * flt ; /* Fault output */
struct gpio_desc * dcm ; /* Current-Limit Mode input (1: DC, 2: USB) */
struct gpio_desc * usus ; /* USB Suspend Input (1: suspended) */
2011-03-29 05:10:16 +04:00
bool fault ;
bool usb_in ;
bool ta_in ;
} ;
static enum power_supply_property max8903_charger_props [ ] = {
POWER_SUPPLY_PROP_STATUS , /* Charger status output */
POWER_SUPPLY_PROP_ONLINE , /* External power source */
POWER_SUPPLY_PROP_HEALTH , /* Fault or OK */
} ;
static int max8903_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
2015-03-12 10:44:11 +03:00
struct max8903_data * data = power_supply_get_drvdata ( psy ) ;
2011-03-29 05:10:16 +04:00
switch ( psp ) {
case POWER_SUPPLY_PROP_STATUS :
val - > intval = POWER_SUPPLY_STATUS_UNKNOWN ;
2021-01-10 17:02:00 +03:00
if ( data - > chg ) {
if ( gpiod_get_value ( data - > chg ) )
/* CHG asserted */
2011-03-29 05:10:16 +04:00
val - > intval = POWER_SUPPLY_STATUS_CHARGING ;
else if ( data - > usb_in | | data - > ta_in )
val - > intval = POWER_SUPPLY_STATUS_NOT_CHARGING ;
else
val - > intval = POWER_SUPPLY_STATUS_DISCHARGING ;
}
break ;
case POWER_SUPPLY_PROP_ONLINE :
val - > intval = 0 ;
if ( data - > usb_in | | data - > ta_in )
val - > intval = 1 ;
break ;
case POWER_SUPPLY_PROP_HEALTH :
val - > intval = POWER_SUPPLY_HEALTH_GOOD ;
if ( data - > fault )
val - > intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE ;
break ;
default :
return - EINVAL ;
}
2016-06-24 05:26:12 +03:00
2011-03-29 05:10:16 +04:00
return 0 ;
}
static irqreturn_t max8903_dcin ( int irq , void * _data )
{
struct max8903_data * data = _data ;
bool ta_in ;
enum power_supply_type old_type ;
2021-01-10 17:02:00 +03:00
/*
* This means the line is asserted .
*
* The signal is active low , but the inversion is handled in the GPIO
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree .
*/
ta_in = gpiod_get_value ( data - > dok ) ;
2011-03-29 05:10:16 +04:00
if ( ta_in = = data - > ta_in )
return IRQ_HANDLED ;
data - > ta_in = ta_in ;
/* Set Current-Limit-Mode 1:DC 0:USB */
2021-01-10 17:02:00 +03:00
if ( data - > dcm )
gpiod_set_value ( data - > dcm , ta_in ) ;
/* Charger Enable / Disable */
if ( data - > cen ) {
int val ;
if ( ta_in )
/* Certainly enable if DOK is asserted */
val = 1 ;
else if ( data - > usb_in )
/* Enable if the USB charger is enabled */
val = 1 ;
else
/* Else default-disable */
val = 0 ;
gpiod_set_value ( data - > cen , val ) ;
}
2011-03-29 05:10:16 +04:00
dev_dbg ( data - > dev , " TA(DC-IN) Charger %s. \n " , ta_in ?
" Connected " : " Disconnected " ) ;
2015-03-12 10:44:11 +03:00
old_type = data - > psy_desc . type ;
2011-03-29 05:10:16 +04:00
if ( data - > ta_in )
2015-03-12 10:44:11 +03:00
data - > psy_desc . type = POWER_SUPPLY_TYPE_MAINS ;
2011-03-29 05:10:16 +04:00
else if ( data - > usb_in )
2015-03-12 10:44:11 +03:00
data - > psy_desc . type = POWER_SUPPLY_TYPE_USB ;
2011-03-29 05:10:16 +04:00
else
2015-03-12 10:44:11 +03:00
data - > psy_desc . type = POWER_SUPPLY_TYPE_BATTERY ;
2011-03-29 05:10:16 +04:00
2015-03-12 10:44:11 +03:00
if ( old_type ! = data - > psy_desc . type )
power_supply_changed ( data - > psy ) ;
2011-03-29 05:10:16 +04:00
return IRQ_HANDLED ;
}
static irqreturn_t max8903_usbin ( int irq , void * _data )
{
struct max8903_data * data = _data ;
bool usb_in ;
enum power_supply_type old_type ;
2021-01-10 17:02:00 +03:00
/*
* This means the line is asserted .
*
* The signal is active low , but the inversion is handled in the GPIO
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree .
*/
usb_in = gpiod_get_value ( data - > uok ) ;
2011-03-29 05:10:16 +04:00
if ( usb_in = = data - > usb_in )
return IRQ_HANDLED ;
data - > usb_in = usb_in ;
/* Do not touch Current-Limit-Mode */
2021-01-10 17:02:00 +03:00
/* Charger Enable / Disable */
if ( data - > cen ) {
int val ;
if ( usb_in )
/* Certainly enable if UOK is asserted */
val = 1 ;
else if ( data - > ta_in )
/* Enable if the DC charger is enabled */
val = 1 ;
else
/* Else default-disable */
val = 0 ;
gpiod_set_value ( data - > cen , val ) ;
}
2011-03-29 05:10:16 +04:00
dev_dbg ( data - > dev , " USB Charger %s. \n " , usb_in ?
" Connected " : " Disconnected " ) ;
2015-03-12 10:44:11 +03:00
old_type = data - > psy_desc . type ;
2011-03-29 05:10:16 +04:00
if ( data - > ta_in )
2015-03-12 10:44:11 +03:00
data - > psy_desc . type = POWER_SUPPLY_TYPE_MAINS ;
2011-03-29 05:10:16 +04:00
else if ( data - > usb_in )
2015-03-12 10:44:11 +03:00
data - > psy_desc . type = POWER_SUPPLY_TYPE_USB ;
2011-03-29 05:10:16 +04:00
else
2015-03-12 10:44:11 +03:00
data - > psy_desc . type = POWER_SUPPLY_TYPE_BATTERY ;
2011-03-29 05:10:16 +04:00
2015-03-12 10:44:11 +03:00
if ( old_type ! = data - > psy_desc . type )
power_supply_changed ( data - > psy ) ;
2011-03-29 05:10:16 +04:00
return IRQ_HANDLED ;
}
static irqreturn_t max8903_fault ( int irq , void * _data )
{
struct max8903_data * data = _data ;
bool fault ;
2021-01-10 17:02:00 +03:00
/*
* This means the line is asserted .
*
* The signal is active low , but the inversion is handled in the GPIO
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree .
*/
fault = gpiod_get_value ( data - > flt ) ;
2011-03-29 05:10:16 +04:00
if ( fault = = data - > fault )
return IRQ_HANDLED ;
data - > fault = fault ;
if ( fault )
dev_err ( data - > dev , " Charger suffers a fault and stops. \n " ) ;
else
dev_err ( data - > dev , " Charger recovered from a fault. \n " ) ;
return IRQ_HANDLED ;
}
2016-06-24 05:26:09 +03:00
static int max8903_setup_gpios ( struct platform_device * pdev )
2011-03-29 05:10:16 +04:00
{
2016-06-24 05:26:09 +03:00
struct max8903_data * data = platform_get_drvdata ( pdev ) ;
2011-03-29 05:10:16 +04:00
struct device * dev = & pdev - > dev ;
2021-01-10 17:02:00 +03:00
bool ta_in = false ;
bool usb_in = false ;
enum gpiod_flags flags ;
data - > dok = devm_gpiod_get_optional ( dev , " dok " , GPIOD_IN ) ;
if ( IS_ERR ( data - > dok ) )
return dev_err_probe ( dev , PTR_ERR ( data - > dok ) ,
" failed to get DOK GPIO " ) ;
if ( data - > dok ) {
gpiod_set_consumer_name ( data - > dok , data - > psy_desc . name ) ;
/*
* The DC OK is pulled up to 1 and goes low when a charger
* is plugged in ( active low ) but in the device tree the
* line is marked as GPIO_ACTIVE_LOW so we get a 1 ( asserted )
* here if the DC charger is plugged in .
*/
ta_in = gpiod_get_value ( data - > dok ) ;
2016-06-24 05:26:08 +03:00
}
2011-03-29 05:10:16 +04:00
2021-01-10 17:02:00 +03:00
data - > uok = devm_gpiod_get_optional ( dev , " uok " , GPIOD_IN ) ;
if ( IS_ERR ( data - > uok ) )
return dev_err_probe ( dev , PTR_ERR ( data - > uok ) ,
" failed to get UOK GPIO " ) ;
if ( data - > uok ) {
gpiod_set_consumer_name ( data - > uok , data - > psy_desc . name ) ;
/*
* The USB OK is pulled up to 1 and goes low when a USB charger
* is plugged in ( active low ) but in the device tree the
* line is marked as GPIO_ACTIVE_LOW so we get a 1 ( asserted )
* here if the USB charger is plugged in .
*/
usb_in = gpiod_get_value ( data - > uok ) ;
2011-03-29 05:10:16 +04:00
}
2021-01-10 17:02:00 +03:00
/* Either DC OK or USB OK must be provided */
if ( ! data - > dok & & ! data - > uok ) {
dev_err ( dev , " no valid power source \n " ) ;
return - EINVAL ;
2011-03-29 05:10:16 +04:00
}
2021-01-10 17:02:00 +03:00
/*
* If either charger is already connected at this point ,
* assert the CEN line and enable charging from the start .
*
* The line is active low but also marked with GPIO_ACTIVE_LOW
* in the device tree , so when we assert the line with
* GPIOD_OUT_HIGH the line will be driven low .
*/
flags = ( ta_in | | usb_in ) ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW ;
/*
* If DC OK is provided , Charger Enable CEN is compulsory
* so this is not optional here .
*/
data - > cen = devm_gpiod_get ( dev , " cen " , flags ) ;
if ( IS_ERR ( data - > cen ) )
return dev_err_probe ( dev , PTR_ERR ( data - > cen ) ,
" failed to get CEN GPIO " ) ;
gpiod_set_consumer_name ( data - > cen , data - > psy_desc . name ) ;
2011-03-29 05:10:16 +04:00
2021-01-10 17:02:00 +03:00
/*
* If the DC charger is connected , then select it .
*
* The DCM line should be marked GPIO_ACTIVE_HIGH in the
* device tree . Driving it high will enable the DC charger
* input over the USB charger input .
*/
flags = ta_in ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW ;
data - > dcm = devm_gpiod_get_optional ( dev , " dcm " , flags ) ;
if ( IS_ERR ( data - > dcm ) )
return dev_err_probe ( dev , PTR_ERR ( data - > dcm ) ,
" failed to get DCM GPIO " ) ;
gpiod_set_consumer_name ( data - > dcm , data - > psy_desc . name ) ;
data - > chg = devm_gpiod_get_optional ( dev , " chg " , GPIOD_IN ) ;
if ( IS_ERR ( data - > chg ) )
return dev_err_probe ( dev , PTR_ERR ( data - > chg ) ,
" failed to get CHG GPIO " ) ;
gpiod_set_consumer_name ( data - > chg , data - > psy_desc . name ) ;
data - > flt = devm_gpiod_get_optional ( dev , " flt " , GPIOD_IN ) ;
if ( IS_ERR ( data - > flt ) )
return dev_err_probe ( dev , PTR_ERR ( data - > flt ) ,
" failed to get FLT GPIO " ) ;
gpiod_set_consumer_name ( data - > flt , data - > psy_desc . name ) ;
data - > usus = devm_gpiod_get_optional ( dev , " usus " , GPIOD_IN ) ;
if ( IS_ERR ( data - > usus ) )
return dev_err_probe ( dev , PTR_ERR ( data - > usus ) ,
" failed to get USUS GPIO " ) ;
gpiod_set_consumer_name ( data - > usus , data - > psy_desc . name ) ;
2011-03-29 05:10:16 +04:00
data - > fault = false ;
data - > ta_in = ta_in ;
data - > usb_in = usb_in ;
2016-06-24 05:26:09 +03:00
return 0 ;
}
static int max8903_probe ( struct platform_device * pdev )
{
struct max8903_data * data ;
struct device * dev = & pdev - > dev ;
struct power_supply_config psy_cfg = { } ;
int ret = 0 ;
data = devm_kzalloc ( dev , sizeof ( struct max8903_data ) , GFP_KERNEL ) ;
2016-06-24 05:26:11 +03:00
if ( ! data )
2016-06-24 05:26:09 +03:00
return - ENOMEM ;
data - > dev = dev ;
platform_set_drvdata ( pdev , data ) ;
ret = max8903_setup_gpios ( pdev ) ;
if ( ret )
return ret ;
2015-03-12 10:44:11 +03:00
data - > psy_desc . name = " max8903_charger " ;
2016-06-24 05:26:09 +03:00
data - > psy_desc . type = ( data - > ta_in ) ? POWER_SUPPLY_TYPE_MAINS :
( ( data - > usb_in ) ? POWER_SUPPLY_TYPE_USB :
2011-03-29 05:10:16 +04:00
POWER_SUPPLY_TYPE_BATTERY ) ;
2015-03-12 10:44:11 +03:00
data - > psy_desc . get_property = max8903_get_property ;
data - > psy_desc . properties = max8903_charger_props ;
data - > psy_desc . num_properties = ARRAY_SIZE ( max8903_charger_props ) ;
2011-03-29 05:10:16 +04:00
2016-06-24 05:26:12 +03:00
psy_cfg . of_node = dev - > of_node ;
2015-03-12 10:44:11 +03:00
psy_cfg . drv_data = data ;
2015-09-17 16:25:00 +03:00
data - > psy = devm_power_supply_register ( dev , & data - > psy_desc , & psy_cfg ) ;
2015-03-12 10:44:11 +03:00
if ( IS_ERR ( data - > psy ) ) {
2011-03-29 05:10:16 +04:00
dev_err ( dev , " failed: power supply register. \n " ) ;
2015-09-17 16:25:00 +03:00
return PTR_ERR ( data - > psy ) ;
2011-03-29 05:10:16 +04:00
}
2021-01-10 17:02:00 +03:00
if ( data - > dok ) {
ret = devm_request_threaded_irq ( dev , gpiod_to_irq ( data - > dok ) ,
2015-11-19 10:12:59 +03:00
NULL , max8903_dcin ,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT ,
" MAX8903 DC IN " , data ) ;
2011-03-29 05:10:16 +04:00
if ( ret ) {
dev_err ( dev , " Cannot request irq %d for DC (%d) \n " ,
2021-01-10 17:02:00 +03:00
gpiod_to_irq ( data - > dok ) , ret ) ;
2015-09-17 16:25:00 +03:00
return ret ;
2011-03-29 05:10:16 +04:00
}
}
2021-01-10 17:02:00 +03:00
if ( data - > uok ) {
ret = devm_request_threaded_irq ( dev , gpiod_to_irq ( data - > uok ) ,
2015-11-19 10:12:59 +03:00
NULL , max8903_usbin ,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT ,
" MAX8903 USB IN " , data ) ;
2011-03-29 05:10:16 +04:00
if ( ret ) {
dev_err ( dev , " Cannot request irq %d for USB (%d) \n " ,
2021-01-10 17:02:00 +03:00
gpiod_to_irq ( data - > uok ) , ret ) ;
2015-09-17 16:25:00 +03:00
return ret ;
2011-03-29 05:10:16 +04:00
}
}
2021-01-10 17:02:00 +03:00
if ( data - > flt ) {
ret = devm_request_threaded_irq ( dev , gpiod_to_irq ( data - > flt ) ,
2015-11-19 10:12:59 +03:00
NULL , max8903_fault ,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT ,
" MAX8903 Fault " , data ) ;
2011-03-29 05:10:16 +04:00
if ( ret ) {
dev_err ( dev , " Cannot request irq %d for Fault (%d) \n " ,
2021-01-10 17:02:00 +03:00
gpiod_to_irq ( data - > flt ) , ret ) ;
2015-09-17 16:25:00 +03:00
return ret ;
2011-03-29 05:10:16 +04:00
}
}
return 0 ;
}
2016-06-24 05:26:12 +03:00
static const struct of_device_id max8903_match_ids [ ] = {
{ . compatible = " maxim,max8903 " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , max8903_match_ids ) ;
2011-03-29 05:10:16 +04:00
static struct platform_driver max8903_driver = {
. probe = max8903_probe ,
. driver = {
. name = " max8903-charger " ,
2016-06-24 05:26:12 +03:00
. of_match_table = max8903_match_ids
2011-03-29 05:10:16 +04:00
} ,
} ;
2011-11-26 08:01:10 +04:00
module_platform_driver ( max8903_driver ) ;
2011-03-29 05:10:16 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " MAX8903 Charger Driver " ) ;
MODULE_AUTHOR ( " MyungJoo Ham <myungjoo.ham@samsung.com> " ) ;
2011-06-24 18:26:36 +04:00
MODULE_ALIAS ( " platform:max8903-charger " ) ;